2026-05-29 17:36:33 +08:00

175 lines
7.5 KiB
Go

package wallet
import (
"context"
"sort"
"strings"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
)
// GetRedPacketConfig 返回红包配置;未配置时 repository 会返回关闭状态。
func (s *Service) GetRedPacketConfig(ctx context.Context, appCode string) (ledger.RedPacketConfig, error) {
if s.repository == nil {
return ledger.RedPacketConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
return s.repository.GetRedPacketConfig(ctx, appCode)
}
// UpdateRedPacketConfig 保存后台红包配置;时间和档位必须在 wallet-service 内强校验。
func (s *Service) UpdateRedPacketConfig(ctx context.Context, config ledger.RedPacketConfig) (ledger.RedPacketConfig, error) {
if s.repository == nil {
return ledger.RedPacketConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
config.AppCode = appcode.Normalize(config.AppCode)
config.CountTiers = normalizeCountTiers(config.CountTiers)
config.AmountTiers = normalizeAmountTiers(config.AmountTiers)
config.RuleURL = strings.TrimSpace(config.RuleURL)
config.ExpireSeconds = ledger.RedPacketExpireSeconds
if config.UpdatedByAdminID <= 0 {
return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required")
}
if len(config.CountTiers) == 0 || len(config.AmountTiers) == 0 {
return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet tiers are required")
}
if config.DelayedOpenSeconds < 0 || config.DailySendLimit < 0 {
return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet config is invalid")
}
if config.DelayedOpenSeconds >= ledger.RedPacketExpireSeconds {
return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet expire_seconds must be greater than delayed_open_seconds")
}
if len(config.CountTiers) > 0 && len(config.AmountTiers) > 0 && config.AmountTiers[0] < int64(config.CountTiers[len(config.CountTiers)-1]) {
return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet amount tiers must cover count tiers")
}
ctx = appcode.WithContext(ctx, config.AppCode)
return s.repository.UpdateRedPacketConfig(ctx, config)
}
// CreateRedPacket 发普通或延迟红包;扣款、随机拆分和 outbox 都由 repository 事务完成。
func (s *Service) CreateRedPacket(ctx context.Context, command ledger.RedPacketCreateCommand) (ledger.RedPacketCreateReceipt, error) {
if s.repository == nil {
return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.CommandID = strings.TrimSpace(command.CommandID)
command.RoomID = strings.TrimSpace(command.RoomID)
command.PacketType = ledger.NormalizeRedPacketType(command.PacketType)
if command.CommandID == "" || command.SenderUserID <= 0 || command.RoomID == "" || command.RegionID <= 0 {
return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.InvalidArgument, "red packet command is incomplete")
}
if command.PacketType == "" {
return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.InvalidArgument, "red packet type is invalid")
}
if command.TotalAmount <= 0 || command.PacketCount <= 0 {
return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.InvalidArgument, "red packet amount and count are required")
}
if command.TotalAmount < int64(command.PacketCount) {
return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.RedPacketAmountTooSmall, "red packet amount is too small")
}
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreateRedPacket(ctx, command)
}
// ClaimRedPacket 抢一个红包份额;同一个用户同一红包只允许成功一次。
func (s *Service) ClaimRedPacket(ctx context.Context, command ledger.RedPacketClaimCommand) (ledger.RedPacketClaimReceipt, error) {
if s.repository == nil {
return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.CommandID = strings.TrimSpace(command.CommandID)
command.PacketID = strings.TrimSpace(command.PacketID)
if command.CommandID == "" || command.PacketID == "" || command.UserID <= 0 {
return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.InvalidArgument, "red packet claim command is incomplete")
}
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.ClaimRedPacket(ctx, command)
}
func (s *Service) ListRedPackets(ctx context.Context, query ledger.RedPacketQuery) ([]ledger.RedPacket, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
query.RoomID = strings.TrimSpace(query.RoomID)
query.PacketType = ledger.NormalizeRedPacketType(query.PacketType)
query.Status = ledger.NormalizeRedPacketStatus(query.Status)
if query.Page <= 0 {
query.Page = 1
}
if query.PageSize <= 0 {
query.PageSize = 20
}
if query.PageSize > 100 {
query.PageSize = 100
}
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListRedPackets(ctx, query)
}
func (s *Service) GetRedPacket(ctx context.Context, appCode string, packetID string, viewerUserID int64, includeClaims bool) (ledger.RedPacket, error) {
if s.repository == nil {
return ledger.RedPacket{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
packetID = strings.TrimSpace(packetID)
if packetID == "" {
return ledger.RedPacket{}, xerr.New(xerr.InvalidArgument, "packet_id is required")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
return s.repository.GetRedPacket(ctx, appCode, packetID, viewerUserID, includeClaims)
}
func (s *Service) ExpireRedPackets(ctx context.Context, appCode string, batchSize int32) (ledger.RedPacketExpireResult, error) {
if s.repository == nil {
return ledger.RedPacketExpireResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
return s.repository.ExpireRedPackets(ctx, appCode, batchSize)
}
func (s *Service) RetryRedPacketRefund(ctx context.Context, appCode string, packetID string, operatorUserID int64) (ledger.RedPacketRefundReceipt, error) {
if s.repository == nil {
return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
packetID = strings.TrimSpace(packetID)
if packetID == "" || operatorUserID <= 0 {
return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.InvalidArgument, "red packet refund retry command is incomplete")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
return s.repository.RetryRedPacketRefund(ctx, appCode, packetID, operatorUserID)
}
func normalizeCountTiers(values []int32) []int32 {
seen := make(map[int32]bool, len(values))
out := make([]int32, 0, len(values))
for _, value := range values {
if value <= 0 || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
}
func normalizeAmountTiers(values []int64) []int64 {
seen := make(map[int64]bool, len(values))
out := make([]int64, 0, len(values))
for _, value := range values {
if value <= 0 || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
}