OrderRefundService.js 7.5 KB

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