package orderLogic import ( "context" "encoding/json" "fmt" "time" commonLogic "github.com/perfect-panel/server/internal/logic/common" "github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/model/subscribe" internaltypes "github.com/perfect-panel/server/internal/types" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) func validateNewUserOnlyEligibilityAtActivation( ctx context.Context, db *gorm.DB, rdb *redis.Client, orderInfo *order.Order, sub *subscribe.Subscribe, ) error { if orderInfo == nil || sub == nil || orderInfo.Type != OrderTypeSubscribe || sub.Discount == "" { return nil } var discounts []internaltypes.SubscribeDiscount if err := json.Unmarshal([]byte(sub.Discount), &discounts); err != nil { return nil } if !isNewUserOnlyForQuantity(discounts, orderInfo.Quantity) { return nil } // Acquire a per-user distributed lock so concurrent new-user-only activations // for the same account are serialised. Without this, two workers can both read // historyCount=0 and both pass the check before either has written the order. lockKey := fmt.Sprintf("new_user_only_activate:%d", orderInfo.UserId) const lockTTL = 30 * time.Second acquired, lockErr := rdb.SetNX(ctx, lockKey, orderInfo.OrderNo, lockTTL).Result() if lockErr != nil { return fmt.Errorf("new user only: acquire lock error: %w", lockErr) } if !acquired { return fmt.Errorf("new user only: another activation is in progress for user %d", orderInfo.UserId) } defer rdb.Del(ctx, lockKey) eligibility, err := commonLogic.ResolveNewUserEligibility(ctx, db, orderInfo.UserId) if err != nil { return err } if !eligibility.IsNewUserAt(time.Now()) { return fmt.Errorf("new user only: user %d is not a new user", orderInfo.UserId) } historyCount, err := commonLogic.CountScopedSubscribePurchaseOrders( ctx, db, eligibility.ScopeUserIDs, 0, []int64{OrderStatusFinished}, orderInfo.OrderNo, ) if err != nil { return fmt.Errorf("new user only: check history error: %w", err) } if historyCount >= 1 { return fmt.Errorf("new user only: user %d already activated an order", orderInfo.UserId) } return nil }