feat(redeem): support negative values for refund/deduction

Allow redeem codes with negative values to enable refund scenarios:
- Balance: negative value deducts balance (clamped to 0, never negative)
- Concurrency: negative value reduces concurrency (clamped to 0)
- Subscription: negative validity_days reduces remaining days; if
  remaining days <= 0, the subscription is canceled (set to expired)

All deductions generate standard redeem code records for audit trail.
This commit is contained in:
erio
2026-04-03 01:50:26 +08:00
parent 055c48ab33
commit 66fde7a2e6
3 changed files with 120 additions and 52 deletions

View File

@@ -35,9 +35,9 @@ func NewRedeemHandler(adminService service.AdminService, redeemService *service.
type GenerateRedeemCodesRequest struct {
Count int `json:"count" binding:"required,min=1,max=100"`
Type string `json:"type" binding:"required,oneof=balance concurrency subscription invitation"`
Value float64 `json:"value" binding:"min=0"`
GroupID *int64 `json:"group_id"` // 订阅类型必填
ValidityDays int `json:"validity_days" binding:"omitempty,max=36500"` // 订阅类型使用,默认30天最大100年
Value float64 `json:"value"`
GroupID *int64 `json:"group_id"` // 订阅类型必填
ValidityDays int `json:"validity_days"` // 订阅类型使用,正数增加/负数退款扣减
}
// CreateAndRedeemCodeRequest represents creating a fixed code and redeeming it for a target user.
@@ -45,10 +45,10 @@ type GenerateRedeemCodesRequest struct {
type CreateAndRedeemCodeRequest struct {
Code string `json:"code" binding:"required,min=3,max=128"`
Type string `json:"type" binding:"omitempty,oneof=balance concurrency subscription invitation"` // 不传时默认 balance向后兼容
Value float64 `json:"value" binding:"required,gt=0"`
Value float64 `json:"value" binding:"required"`
UserID int64 `json:"user_id" binding:"required,gt=0"`
GroupID *int64 `json:"group_id"` // subscription 类型必填
ValidityDays int `json:"validity_days" binding:"omitempty,max=36500"` // subscription 类型必填,>0
GroupID *int64 `json:"group_id"` // subscription 类型必填
ValidityDays int `json:"validity_days"` // subscription 类型:正数增加,负数退款扣减
Notes string `json:"notes"`
}
@@ -150,8 +150,8 @@ func (h *RedeemHandler) CreateAndRedeem(c *gin.Context) {
response.BadRequest(c, "group_id is required for subscription type")
return
}
if req.ValidityDays <= 0 {
response.BadRequest(c, "validity_days must be greater than 0 for subscription type")
if req.ValidityDays == 0 {
response.BadRequest(c, "validity_days must not be zero for subscription type")
return
}
}