mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-01 15:02:58 +08:00
修复邀请码普通兑换错误
This commit is contained in:
@@ -374,6 +374,13 @@ func (s *RedeemService) releaseRedeemLock(ctx context.Context, code string) {
|
||||
_ = s.cache.ReleaseRedeemLock(ctx, code)
|
||||
}
|
||||
|
||||
func unsupportedRedeemTypeError(codeType string) error {
|
||||
if codeType == RedeemTypeInvitation {
|
||||
return infraerrors.BadRequest("REDEEM_CODE_UNSUPPORTED_TYPE", "invitation codes can only be used during registration")
|
||||
}
|
||||
return infraerrors.BadRequest("REDEEM_CODE_UNSUPPORTED_TYPE", fmt.Sprintf("unsupported redeem type: %s", codeType))
|
||||
}
|
||||
|
||||
// Redeem 使用兑换码
|
||||
func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) (*RedeemCode, error) {
|
||||
// 检查限流
|
||||
@@ -407,9 +414,15 @@ func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) (
|
||||
return nil, ErrRedeemCodeUsed
|
||||
}
|
||||
|
||||
// 验证兑换码类型的前置条件
|
||||
if redeemCode.Type == RedeemTypeSubscription && redeemCode.GroupID == nil {
|
||||
return nil, infraerrors.BadRequest("REDEEM_CODE_INVALID", "invalid subscription redeem code: missing group_id")
|
||||
// 验证兑换码类型的前置条件。邀请码属于注册流程,不能通过普通兑换接口使用。
|
||||
switch redeemCode.Type {
|
||||
case RedeemTypeBalance, RedeemTypeConcurrency:
|
||||
case RedeemTypeSubscription:
|
||||
if redeemCode.GroupID == nil {
|
||||
return nil, infraerrors.BadRequest("REDEEM_CODE_INVALID", "invalid subscription redeem code: missing group_id")
|
||||
}
|
||||
default:
|
||||
return nil, unsupportedRedeemTypeError(redeemCode.Type)
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
@@ -483,7 +496,7 @@ func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) (
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported redeem type: %s", redeemCode.Type)
|
||||
return nil, unsupportedRedeemTypeError(redeemCode.Type)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type redeemRejectRepo struct {
|
||||
code RedeemCode
|
||||
useCalled bool
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) Create(ctx context.Context, code *RedeemCode) error {
|
||||
panic("unexpected Create call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) CreateBatch(ctx context.Context, codes []RedeemCode) error {
|
||||
panic("unexpected CreateBatch call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) GetByID(ctx context.Context, id int64) (*RedeemCode, error) {
|
||||
if r.code.ID != id {
|
||||
return nil, ErrRedeemCodeNotFound
|
||||
}
|
||||
clone := r.code
|
||||
return &clone, nil
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) GetByCode(ctx context.Context, code string) (*RedeemCode, error) {
|
||||
if r.code.Code != code {
|
||||
return nil, ErrRedeemCodeNotFound
|
||||
}
|
||||
clone := r.code
|
||||
return &clone, nil
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) Update(ctx context.Context, code *RedeemCode) error {
|
||||
panic("unexpected Update call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) BatchUpdate(ctx context.Context, ids []int64, fields RedeemCodeBatchUpdateFields) (int64, error) {
|
||||
panic("unexpected BatchUpdate call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) Delete(ctx context.Context, id int64) error {
|
||||
panic("unexpected Delete call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) Use(ctx context.Context, id, userID int64) error {
|
||||
r.useCalled = true
|
||||
r.code.Status = StatusUsed
|
||||
r.code.UsedBy = &userID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) List(ctx context.Context, params pagination.PaginationParams) ([]RedeemCode, *pagination.PaginationResult, error) {
|
||||
panic("unexpected List call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) ListWithFilters(ctx context.Context, params pagination.PaginationParams, codeType, status, search string) ([]RedeemCode, *pagination.PaginationResult, error) {
|
||||
panic("unexpected ListWithFilters call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) ListByUser(ctx context.Context, userID int64, limit int) ([]RedeemCode, error) {
|
||||
panic("unexpected ListByUser call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) ListByUserPaginated(ctx context.Context, userID int64, params pagination.PaginationParams, codeType string) ([]RedeemCode, *pagination.PaginationResult, error) {
|
||||
panic("unexpected ListByUserPaginated call")
|
||||
}
|
||||
|
||||
func (r *redeemRejectRepo) SumPositiveBalanceByUser(ctx context.Context, userID int64) (float64, error) {
|
||||
panic("unexpected SumPositiveBalanceByUser call")
|
||||
}
|
||||
|
||||
func TestRedeemRejectsInvitationCodeBeforeTransaction(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
redeemRepo := &redeemRejectRepo{
|
||||
code: RedeemCode{
|
||||
ID: 1,
|
||||
Code: "INVITE-001",
|
||||
Type: RedeemTypeInvitation,
|
||||
Status: StatusUnused,
|
||||
},
|
||||
}
|
||||
redeemService := NewRedeemService(redeemRepo, nil, nil, nil, nil, nil, nil, nil)
|
||||
|
||||
got, err := redeemService.Redeem(ctx, 2, redeemRepo.code.Code)
|
||||
|
||||
require.Nil(t, got)
|
||||
require.Error(t, err)
|
||||
require.True(t, infraerrors.IsBadRequest(err))
|
||||
require.Equal(t, "REDEEM_CODE_UNSUPPORTED_TYPE", infraerrors.Reason(err))
|
||||
require.Equal(t, "invitation codes can only be used during registration", infraerrors.Message(err))
|
||||
require.False(t, redeemRepo.useCalled)
|
||||
require.Equal(t, StatusUnused, redeemRepo.code.Status)
|
||||
require.Nil(t, redeemRepo.code.UsedBy)
|
||||
}
|
||||
Reference in New Issue
Block a user