OrderSettlement.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. const db = require('../plugin/DataBase/db')
  2. const Redis = require('../plugin/DataBase/Redis')
  3. const { releaseUsageForOrder } = require('./CouponService')
  4. const { insertLedgerRecord } = require('./Lepao/CountLedger')
  5. const { calculateNextVipExpire } = require('./VipService')
  6. const ORDER_STATE_PENDING = 0
  7. const ORDER_STATE_PROCESSING = 1
  8. const ORDER_STATE_COMPLETED = 2
  9. const ORDER_STATE_CLOSED = 3
  10. const ORDER_STATE_ERROR = 4
  11. function normalizeResult(result) {
  12. return Array.isArray(result) ? result[0] : result
  13. }
  14. async function closePendingOrder({ orderId, operatorUuid = null, payId = null, logger } = {}) {
  15. if (!orderId) return { closed: false, reason: 'missing_order_id' }
  16. const conn = await db.connect()
  17. try {
  18. await conn.beginTransaction()
  19. const [orderRows] = await conn.execute(
  20. 'SELECT orderId, state, goods_id, create_user FROM orders WHERE orderId = ? FOR UPDATE',
  21. [orderId]
  22. )
  23. const order = orderRows?.[0]
  24. if (!order) {
  25. await conn.rollback()
  26. return { closed: false, reason: 'not_found' }
  27. }
  28. if (Number(order.state) !== ORDER_STATE_PENDING) {
  29. await conn.rollback()
  30. return { closed: false, reason: 'state_changed', state: Number(order.state) }
  31. }
  32. const params = payId
  33. ? [ORDER_STATE_CLOSED, payId, orderId, ORDER_STATE_PENDING]
  34. : [ORDER_STATE_CLOSED, orderId, ORDER_STATE_PENDING]
  35. const sql = payId
  36. ? 'UPDATE orders SET state = ?, pay_id = ? WHERE orderId = ? AND state = ?'
  37. : 'UPDATE orders SET state = ? WHERE orderId = ? AND state = ?'
  38. const [closeRes] = await conn.execute(sql, params)
  39. if (!closeRes || closeRes.affectedRows !== 1) {
  40. await conn.rollback()
  41. return { closed: false, reason: 'update_failed' }
  42. }
  43. if (order.goods_id != null) {
  44. await conn.execute('UPDATE goods SET num = num + 1 WHERE id = ?', [order.goods_id])
  45. }
  46. await conn.commit()
  47. } catch (error) {
  48. try { await conn.rollback() } catch (_) { }
  49. logger?.error?.(`关闭订单失败 ${orderId}: ${error.stack || error}`)
  50. throw error
  51. }
  52. try {
  53. await releaseUsageForOrder(orderId)
  54. await Redis.del(`payData:${orderId}`)
  55. } catch (cleanupError) {
  56. logger?.warn?.(`关闭订单后清理资源失败 ${orderId}: ${cleanupError.stack || cleanupError}`)
  57. }
  58. logger?.info?.(`订单已关闭并返还库存,订单号:${orderId}${operatorUuid ? `,操作人:${operatorUuid}` : ''}`)
  59. return { closed: true }
  60. }
  61. async function completePaidOrder({ orderId, payType, payId, payTime = Date.now(), paidAmount = null, logger } = {}) {
  62. if (!orderId) return { completed: false, reason: 'missing_order_id' }
  63. const conn = await db.connect()
  64. try {
  65. await conn.beginTransaction()
  66. const [lockRows] = await conn.execute(
  67. 'SELECT orderId, state, price FROM orders WHERE orderId = ? FOR UPDATE',
  68. [orderId]
  69. )
  70. const locked = lockRows?.[0]
  71. if (!locked || Number(locked.state) !== ORDER_STATE_PENDING) {
  72. await conn.rollback()
  73. return {
  74. completed: false,
  75. reason: locked ? 'state_changed' : 'not_found',
  76. state: locked ? Number(locked.state) : null
  77. }
  78. }
  79. if (paidAmount !== null && Math.abs(Number(locked.price) - Number(paidAmount)) > 0.001) {
  80. await conn.rollback()
  81. logger?.error?.(`订单支付金额不匹配,订单号:${orderId},应付:${locked.price},实付:${paidAmount}`)
  82. return { completed: false, reason: 'amount_mismatch' }
  83. }
  84. const [markRows] = await conn.execute(
  85. 'UPDATE orders SET state = ?, pay_type = ?, pay_id = ?, pay_time = ? WHERE orderId = ? AND state = ?',
  86. [ORDER_STATE_PROCESSING, payType, payId, payTime, orderId, ORDER_STATE_PENDING]
  87. )
  88. if (!markRows || markRows.affectedRows !== 1) {
  89. await conn.rollback()
  90. return { completed: false, reason: 'state_changed' }
  91. }
  92. const [orderRows] = await conn.execute(
  93. `SELECT
  94. o.create_user,
  95. g.lepao_count,
  96. g.ic_count,
  97. g.vip,
  98. g.vip_validity_type,
  99. g.vip_valid_days,
  100. g.vip_fixed_expire_time
  101. FROM orders o
  102. LEFT JOIN goods g ON o.goods_id = g.id
  103. WHERE o.orderId = ?
  104. LIMIT 1`,
  105. [orderId]
  106. )
  107. const order = orderRows?.[0]
  108. if (!order || !order.create_user) {
  109. await conn.execute('UPDATE orders SET state = ? WHERE orderId = ?', [ORDER_STATE_ERROR, orderId])
  110. await conn.commit()
  111. return { completed: false, reason: 'bad_order_data' }
  112. }
  113. const addLepao = Number(order.lepao_count || 0)
  114. const addIc = Number(order.ic_count || 0)
  115. const [userRows] = await conn.execute(
  116. 'SELECT lepao_count, vip, vip_expire_time FROM users WHERE uuid = ? FOR UPDATE',
  117. [order.create_user]
  118. )
  119. if (!userRows || userRows.length !== 1) {
  120. await conn.execute('UPDATE orders SET state = ? WHERE orderId = ?', [ORDER_STATE_ERROR, orderId])
  121. await conn.commit()
  122. return { completed: false, reason: 'user_not_found' }
  123. }
  124. const beforeLepao = Number(userRows[0].lepao_count || 0)
  125. const afterLepao = beforeLepao + addLepao
  126. const nextVipExpireTime = Number(order.vip || 0) === 1
  127. ? calculateNextVipExpire({
  128. currentExpireTime: userRows[0].vip_expire_time,
  129. payTime,
  130. validityType: order.vip_validity_type,
  131. validDays: order.vip_valid_days,
  132. fixedExpireTime: order.vip_fixed_expire_time
  133. })
  134. : Number(userRows[0].vip_expire_time || 0)
  135. const nextVip = nextVipExpireTime > Date.now() || (Number(userRows[0].vip || 0) === 1 && nextVipExpireTime === 0) || (Number(order.vip || 0) === 1 && nextVipExpireTime === 0) ? 1 : 0
  136. const [updateUserRes] = await conn.execute(
  137. 'UPDATE users SET lepao_count = lepao_count + ?, ic_count = ic_count + ?, vip = ?, vip_expire_time = ? WHERE uuid = ?',
  138. [addLepao, addIc, nextVip, nextVipExpireTime, order.create_user]
  139. )
  140. if (!updateUserRes || updateUserRes.affectedRows !== 1) {
  141. await conn.execute('UPDATE orders SET state = ? WHERE orderId = ?', [ORDER_STATE_ERROR, orderId])
  142. await conn.commit()
  143. return { completed: false, reason: 'update_user_failed' }
  144. }
  145. const [finishRes] = await conn.execute(
  146. 'UPDATE orders SET state = ? WHERE orderId = ? AND state = ?',
  147. [ORDER_STATE_COMPLETED, orderId, ORDER_STATE_PROCESSING]
  148. )
  149. if (!finishRes || finishRes.affectedRows !== 1) {
  150. await conn.rollback()
  151. return { completed: false, reason: 'finish_failed' }
  152. }
  153. if (addLepao !== 0) {
  154. await insertLedgerRecord({
  155. executor: conn,
  156. userUuid: order.create_user,
  157. delta: addLepao,
  158. balanceBefore: beforeLepao,
  159. balanceAfter: afterLepao,
  160. bizType: 'purchase',
  161. bizId: orderId,
  162. remark: `订单号:${orderId}`
  163. })
  164. }
  165. await conn.commit()
  166. try { await Redis.del(`payData:${orderId}`) } catch (_) { }
  167. logger?.info?.(`订单支付处理完成,订单号:${orderId}`)
  168. return { completed: true, userUuid: order.create_user, addLepao }
  169. } catch (error) {
  170. try { await conn.rollback() } catch (_) { }
  171. logger?.error?.(`完成订单失败 ${orderId}: ${error.stack || error}`)
  172. throw error
  173. }
  174. }
  175. module.exports = {
  176. ORDER_STATE_PENDING,
  177. ORDER_STATE_PROCESSING,
  178. ORDER_STATE_COMPLETED,
  179. ORDER_STATE_CLOSED,
  180. ORDER_STATE_ERROR,
  181. closePendingOrder,
  182. completePaidOrder,
  183. normalizeResult
  184. }