CouponService.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. const db = require('../plugin/DataBase/db')
  2. function roundMoney(n) {
  3. return Math.round(Number(n) * 100) / 100
  4. }
  5. function calcDiscount(price, discountType, discountValue) {
  6. const p = Number(price)
  7. let discount = 0
  8. if (discountType === 'percent') {
  9. const pct = Math.min(100, Math.max(0, Number(discountValue)))
  10. discount = roundMoney((p * pct) / 100)
  11. } else {
  12. discount = roundMoney(Math.min(p, Math.max(0, Number(discountValue))))
  13. }
  14. let finalPrice = roundMoney(p - discount)
  15. if (finalPrice < 0.01) finalPrice = 0.01
  16. if (finalPrice > p) finalPrice = p
  17. discount = roundMoney(p - finalPrice)
  18. return { discountAmount: discount, finalPrice }
  19. }
  20. async function countActiveUsage(couponId, userUuid) {
  21. const totalRows = await db.query(
  22. `SELECT COUNT(*) AS cnt FROM coupon_usage
  23. WHERE coupon_id = ?`,
  24. [couponId]
  25. )
  26. const userRows = await db.query(
  27. `SELECT COUNT(*) AS cnt FROM coupon_usage
  28. WHERE coupon_id = ? AND user_uuid = ?`,
  29. [couponId, userUuid]
  30. )
  31. return {
  32. total: Number(totalRows?.[0]?.cnt || 0),
  33. perUser: Number(userRows?.[0]?.cnt || 0)
  34. }
  35. }
  36. /**
  37. * 校验优惠码并计算优惠后价格
  38. */
  39. async function validateCoupon({ code, userUuid, goodsId, goodsPrice }) {
  40. const normalizedCode = String(code || '').trim().toUpperCase()
  41. if (!normalizedCode) {
  42. return { ok: false, msg: '请输入优惠码' }
  43. }
  44. const rows = await db.query(
  45. `SELECT * FROM coupons WHERE code = ? AND state = 1 LIMIT 1`,
  46. [normalizedCode]
  47. )
  48. if (!rows || rows.length !== 1) {
  49. return { ok: false, msg: '优惠码不存在或已失效' }
  50. }
  51. const coupon = rows[0]
  52. const now = Date.now()
  53. if (coupon.start_time && now < Number(coupon.start_time)) {
  54. return { ok: false, msg: '优惠码尚未生效' }
  55. }
  56. if (coupon.end_time && now > Number(coupon.end_time)) {
  57. return { ok: false, msg: '优惠码已过期' }
  58. }
  59. const price = Number(goodsPrice)
  60. const minAmount = Number(coupon.min_amount || 0)
  61. if (minAmount > 0 && price < minAmount) {
  62. return { ok: false, msg: `订单满 ¥${minAmount} 才可使用该优惠码` }
  63. }
  64. if (Number(coupon.goods_scope) === 1) {
  65. const goodsRows = await db.query(
  66. 'SELECT 1 FROM coupon_goods WHERE coupon_id = ? AND goods_id = ? LIMIT 1',
  67. [coupon.id, goodsId]
  68. )
  69. if (!goodsRows || goodsRows.length === 0) {
  70. return { ok: false, msg: '该优惠码不适用于当前商品' }
  71. }
  72. }
  73. if (Number(coupon.user_scope) === 1) {
  74. const userRows = await db.query(
  75. 'SELECT 1 FROM coupon_users WHERE coupon_id = ? AND user_uuid = ? LIMIT 1',
  76. [coupon.id, userUuid]
  77. )
  78. if (!userRows || userRows.length === 0) {
  79. return { ok: false, msg: '您暂无使用该优惠码的权限' }
  80. }
  81. }
  82. const usage = await countActiveUsage(coupon.id, userUuid)
  83. const totalLimit = Number(coupon.total_limit || 0)
  84. const perUserLimit = Number(coupon.per_user_limit || 1)
  85. if (totalLimit > 0 && usage.total >= totalLimit) {
  86. return { ok: false, msg: '优惠码已达使用上限' }
  87. }
  88. if (usage.perUser >= perUserLimit) {
  89. return { ok: false, msg: '您已达到该优惠码的使用次数上限' }
  90. }
  91. const { discountAmount, finalPrice } = calcDiscount(
  92. price,
  93. coupon.discount_type,
  94. coupon.discount_value
  95. )
  96. if (discountAmount <= 0) {
  97. return { ok: false, msg: '优惠码无效,未产生优惠' }
  98. }
  99. return {
  100. ok: true,
  101. couponId: coupon.id,
  102. code: coupon.code,
  103. name: coupon.name,
  104. discountType: coupon.discount_type,
  105. discountValue: Number(coupon.discount_value),
  106. originalPrice: roundMoney(price),
  107. discountAmount,
  108. finalPrice,
  109. displayDiscount:
  110. coupon.discount_type === 'percent'
  111. ? `${coupon.discount_value}% 折扣`
  112. : `立减 ¥${coupon.discount_value}`
  113. }
  114. }
  115. async function recordUsage(couponId, orderId, userUuid, discountAmount) {
  116. const time = Date.now()
  117. await db.query(
  118. `INSERT INTO coupon_usage (coupon_id, order_id, user_uuid, discount_amount, create_time)
  119. VALUES (?, ?, ?, ?, ?)`,
  120. [couponId, orderId, userUuid, discountAmount, time]
  121. )
  122. await db.query('UPDATE coupons SET used_count = used_count + 1 WHERE id = ?', [couponId])
  123. }
  124. async function releaseUsageForOrder(orderId) {
  125. const rows = await db.query(
  126. 'SELECT coupon_id FROM orders WHERE orderId = ? AND coupon_id IS NOT NULL LIMIT 1',
  127. [orderId]
  128. )
  129. if (!rows || !rows[0]?.coupon_id) return
  130. const couponId = rows[0].coupon_id
  131. const del = await db.query('DELETE FROM coupon_usage WHERE order_id = ?', [orderId])
  132. if (del?.affectedRows > 0) {
  133. await db.query(
  134. 'UPDATE coupons SET used_count = GREATEST(used_count - 1, 0) WHERE id = ?',
  135. [couponId]
  136. )
  137. }
  138. }
  139. module.exports = {
  140. roundMoney,
  141. calcDiscount,
  142. validateCoupon,
  143. recordUsage,
  144. releaseUsageForOrder
  145. }