diff --git a/internal/service/user/order_timeout.go b/internal/service/user/order_timeout.go index f41b49d..d04fc75 100755 --- a/internal/service/user/order_timeout.go +++ b/internal/service/user/order_timeout.go @@ -65,41 +65,52 @@ func (s *service) cleanupExpiredOrders() { func (s *service) cancelExpiredOrder(ctx context.Context, orderID int64, userID int64, couponID int64, pointsAmount int64) { // 1. 恢复优惠券 if couponID > 0 { - type couponRow struct { - AppliedAmount int64 - DiscountType int32 - } - var cr couponRow - s.readDB.OrderCoupons.WithContext(ctx).UnderlyingDB().Raw(` - SELECT oc.applied_amount, sc.discount_type - FROM order_coupons oc - JOIN user_coupons uc ON uc.id = oc.user_coupon_id - JOIN system_coupons sc ON sc.id = uc.coupon_id - WHERE oc.order_id = ? AND oc.user_coupon_id = ? - `, orderID, couponID).Scan(&cr) + // 幂等校验:若已记录过 timeout_refund 流水则跳过 + var refundCount int64 + s.readDB.UserCouponLedger.WithContext(ctx).UnderlyingDB().Raw(` + SELECT COUNT(*) FROM user_coupon_ledger + WHERE user_coupon_id = ? AND order_id = ? AND action = 'timeout_refund' + `, couponID, orderID).Scan(&refundCount) - if cr.AppliedAmount > 0 { - // 统一回退逻辑:无论券种,统统将预扣金额加回余额,并重置状态为 1 (未使用/有余额) - res := s.writeDB.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET balance_amount = balance_amount + ?, - status = 1, - used_order_id = NULL, - used_at = NULL - WHERE id = ? AND status = 4 - `, cr.AppliedAmount, couponID) + if refundCount == 0 { + // 优先从 order_coupons 获取实际抵扣金额 + var appliedAmount int64 + s.readDB.OrderCoupons.WithContext(ctx).UnderlyingDB().Raw(` + SELECT applied_amount FROM order_coupons + WHERE order_id = ? AND user_coupon_id = ? + `, orderID, couponID).Scan(&appliedAmount) - if res.RowsAffected > 0 { - // 记录流水 - s.writeDB.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ - UserID: userID, - UserCouponID: couponID, - ChangeAmount: cr.AppliedAmount, - BalanceAfter: 0, // 异步流水无法实时算最新,标记 0 或查询后填入,这里暂保持 Action - OrderID: orderID, - Action: "timeout_refund", - CreatedAt: time.Now(), - }) + // 兜底:order_coupons 无记录时,从流水中回推预扣金额 + if appliedAmount <= 0 { + s.readDB.UserCouponLedger.WithContext(ctx).UnderlyingDB().Raw(` + SELECT COALESCE(SUM(CASE WHEN change_amount < 0 THEN -change_amount ELSE 0 END), 0) + FROM user_coupon_ledger + WHERE user_id = ? AND user_coupon_id = ? AND order_id = ? AND action IN ('reserve', 'usage') + `, userID, couponID, orderID).Scan(&appliedAmount) + } + + if appliedAmount > 0 { + // 恢复余额 + 重置状态(不依赖 status 条件,兼容金额券 status=1/2 和冻结券 status=4) + res := s.writeDB.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` + UPDATE user_coupons + SET balance_amount = balance_amount + ?, + status = 1, + used_order_id = NULL, + used_at = NULL + WHERE id = ? + `, appliedAmount, couponID) + + if res.RowsAffected > 0 { + // 记录流水 + s.writeDB.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ + UserID: userID, + UserCouponID: couponID, + ChangeAmount: appliedAmount, + OrderID: orderID, + Action: "timeout_refund", + CreatedAt: time.Now(), + }) + } } } }