117 lines
5.1 KiB
Go
117 lines
5.1 KiB
Go
package wallet
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
"hyapp/services/wallet-service/internal/domain/ledger"
|
||
)
|
||
|
||
const vipDailyCoinRebateTaskDayLayout = "2006-01-02"
|
||
|
||
// ProcessVipDailyCoinRebateBatch 只接受 UTC 日期,不接受时区偏移。空 task_day 使用 wallet owner
|
||
// 当前 UTC 日期,使调度方无需也不能自行决定业务切日。
|
||
func (s *Service) ProcessVipDailyCoinRebateBatch(ctx context.Context, command ledger.ProcessVipDailyCoinRebateBatchCommand) (ledger.ProcessVipDailyCoinRebateBatchResult, error) {
|
||
if s.repository == nil {
|
||
return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
||
}
|
||
command.AppCode = appcode.Normalize(command.AppCode)
|
||
now := s.now().UTC()
|
||
command.NowMS = now.UnixMilli()
|
||
command.TaskDay = strings.TrimSpace(command.TaskDay)
|
||
if command.TaskDay == "" {
|
||
command.TaskDay = now.Format(vipDailyCoinRebateTaskDayLayout)
|
||
}
|
||
dayStart, err := time.ParseInLocation(vipDailyCoinRebateTaskDayLayout, command.TaskDay, time.UTC)
|
||
if err != nil || dayStart.Format(vipDailyCoinRebateTaskDayLayout) != command.TaskDay {
|
||
return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.InvalidArgument, "task_day must be an UTC date in YYYY-MM-DD")
|
||
}
|
||
if dayStart.After(now) {
|
||
return ledger.ProcessVipDailyCoinRebateBatchResult{}, xerr.New(xerr.InvalidArgument, "future vip rebate task_day is not allowed")
|
||
}
|
||
if command.BatchSize <= 0 {
|
||
command.BatchSize = 100
|
||
}
|
||
if command.BatchSize > 500 {
|
||
command.BatchSize = 500
|
||
}
|
||
ctx = appcode.WithContext(ctx, command.AppCode)
|
||
return s.repository.ProcessVipDailyCoinRebateBatch(ctx, command)
|
||
}
|
||
|
||
// GetMyCurrentVipDailyCoinRebate 查询当前 UTC 日返现;没有记录是正常状态,以 found=false 表达。
|
||
func (s *Service) GetMyCurrentVipDailyCoinRebate(ctx context.Context, userID int64) (ledger.VipDailyCoinRebate, bool, error) {
|
||
if userID <= 0 {
|
||
return ledger.VipDailyCoinRebate{}, false, xerr.New(xerr.InvalidArgument, "user_id is required")
|
||
}
|
||
if s.repository == nil {
|
||
return ledger.VipDailyCoinRebate{}, false, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
||
}
|
||
nowMS := s.now().UTC().UnixMilli()
|
||
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
|
||
return s.repository.GetMyCurrentVipDailyCoinRebate(ctx, userID, nowMS)
|
||
}
|
||
|
||
// ListMyVipDailyCoinRebateStatuses 返回用户历史记录;expired 由 wallet owner 使用当前 UTC 时钟投影。
|
||
func (s *Service) ListMyVipDailyCoinRebateStatuses(ctx context.Context, query ledger.ListVipDailyCoinRebateStatusesQuery) ([]ledger.VipDailyCoinRebate, int64, error) {
|
||
if query.UserID <= 0 {
|
||
return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
|
||
}
|
||
if s.repository == nil {
|
||
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
||
}
|
||
query.AppCode = appcode.Normalize(query.AppCode)
|
||
if query.Page <= 0 {
|
||
query.Page = 1
|
||
}
|
||
if query.PageSize <= 0 {
|
||
query.PageSize = 20
|
||
}
|
||
if query.PageSize > 100 {
|
||
query.PageSize = 100
|
||
}
|
||
if len(query.RebateIDs) > 100 {
|
||
return nil, 0, xerr.New(xerr.InvalidArgument, "rebate_ids cannot contain more than 100 items")
|
||
}
|
||
seenRebateIDs := make(map[string]struct{}, len(query.RebateIDs))
|
||
normalizedRebateIDs := make([]string, 0, len(query.RebateIDs))
|
||
for _, rebateID := range query.RebateIDs {
|
||
rebateID = strings.TrimSpace(rebateID)
|
||
if rebateID == "" || len(rebateID) > 96 {
|
||
return nil, 0, xerr.New(xerr.InvalidArgument, "rebate_ids contains an invalid id")
|
||
}
|
||
if _, exists := seenRebateIDs[rebateID]; exists {
|
||
continue
|
||
}
|
||
seenRebateIDs[rebateID] = struct{}{}
|
||
normalizedRebateIDs = append(normalizedRebateIDs, rebateID)
|
||
}
|
||
query.RebateIDs = normalizedRebateIDs
|
||
query.NowMS = s.now().UTC().UnixMilli()
|
||
ctx = appcode.WithContext(ctx, query.AppCode)
|
||
return s.repository.ListMyVipDailyCoinRebateStatuses(ctx, query)
|
||
}
|
||
|
||
// ClaimVipDailyCoinRebate 只校验命令形状;可领取窗口、金额、付费 VIP 快照和余额变更均在
|
||
// repository 的同一行锁事务内裁决,客户端无法提交金额或延长过期时间。
|
||
func (s *Service) ClaimVipDailyCoinRebate(ctx context.Context, command ledger.ClaimVipDailyCoinRebateCommand) (ledger.ClaimVipDailyCoinRebateReceipt, error) {
|
||
command.AppCode = appcode.Normalize(command.AppCode)
|
||
command.CommandID = strings.TrimSpace(command.CommandID)
|
||
command.RebateID = strings.TrimSpace(command.RebateID)
|
||
if command.CommandID == "" || command.UserID <= 0 || command.RebateID == "" {
|
||
return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.InvalidArgument, "vip daily coin rebate claim command is incomplete")
|
||
}
|
||
if len(command.CommandID) > 128 || len(command.RebateID) > 96 {
|
||
return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.InvalidArgument, "vip daily coin rebate claim command is invalid")
|
||
}
|
||
if s.repository == nil {
|
||
return ledger.ClaimVipDailyCoinRebateReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
||
}
|
||
command.NowMS = s.now().UTC().UnixMilli()
|
||
ctx = appcode.WithContext(ctx, command.AppCode)
|
||
return s.repository.ClaimVipDailyCoinRebate(ctx, command)
|
||
}
|