Browse Source

🐞 fix: 修复优惠码重复使用问题

Pchen. 3 weeks ago
parent
commit
488cc2fa9b
3 changed files with 54 additions and 9 deletions
  1. 6 2
      apis/Order/CallBack.js
  2. 44 1
      apis/Order/CreateOrder.js
  3. 4 6
      lib/CouponService.js

+ 6 - 2
apis/Order/CallBack.js

@@ -4,6 +4,7 @@ const { BaseStdResponse } = require("../../BaseStdResponse.js")
 const config = require('../../config.json')
 const crypto = require("crypto")
 const { insertLedgerRecord } = require('../../lib/Lepao/CountLedger')
+const { releaseUsageForOrder } = require('../../lib/CouponService')
 
 const PAYMENT_KEY = config.pay.key
 
@@ -68,8 +69,11 @@ class CallBack extends API {
 
         // 支付未成功,标记为失败
         if (trade_status !== 'TRADE_SUCCESS') {
-            const sql = 'UPDATE orders SET state = 3, pay_id = ? WHERE orderId = ?'
-            await db.query(sql, [trade_no, out_trade_no])
+            const sql = 'UPDATE orders SET state = 3, pay_id = ? WHERE orderId = ? AND state = 0'
+            const updateRes = await db.query(sql, [trade_no, out_trade_no])
+            if (updateRes?.affectedRows > 0) {
+                await releaseUsageForOrder(out_trade_no)
+            }
             this.logger.info(`支付未成功。订单号:${out_trade_no}`)
             return res.send('success')
         }

+ 44 - 1
apis/Order/CreateOrder.js

@@ -189,6 +189,21 @@ function generatePaymentSign(params, key) {
     return crypto.createHash('md5').update(query, 'utf8').digest('hex')
 }
 
+async function acquireCouponUsageLock(couponId) {
+    const lockKey = `coupon:usage:${couponId}`
+    const rows = await db.query('SELECT GET_LOCK(?, 5) AS ok', [lockKey])
+    return { lockKey, ok: Number(rows?.[0]?.ok || 0) === 1 }
+}
+
+async function releaseCouponUsageLock(lockKey) {
+    if (!lockKey) return
+    try {
+        await db.query('SELECT RELEASE_LOCK(?)', [lockKey])
+    } catch (e) {
+        // 释放失败仅记录,不影响主流程
+    }
+}
+
 class CreateOrder extends API {
     constructor() {
         super()
@@ -242,9 +257,34 @@ class CreateOrder extends API {
             let discountAmount = 0
             let couponId = null
             let appliedCouponCode = null
+            let couponLockKey = null
 
             if (coupon_code && String(coupon_code).trim()) {
-                const couponResult = await validateCoupon({
+                let couponResult = await validateCoupon({
+                    code: coupon_code,
+                    userUuid: uuid,
+                    goodsId: goods_id,
+                    goodsPrice: goods.price
+                })
+                if (!couponResult.ok) {
+                    return res.json({ ...BaseStdResponse.ERR, msg: couponResult.msg })
+                }
+                finalPrice = couponResult.finalPrice
+                discountAmount = couponResult.discountAmount
+                couponId = couponResult.couponId
+                appliedCouponCode = couponResult.code
+
+                const lockRet = await acquireCouponUsageLock(couponId)
+                if (!lockRet.ok) {
+                    return res.json({
+                        ...BaseStdResponse.ERR,
+                        msg: '优惠码校验繁忙,请稍后重试'
+                    })
+                }
+                couponLockKey = lockRet.lockKey
+
+                // 拿到锁后重查一次,避免并发下单绕过“每人限用次数”
+                couponResult = await validateCoupon({
                     code: coupon_code,
                     userUuid: uuid,
                     goodsId: goods_id,
@@ -253,6 +293,7 @@ class CreateOrder extends API {
                 if (!couponResult.ok) {
                     return res.json({ ...BaseStdResponse.ERR, msg: couponResult.msg })
                 }
+
                 finalPrice = couponResult.finalPrice
                 discountAmount = couponResult.discountAmount
                 couponId = couponResult.couponId
@@ -359,6 +400,8 @@ class CreateOrder extends API {
                 ...BaseStdResponse.ERR,
                 msg: "创建订单异常,请联系管理员"
             })
+        } finally {
+            await releaseCouponUsageLock(couponLockKey)
         }
     }
 }

+ 4 - 6
lib/CouponService.js

@@ -22,15 +22,13 @@ function calcDiscount(price, discountType, discountValue) {
 
 async function countActiveUsage(couponId, userUuid) {
     const totalRows = await db.query(
-        `SELECT COUNT(*) AS cnt FROM coupon_usage cu
-         INNER JOIN orders o ON o.orderId = cu.order_id
-         WHERE cu.coupon_id = ? AND o.state IN (0, 1, 2)`,
+        `SELECT COUNT(*) AS cnt FROM coupon_usage
+         WHERE coupon_id = ?`,
         [couponId]
     )
     const userRows = await db.query(
-        `SELECT COUNT(*) AS cnt FROM coupon_usage cu
-         INNER JOIN orders o ON o.orderId = cu.order_id
-         WHERE cu.coupon_id = ? AND cu.user_uuid = ? AND o.state IN (0, 1, 2)`,
+        `SELECT COUNT(*) AS cnt FROM coupon_usage
+         WHERE coupon_id = ? AND user_uuid = ?`,
         [couponId, userUuid]
     )
     return {