OrderRefundService.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. const db = require('../plugin/DataBase/db')
  2. const { insertLedgerRecord } = require('./Lepao/CountLedger')
  3. const { requestPaymentRefund } = require('./PaymentClient')
  4. const REFUND_WINDOW_MS = 7 * 24 * 60 * 60 * 1000
  5. const ORDER_STATE_COMPLETED = 2
  6. const ORDER_STATE_REFUNDED = 5
  7. function evaluateRefundEligibility({
  8. state,
  9. payTime,
  10. userLepaoCount,
  11. goodsLepaoCount,
  12. allowRefund = 1,
  13. skipTimeLimit = false
  14. }) {
  15. if (Number(state) === ORDER_STATE_REFUNDED) return { canRefund: false, reason: '订单已退款' }
  16. if (Number(state) !== ORDER_STATE_COMPLETED) return { canRefund: false, reason: '仅已完成订单可申请退款' }
  17. if (!skipTimeLimit && Number(allowRefund) === 0) return { canRefund: false, reason: '该商品不支持自助退款' }
  18. if (!payTime) return { canRefund: false, reason: '订单支付时间异常' }
  19. if (!skipTimeLimit && Date.now() - Number(payTime) > REFUND_WINDOW_MS) return { canRefund: false, reason: '已超过 7 天退款期限' }
  20. const purchasedCount = Number(goodsLepaoCount || 0)
  21. const remainingCount = Number(userLepaoCount || 0)
  22. if (purchasedCount > 0 && remainingCount <= purchasedCount) {
  23. return { canRefund: false, reason: '账户剩余次数需大于订单购买次数才可退款' }
  24. }
  25. return { canRefund: true, reason: '' }
  26. }
  27. async function loadRefundContext(orderId) {
  28. const rows = await db.query(
  29. `SELECT
  30. o.orderId,
  31. o.state,
  32. o.price,
  33. o.pay_id,
  34. o.pay_time,
  35. o.create_user,
  36. g.lepao_count,
  37. g.ic_count,
  38. g.allow_refund
  39. FROM orders o
  40. LEFT JOIN goods g ON o.goods_id = g.id
  41. WHERE o.orderId = ?
  42. LIMIT 1`,
  43. [orderId]
  44. )
  45. if (!rows || rows.length !== 1) return null
  46. const order = rows[0]
  47. const userRows = await db.query(
  48. 'SELECT lepao_count, ic_count FROM users WHERE uuid = ? LIMIT 1',
  49. [order.create_user]
  50. )
  51. if (!userRows || userRows.length !== 1) return null
  52. return { order, user: userRows[0] }
  53. }
  54. async function executeOrderRefund({
  55. orderId,
  56. operatorUuid = null,
  57. skipTimeLimit = false,
  58. logger
  59. }) {
  60. try {
  61. const context = await loadRefundContext(orderId)
  62. if (!context) return { ok: false, msg: '订单或用户不存在' }
  63. const { order, user } = context
  64. const eligibility = evaluateRefundEligibility({
  65. state: order.state,
  66. payTime: order.pay_time,
  67. userLepaoCount: user.lepao_count,
  68. goodsLepaoCount: order.lepao_count,
  69. allowRefund: order.allow_refund,
  70. skipTimeLimit
  71. })
  72. if (!eligibility.canRefund) return { ok: false, msg: eligibility.reason }
  73. const deductLepao = Number(order.lepao_count || 0)
  74. const deductIc = Number(order.ic_count || 0)
  75. if (deductLepao > 0 && Number(user.lepao_count || 0) < deductLepao) {
  76. return { ok: false, msg: '账户乐跑次数不足,无法完成退款' }
  77. }
  78. await requestPaymentRefund({
  79. orderId: order.orderId,
  80. tradeNo: order.pay_id,
  81. money: order.price,
  82. logger
  83. })
  84. const conn = await db.connect()
  85. try {
  86. await conn.beginTransaction()
  87. const [orderRows] = await conn.execute(
  88. `SELECT
  89. o.orderId,
  90. o.state,
  91. o.create_user,
  92. g.lepao_count,
  93. g.ic_count
  94. FROM orders o
  95. LEFT JOIN goods g ON o.goods_id = g.id
  96. WHERE o.orderId = ?
  97. FOR UPDATE`,
  98. [orderId]
  99. )
  100. if (!orderRows || orderRows.length !== 1) {
  101. await conn.rollback()
  102. return { ok: false, msg: '订单不存在' }
  103. }
  104. const lockedOrder = orderRows[0]
  105. if (Number(lockedOrder.state) !== ORDER_STATE_COMPLETED) {
  106. await conn.rollback()
  107. return { ok: false, msg: '订单状态已变更,请刷新后重试' }
  108. }
  109. const [userRows] = await conn.execute(
  110. 'SELECT lepao_count, ic_count FROM users WHERE uuid = ? FOR UPDATE',
  111. [lockedOrder.create_user]
  112. )
  113. if (!userRows || userRows.length !== 1) {
  114. await conn.rollback()
  115. return { ok: false, msg: '用户不存在' }
  116. }
  117. const lockedUser = userRows[0]
  118. const lockedDeductLepao = Number(lockedOrder.lepao_count || 0)
  119. const lockedDeductIc = Number(lockedOrder.ic_count || 0)
  120. const beforeLepao = Number(lockedUser.lepao_count || 0)
  121. const beforeIc = Number(lockedUser.ic_count || 0)
  122. if (lockedDeductLepao > 0 && beforeLepao < lockedDeductLepao) {
  123. await conn.rollback()
  124. logger?.error?.(`Refund paid but count deduction failed, orderId=${orderId}`)
  125. return { ok: false, msg: '支付已退款但扣减次数失败,请联系客服处理' }
  126. }
  127. const afterLepao = beforeLepao - lockedDeductLepao
  128. const afterIc = Math.max(0, beforeIc - lockedDeductIc)
  129. const [updateUserRes] = await conn.execute(
  130. 'UPDATE users SET lepao_count = ?, ic_count = ? WHERE uuid = ?',
  131. [afterLepao, afterIc, lockedOrder.create_user]
  132. )
  133. if (!updateUserRes || updateUserRes.affectedRows !== 1) {
  134. await conn.rollback()
  135. logger?.error?.(`Refund paid but user update failed, orderId=${orderId}`)
  136. return { ok: false, msg: '支付已退款但更新账户失败,请联系客服处理' }
  137. }
  138. const [updateOrderRes] = await conn.execute(
  139. 'UPDATE orders SET state = ? WHERE orderId = ? AND state = ?',
  140. [ORDER_STATE_REFUNDED, orderId, ORDER_STATE_COMPLETED]
  141. )
  142. if (!updateOrderRes || updateOrderRes.affectedRows !== 1) {
  143. await conn.rollback()
  144. logger?.error?.(`Refund paid but order update failed, orderId=${orderId}`)
  145. return { ok: false, msg: '支付已退款但更新订单失败,请联系客服处理' }
  146. }
  147. if (lockedDeductLepao !== 0) {
  148. await insertLedgerRecord({
  149. executor: conn,
  150. userUuid: lockedOrder.create_user,
  151. delta: -lockedDeductLepao,
  152. balanceBefore: beforeLepao,
  153. balanceAfter: afterLepao,
  154. bizType: 'purchase_refund',
  155. bizId: orderId,
  156. operatorUuid,
  157. remark: `订单退款:${orderId}`
  158. })
  159. }
  160. await conn.commit()
  161. return { ok: true, msg: '退款成功' }
  162. } catch (dbError) {
  163. try { await conn.rollback() } catch (_) { }
  164. logger?.error?.(`Refund ledger failed ${orderId}: ${dbError.stack || dbError}`)
  165. return { ok: false, msg: '支付已退款但入账失败,请联系客服处理' }
  166. }
  167. } catch (error) {
  168. logger?.error?.(`Order refund failed ${orderId}: ${error.stack || error}`)
  169. return { ok: false, msg: error.message || '退款失败,请稍后再试' }
  170. }
  171. }
  172. module.exports = {
  173. REFUND_WINDOW_MS,
  174. ORDER_STATE_REFUNDED,
  175. evaluateRefundEligibility,
  176. executeOrderRefund
  177. }