package user import ( "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" "context" "fmt" "time" ) // RecordOrderCouponUsage 记录订单使用的用户券与本次抵扣金额 func (s *service) RecordOrderCouponUsage(ctx context.Context, orderID int64, userCouponID int64, appliedAmount int64) error { if orderID <= 0 || userCouponID <= 0 || appliedAmount <= 0 { return nil } // 根治方案:使用唯一索引保证记录不重复 return s.repo.GetDbW().Exec("INSERT IGNORE INTO order_coupons (order_id, user_coupon_id, applied_amount, created_at) VALUES (?,?,?,NOW(3))", orderID, userCouponID, appliedAmount).Error } // DeductCouponsForPaidOrder 支付成功后确认优惠券预扣 // 新逻辑:优惠券余额已在下单时预扣(status=4),支付成功后只需确认状态 // 金额券:status 4 → 2(若余额为0)或保持 4(若还有余额,等后续订单继续使用) // 满减/折扣券:status 4 → 2 func (s *service) DeductCouponsForPaidOrder(ctx context.Context, tx *dao.Query, userID int64, orderID int64, paidAt time.Time) error { type row struct { UserCouponID int64 AppliedAmount int64 Status int32 UsedOrderID int64 DiscountType int32 BalanceAmount int64 } var rows []row // 确定使用的数据库查询对象 db := s.writeDB if tx != nil { db = tx } readDb := s.readDB if tx != nil { readDb = tx } // 1. 获取该订单关联的所有优惠券使用记录(包含当前余额) _ = readDb.OrderCoupons.WithContext(ctx).UnderlyingDB().Raw(` SELECT oc.user_coupon_id AS user_coupon_id, oc.applied_amount AS applied_amount, uc.status AS status, uc.used_order_id AS used_order_id, sc.discount_type AS discount_type, uc.balance_amount AS balance_amount 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=? `, orderID).Scan(&rows).Error for _, r := range rows { // 幂等校验:检查是否已有确认流水 ledgerExists, _ := readDb.UserCouponLedger.WithContext(ctx).Where( readDb.UserCouponLedger.UserCouponID.Eq(r.UserCouponID), readDb.UserCouponLedger.OrderID.Eq(orderID), readDb.UserCouponLedger.Action.Eq("confirm"), ).Count() if ledgerExists > 0 { continue } // 2. 确认预扣:将 status=4 (预扣中) 更新为最终状态 if r.DiscountType == 1 { // 金额券 // 容错:如果券因历史 Bug 仍为 status=4,根据余额修正为正确状态 if r.Status == 4 { finalStatus := int32(1) // 还有余额 → 未使用 if r.BalanceAmount <= 0 { finalStatus = 2 // 余额已扣完 → 已使用 } db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec( "UPDATE user_coupons SET status = ? WHERE id = ? AND status = 4", finalStatus, r.UserCouponID) } // 记录确认流水 ledger := &model.UserCouponLedger{ UserID: userID, UserCouponID: r.UserCouponID, ChangeAmount: 0, // 确认操作不改变余额 BalanceAfter: r.BalanceAmount, OrderID: orderID, Action: "confirm", CreatedAt: time.Now(), } _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) } else { // 满减/折扣券:一次性核销 res := db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` UPDATE user_coupons SET status = 2 WHERE id = ? AND status = 4 `, r.UserCouponID) if res.Error != nil { return fmt.Errorf("failed to confirm coupon: %w", res.Error) } // 记录确认流水 ledger := &model.UserCouponLedger{ UserID: userID, UserCouponID: r.UserCouponID, ChangeAmount: 0, BalanceAfter: 0, OrderID: orderID, Action: "confirm", CreatedAt: time.Now(), } _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) } } return nil }