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 (预扣中) 更新为最终状态 // 统一逻辑:只要还有余额,状态回转到 1 (未使用/有余额);否则设为 2 (已用完) finalStatus := int32(1) if r.BalanceAmount <= 0 { finalStatus = 2 } res := db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` UPDATE user_coupons SET status = ? WHERE id = ? AND status = 4 `, finalStatus, 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: r.BalanceAmount, OrderID: orderID, Action: "confirm", CreatedAt: time.Now(), } _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) } return nil }