new-api/controller/promo_code_user.go
zizi 1de1cdb14f feat: add promo code bonus flow
Add promo code models and APIs, persist selected promo codes on top-up orders, and apply bonus quota idempotently when payments complete.
2026-05-20 12:57:54 +08:00

97 lines
2.3 KiB
Go

package controller
import (
"errors"
"strings"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type validatePromoCodeRequest struct {
Code string `json:"code"`
Amount int `json:"amount"`
}
type validatePromoCodeResponse struct {
Valid bool `json:"valid"`
Reason string `json:"reason"`
PromoCodeId int `json:"promo_code_id"`
BonusAmount int `json:"bonus_amount"`
Code string `json:"code"`
}
func ValidatePromoCode(c *gin.Context) {
var req validatePromoCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ApiError(c, err)
return
}
code := normalizeUserPromoCode(req.Code)
if code == "" {
common.ApiErrorMsg(c, "优惠码不能为空")
return
}
promoCode, err := model.GetPromoCodeByCode(code)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "not_found"))
return
}
common.ApiError(c, err)
return
}
if promoCode.Status != 1 {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "disabled"))
return
}
if promoCode.ExpiresAt != nil && promoCode.ExpiresAt.Before(time.Now()) {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "expired"))
return
}
if promoCode.MaxUses > 0 && promoCode.UsedCount >= promoCode.MaxUses {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "max_uses_reached"))
return
}
if req.Amount < promoCode.MinRechargeAmount {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "below_min_recharge"))
return
}
userId := c.GetInt("id")
if _, err := model.GetPromoCodeUsageByCodeAndUser(promoCode.Id, userId); err == nil {
common.ApiSuccess(c, newInvalidPromoCodeResponse(code, "already_used"))
return
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, validatePromoCodeResponse{
Valid: true,
Reason: "",
PromoCodeId: promoCode.Id,
BonusAmount: promoCode.BonusAmount,
Code: promoCode.Code,
})
}
func normalizeUserPromoCode(code string) string {
return strings.ToUpper(strings.TrimSpace(code))
}
func newInvalidPromoCodeResponse(code string, reason string) validatePromoCodeResponse {
return validatePromoCodeResponse{
Valid: false,
Reason: reason,
Code: code,
}
}