fix: 修复过期优惠券仍可兑换/使用的漏洞
- store.go: 积分商城优惠券列表加 valid_end > now 过滤 - coupons_list.go: 修复 NULL valid_end 被错误排除,无截止日期券正确显示为有效 - activity_order_service.go: 过期/不可用券下单返回明确错误,不再静默跳过 - points_redeem_coupon_app.go: 积分兑换前校验模板 valid_end - coupon_add.go: 发券前校验模板 valid_end,过期拒绝发放
This commit is contained in:
parent
9f7a7d29fb
commit
4ffd8e8326
@ -1,6 +1,8 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"bindbox-game/internal/code"
|
||||
"bindbox-game/internal/pkg/core"
|
||||
"bindbox-game/internal/pkg/logger"
|
||||
@ -120,7 +122,8 @@ func (h *storeHandler) ListStoreItemsForApp() core.HandlerFunc {
|
||||
list[i] = listStoreItem{ID: it.ID, Kind: "item_card", Name: it.Name, Price: it.Price, PointsRequired: pts, Status: it.Status, Supported: true}
|
||||
}
|
||||
case "coupon":
|
||||
q := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.Status.Eq(1), h.readDB.SystemCoupons.ShowInMiniapp.Eq(1))
|
||||
now := time.Now()
|
||||
q := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.Status.Eq(1), h.readDB.SystemCoupons.ShowInMiniapp.Eq(1), h.readDB.SystemCoupons.ValidEnd.Gt(now))
|
||||
// 关键词筛选
|
||||
if req.Keyword != "" {
|
||||
q = q.Where(h.readDB.SystemCoupons.Name.Like("%" + req.Keyword + "%"))
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bindbox-game/internal/code"
|
||||
"bindbox-game/internal/pkg/core"
|
||||
"bindbox-game/internal/pkg/validation"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type redeemCouponRequest struct {
|
||||
@ -52,6 +54,10 @@ func (h *handler) RedeemPointsToCoupon() core.HandlerFunc {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 150002, "only amount coupons supported"))
|
||||
return
|
||||
}
|
||||
if !sc.ValidEnd.IsZero() && sc.ValidEnd.Before(time.Now()) {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 150005, "该优惠券模板已过期,无法兑换"))
|
||||
return
|
||||
}
|
||||
// sc.DiscountValue 是优惠券面值(分),直接用于扣除
|
||||
// 例如:30 元优惠券 = 3000 分
|
||||
needCents := sc.DiscountValue
|
||||
|
||||
@ -229,8 +229,7 @@ func (s *activityOrderService) applyCouponWithLock(ctx context.Context, tx *dao.
|
||||
// 如果是金额券,status=1。
|
||||
// 如果是满减券,status=1。
|
||||
if uc.Status != 1 {
|
||||
fmt.Printf("[优惠券] 状态不可用 id=%d status=%d\n", userCouponID, uc.Status)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("优惠券不可用")
|
||||
}
|
||||
|
||||
fmt.Printf("[优惠券] 用户券ID=%d 开始=%s 结束=%s 余额=%d\n", userCouponID, uc.ValidStart.Format(time.RFC3339), func() string {
|
||||
@ -243,25 +242,20 @@ func (s *activityOrderService) applyCouponWithLock(ctx context.Context, tx *dao.
|
||||
sc, _ := tx.SystemCoupons.WithContext(ctx).Where(tx.SystemCoupons.ID.Eq(uc.CouponID), tx.SystemCoupons.Status.Eq(1)).First()
|
||||
now := time.Now()
|
||||
if sc == nil {
|
||||
fmt.Printf("[优惠券] 模板不存在 用户券ID=%d\n", userCouponID)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("优惠券模板不存在")
|
||||
}
|
||||
if uc.ValidStart.After(now) {
|
||||
fmt.Printf("[优惠券] 未到开始时间 用户券ID=%d\n", userCouponID)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("优惠券未到使用时间")
|
||||
}
|
||||
if !uc.ValidEnd.IsZero() && uc.ValidEnd.Before(now) {
|
||||
fmt.Printf("[优惠券] 已过期 用户券ID=%d\n", userCouponID)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("优惠券已过期")
|
||||
}
|
||||
scopeOK := (sc.ScopeType == 1) || (sc.ScopeType == 2 && sc.ActivityID == activityID)
|
||||
if !scopeOK {
|
||||
fmt.Printf("[优惠券] 范围不匹配 用户券ID=%d scope_type=%d\n", userCouponID, sc.ScopeType)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("优惠券不适用于当前活动")
|
||||
}
|
||||
if order.TotalAmount < sc.MinSpend {
|
||||
fmt.Printf("[优惠券] 未达使用门槛 用户券ID=%d min_spend(分)=%d 订单总额(分)=%d\n", userCouponID, sc.MinSpend, order.TotalAmount)
|
||||
return 0, nil, nil
|
||||
return 0, nil, fmt.Errorf("订单金额未达优惠券使用门槛")
|
||||
}
|
||||
|
||||
// 50% 封顶
|
||||
|
||||
@ -26,6 +26,9 @@ func (s *service) AddCoupon(ctx context.Context, userID int64, couponID int64) e
|
||||
}())
|
||||
return errors.New("coupon not found or disabled")
|
||||
}
|
||||
if !tpl.ValidEnd.IsZero() && tpl.ValidEnd.Before(time.Now()) {
|
||||
return errors.New("coupon template expired")
|
||||
}
|
||||
// 配额检查:若 TotalQuantity > 0 则限制发放总量
|
||||
if tpl.TotalQuantity > 0 {
|
||||
issued, ierr := s.readDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.CouponID.Eq(couponID)).Count()
|
||||
|
||||
@ -53,12 +53,22 @@ func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32
|
||||
Where("`"+tableName+"`.user_id = ?", userID)
|
||||
|
||||
switch status {
|
||||
case 1: // 有效:余额 > 0 且 未过期
|
||||
db = db.Where(tableName+".balance_amount > ? AND "+tableName+".valid_end > ? AND "+tableName+".status IN (?, ?, ?)", 0, now, 1, 2, 4)
|
||||
case 1: // 有效:余额 > 0 且 未过期(NULL/零值 valid_end 视为永久有效)
|
||||
db = db.Where(
|
||||
tableName+".balance_amount > ? AND "+
|
||||
"("+tableName+".valid_end IS NULL OR "+tableName+".valid_end = ? OR "+tableName+".valid_end > ?) AND "+
|
||||
tableName+".status IN (?, ?, ?)",
|
||||
0, time.Time{}, now, 1, 2, 4,
|
||||
)
|
||||
case 2: // 已失效:余额用完 OR 已标记过期 OR 已过截止时间
|
||||
db = db.Where("("+tableName+".balance_amount = ?) OR "+tableName+".status = ? OR "+tableName+".valid_end <= ?", 0, 3, now)
|
||||
db = db.Where("("+tableName+".balance_amount = ?) OR "+tableName+".status = ? OR ("+tableName+".valid_end IS NOT NULL AND "+tableName+".valid_end != ? AND "+tableName+".valid_end <= ?)", 0, 3, time.Time{}, now)
|
||||
default:
|
||||
db = db.Where(tableName+".balance_amount > ? AND "+tableName+".valid_end > ? AND "+tableName+".status IN (?, ?, ?)", 0, now, 1, 2, 4)
|
||||
db = db.Where(
|
||||
tableName+".balance_amount > ? AND "+
|
||||
"("+tableName+".valid_end IS NULL OR "+tableName+".valid_end = ? OR "+tableName+".valid_end > ?) AND "+
|
||||
tableName+".status IN (?, ?, ?)",
|
||||
0, time.Time{}, now, 1, 2, 4,
|
||||
)
|
||||
}
|
||||
|
||||
if err = db.Count(&total).Error; err != nil {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user