限制套餐具体到 档位
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m45s
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m45s
This commit is contained in:
parent
69028898a4
commit
79a97ec569
@ -1,4 +1,4 @@
|
|||||||
DELETE
|
select
|
||||||
FROM `system`
|
FROM `system`
|
||||||
WHERE `category` = 'invite'
|
WHERE `category` = 'invite'
|
||||||
AND `key` = 'GiftDays';
|
AND `key` = 'GiftDays';
|
||||||
|
|||||||
@ -13,3 +13,19 @@ func getDiscount(discounts []types.SubscribeDiscount, inputMonths int64) float64
|
|||||||
|
|
||||||
return finalDiscount / float64(100)
|
return finalDiscount / float64(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isNewUserOnlyForQuantity checks whether the matched discount tier has new_user_only enabled.
|
||||||
|
func isNewUserOnlyForQuantity(discounts []types.SubscribeDiscount, inputQuantity int64) bool {
|
||||||
|
var finalDiscount float64 = 100
|
||||||
|
var newUserOnly bool
|
||||||
|
|
||||||
|
for _, discount := range discounts {
|
||||||
|
if inputQuantity >= discount.Quantity && discount.Discount < finalDiscount {
|
||||||
|
finalDiscount = discount.Discount
|
||||||
|
newUserOnly = discount.NewUserOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUserOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,8 +109,14 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check new user only restriction
|
// check new user only restriction (tier-level only)
|
||||||
if !isSingleModeRenewal && sub.NewUserOnly != nil && *sub.NewUserOnly {
|
var newUserOnly bool
|
||||||
|
if !isSingleModeRenewal && sub.Discount != "" {
|
||||||
|
var dis []types.SubscribeDiscount
|
||||||
|
_ = json.Unmarshal([]byte(sub.Discount), &dis)
|
||||||
|
newUserOnly = isNewUserOnlyForQuantity(dis, req.Quantity)
|
||||||
|
}
|
||||||
|
if newUserOnly {
|
||||||
if time.Since(u.CreatedAt) > 24*time.Hour {
|
if time.Since(u.CreatedAt) > 24*time.Hour {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "not a new user")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "not a new user")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -270,20 +270,28 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check new user only restriction inside transaction to prevent race condition
|
// check new user only restriction inside transaction to prevent race condition (tier-level only)
|
||||||
if orderInfo.Type == 1 && sub.NewUserOnly != nil && *sub.NewUserOnly {
|
if orderInfo.Type == 1 {
|
||||||
if time.Since(u.CreatedAt) > 24*time.Hour {
|
var txNewUserOnly bool
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "not a new user")
|
if sub.Discount != "" {
|
||||||
|
var dis []types.SubscribeDiscount
|
||||||
|
_ = json.Unmarshal([]byte(sub.Discount), &dis)
|
||||||
|
txNewUserOnly = isNewUserOnlyForQuantity(dis, orderInfo.Quantity)
|
||||||
}
|
}
|
||||||
var historyCount int64
|
if txNewUserOnly {
|
||||||
if e := db.Model(&order.Order{}).
|
if time.Since(u.CreatedAt) > 24*time.Hour {
|
||||||
Where("user_id = ? AND subscribe_id = ? AND type = 1 AND status IN ?",
|
return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "not a new user")
|
||||||
u.Id, targetSubscribeID, []int{2, 5}).
|
}
|
||||||
Count(&historyCount).Error; e != nil {
|
var historyCount int64
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "check new user purchase history error: %v", e.Error())
|
if e := db.Model(&order.Order{}).
|
||||||
}
|
Where("user_id = ? AND subscribe_id = ? AND type = 1 AND status IN ?",
|
||||||
if historyCount >= 1 {
|
u.Id, targetSubscribeID, []int{2, 5}).
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "already purchased new user plan")
|
Count(&historyCount).Error; e != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "check new user purchase history error: %v", e.Error())
|
||||||
|
}
|
||||||
|
if historyCount >= 1 {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "already purchased new user plan")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,8 +73,9 @@ func (s *Subscribe) BeforeUpdate(tx *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Discount struct {
|
type Discount struct {
|
||||||
Months int64 `json:"months"`
|
Months int64 `json:"months"`
|
||||||
Discount int64 `json:"discount"`
|
Discount int64 `json:"discount"`
|
||||||
|
NewUserOnly bool `json:"new_user_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
|
|||||||
@ -2455,8 +2455,9 @@ type SubscribeConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SubscribeDiscount struct {
|
type SubscribeDiscount struct {
|
||||||
Quantity int64 `json:"quantity"`
|
Quantity int64 `json:"quantity"`
|
||||||
Discount float64 `json:"discount"`
|
Discount float64 `json:"discount"`
|
||||||
|
NewUserOnly bool `json:"new_user_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubscribeGroup struct {
|
type SubscribeGroup struct {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"github.com/perfect-panel/server/internal/logic/telegram"
|
"github.com/perfect-panel/server/internal/logic/telegram"
|
||||||
"github.com/perfect-panel/server/internal/model/order"
|
"github.com/perfect-panel/server/internal/model/order"
|
||||||
|
internaltypes "github.com/perfect-panel/server/internal/types"
|
||||||
"github.com/perfect-panel/server/internal/model/redemption"
|
"github.com/perfect-panel/server/internal/model/redemption"
|
||||||
"github.com/perfect-panel/server/internal/model/subscribe"
|
"github.com/perfect-panel/server/internal/model/subscribe"
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
@ -223,6 +224,29 @@ func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.O
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check new user only restriction at activation to prevent concurrent bypass
|
||||||
|
if orderInfo.Type == OrderTypeSubscribe && sub.Discount != "" {
|
||||||
|
var dis []internaltypes.SubscribeDiscount
|
||||||
|
if jsonErr := json.Unmarshal([]byte(sub.Discount), &dis); jsonErr == nil {
|
||||||
|
newUserOnly := isNewUserOnlyForQuantity(dis, orderInfo.Quantity)
|
||||||
|
if newUserOnly {
|
||||||
|
if time.Since(userInfo.CreatedAt) > 24*time.Hour {
|
||||||
|
return fmt.Errorf("new user only: user %d is not a new user", userInfo.Id)
|
||||||
|
}
|
||||||
|
var historyCount int64
|
||||||
|
if e := l.svc.DB.Model(&order.Order{}).
|
||||||
|
Where("user_id = ? AND subscribe_id = ? AND type = 1 AND status = ? AND order_no != ?",
|
||||||
|
orderInfo.UserId, orderInfo.SubscribeId, OrderStatusFinished, orderInfo.OrderNo).
|
||||||
|
Count(&historyCount).Error; e != nil {
|
||||||
|
return fmt.Errorf("new user only: check history error: %w", e)
|
||||||
|
}
|
||||||
|
if historyCount >= 1 {
|
||||||
|
return fmt.Errorf("new user only: user %d already activated subscribe %d", userInfo.Id, orderInfo.SubscribeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var userSub *user.Subscribe
|
var userSub *user.Subscribe
|
||||||
|
|
||||||
// 单订阅模式下,优先兜底为“续费语义”:延长已购订阅,避免并发下重复创建 user_subscribe
|
// 单订阅模式下,优先兜底为“续费语义”:延长已购订阅,避免并发下重复创建 user_subscribe
|
||||||
@ -615,7 +639,7 @@ func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *use
|
|||||||
|
|
||||||
func (l *ActivateOrderLogic) grantGiftDaysToBothParties(ctx context.Context, referee *user.User, orderNo string) {
|
func (l *ActivateOrderLogic) grantGiftDaysToBothParties(ctx context.Context, referee *user.User, orderNo string) {
|
||||||
giftDays := l.svc.Config.Invite.GiftDays
|
giftDays := l.svc.Config.Invite.GiftDays
|
||||||
if giftDays <= 0 || referee == nil || referee.Id == 0 {
|
if giftDays <= 0 || referee == nil || referee.Id == 0 || referee.RefererId == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = l.grantGiftDays(ctx, referee, int(giftDays), orderNo, "邀请赠送")
|
_ = l.grantGiftDays(ctx, referee, int(giftDays), orderNo, "邀请赠送")
|
||||||
@ -1312,3 +1336,18 @@ func (l *ActivateOrderLogic) RedemptionActivate(ctx context.Context, orderInfo *
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isNewUserOnlyForQuantity checks whether the matched discount tier has new_user_only enabled.
|
||||||
|
func isNewUserOnlyForQuantity(discounts []internaltypes.SubscribeDiscount, inputQuantity int64) bool {
|
||||||
|
var finalDiscount float64 = 100
|
||||||
|
var newUserOnly bool
|
||||||
|
|
||||||
|
for _, d := range discounts {
|
||||||
|
if inputQuantity >= d.Quantity && d.Discount < finalDiscount {
|
||||||
|
finalDiscount = d.Discount
|
||||||
|
newUserOnly = d.NewUserOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUserOnly
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user