bindbox-game/internal/service/user/order_coupons.go
2026-02-18 23:23:34 +08:00

121 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}