diff --git a/internal/logic/public/order/preCreateOrderLogic.go b/internal/logic/public/order/preCreateOrderLogic.go index 4e16715..7c86330 100644 --- a/internal/logic/public/order/preCreateOrderLogic.go +++ b/internal/logic/public/order/preCreateOrderLogic.go @@ -55,6 +55,25 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r l.Errorw("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", req.SubscribeId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) } + + // check subscribe plan quota limit + if sub.Quota > 0 { + userSub, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id) + if err != nil { + l.Errorw("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscription error: %v", err.Error()) + } + var count int64 + for _, v := range userSub { + if v.SubscribeId == req.SubscribeId { + count++ + } + } + if count >= sub.Quota { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeQuotaLimit), "quota limit") + } + } + var discount float64 = 1 if sub.Discount != "" { var dis []types.SubscribeDiscount diff --git a/internal/logic/public/order/purchaseLogic.go b/internal/logic/public/order/purchaseLogic.go index cbc960f..06f611c 100644 --- a/internal/logic/public/order/purchaseLogic.go +++ b/internal/logic/public/order/purchaseLogic.go @@ -87,19 +87,6 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeOutOfStock), "subscribe out of stock") } - // check subscribe plan limit - if sub.Quota > 0 { - var count int64 - for _, v := range userSub { - if v.SubscribeId == req.SubscribeId { - count += 1 - } - } - if count >= sub.Quota { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeQuotaLimit), "quota limit") - } - } - var discount float64 = 1 if sub.Discount != "" { var dis []types.SubscribeDiscount @@ -195,6 +182,24 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P } // Database transaction err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error { + // check subscribe plan quota limit inside transaction to prevent race condition + if sub.Quota > 0 { + var currentUserSub []user.Subscribe + if e := db.Model(&user.Subscribe{}).Where("user_id = ?", u.Id).Find(¤tUserSub).Error; e != nil { + l.Errorw("[Purchase] Database query error", logger.Field("error", e.Error()), logger.Field("user_id", u.Id)) + return e + } + var count int64 + for _, v := range currentUserSub { + if v.SubscribeId == req.SubscribeId { + count++ + } + } + if count >= sub.Quota { + return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeQuotaLimit), "quota limit") + } + } + // update user deduction && Pre deduction ,Return after canceling the order if orderInfo.GiftAmount > 0 { // update user deduction && Pre deduction ,Return after canceling the order diff --git a/internal/logic/public/redemption/redeemCodeLogic.go b/internal/logic/public/redemption/redeemCodeLogic.go index a00bcd9..83bed30 100644 --- a/internal/logic/public/redemption/redeemCodeLogic.go +++ b/internal/logic/public/redemption/redeemCodeLogic.go @@ -160,6 +160,24 @@ func (l *RedeemCodeLogic) RedeemCode(req *types.RedeemCodeRequest) (resp *types. return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update subscribe error: %v", err.Error()) } } else { + // Check quota limit before creating new subscribe + if subscribePlan.Quota > 0 { + var count int64 + if err := tx.Model(&user.Subscribe{}).Where("user_id = ? AND subscribe_id = ?", u.Id, redemptionCode.SubscribePlan).Count(&count).Error; err != nil { + l.Errorw("[RedeemCode] Count user subscribe failed", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count user subscribe error: %v", err.Error()) + } + if count >= subscribePlan.Quota { + l.Infow("[RedeemCode] Subscribe quota limit exceeded", + logger.Field("user_id", u.Id), + logger.Field("subscribe_id", redemptionCode.SubscribePlan), + logger.Field("quota", subscribePlan.Quota), + logger.Field("current_count", count), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeQuotaLimit), "subscribe quota limit exceeded") + } + } + // Create new subscribe expireTime, traffic, err := calculateSubscribeTimeAndTraffic(redemptionCode.UnitTime, redemptionCode.Quantity, subscribePlan.Traffic) if err != nil { diff --git a/queue/logic/order/activateOrderLogic.go b/queue/logic/order/activateOrderLogic.go index 55dc284..ff4f69e 100644 --- a/queue/logic/order/activateOrderLogic.go +++ b/queue/logic/order/activateOrderLogic.go @@ -342,6 +342,24 @@ func (l *ActivateOrderLogic) createUserSubscription(ctx context.Context, orderIn Status: 1, } + // Check quota limit before creating subscription (final safeguard) + if sub.Quota > 0 { + var count int64 + if err := l.svc.DB.Model(&user.Subscribe{}).Where("user_id = ? AND subscribe_id = ?", orderInfo.UserId, orderInfo.SubscribeId).Count(&count).Error; err != nil { + logger.WithContext(ctx).Error("Count user subscribe failed", logger.Field("error", err.Error())) + return nil, err + } + if count >= sub.Quota { + logger.WithContext(ctx).Infow("Subscribe quota limit exceeded", + logger.Field("user_id", orderInfo.UserId), + logger.Field("subscribe_id", orderInfo.SubscribeId), + logger.Field("quota", sub.Quota), + logger.Field("current_count", count), + ) + return nil, fmt.Errorf("subscribe quota limit exceeded") + } + } + if err := l.svc.UserModel.InsertSubscribe(ctx, userSub); err != nil { logger.WithContext(ctx).Error("Insert user subscribe failed", logger.Field("error", err.Error())) return nil, err