2026-06-23 11:53:00 +08:00

195 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package wallet
import (
"context"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
"strings"
)
// CreditTaskReward 发放任务奖励金币;任务完成判断属于 activity-service这里只负责账本幂等入账。
func (s *Service) CreditTaskReward(ctx context.Context, command ledger.TaskRewardCommand) (ledger.TaskRewardReceipt, error) {
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.TaskID == "" || command.CycleKey == "" {
return ledger.TaskRewardReceipt{}, xerr.New(xerr.InvalidArgument, "task reward command is incomplete")
}
command.TaskType = strings.ToLower(strings.TrimSpace(command.TaskType))
if command.TaskType != "daily" && command.TaskType != "exclusive" {
return ledger.TaskRewardReceipt{}, xerr.New(xerr.InvalidArgument, "task_type is invalid")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.TaskRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.TaskRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditTaskReward(ctx, command)
}
// CreditLuckyGiftReward 发放幸运礼物中奖金币;抽奖资格和金额已由 activity-service 决策,钱包只做幂等入账。
func (s *Service) CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) {
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.DrawID == "" || command.RoomID == "" || command.GiftID == "" || command.PoolID == "" {
return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.InvalidArgument, "lucky gift reward command is incomplete")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.DrawID = strings.TrimSpace(command.DrawID)
command.RoomID = strings.TrimSpace(command.RoomID)
command.GiftID = strings.TrimSpace(command.GiftID)
command.PoolID = strings.TrimSpace(command.PoolID)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditLuckyGiftReward(ctx, command)
}
// CreditWheelReward 发放转盘金币奖品抽奖、RTP 和奖品命中归 activity-service钱包只负责独立 reason 的幂等入账。
func (s *Service) CreditWheelReward(ctx context.Context, command ledger.WheelRewardCommand) (ledger.WheelRewardReceipt, error) {
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.DrawID == "" || command.WheelID == "" || command.SelectedTierID == "" {
return ledger.WheelRewardReceipt{}, xerr.New(xerr.InvalidArgument, "wheel reward command is incomplete")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.WheelRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.WheelRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.DrawID = strings.TrimSpace(command.DrawID)
command.WheelID = strings.TrimSpace(command.WheelID)
command.SelectedTierID = strings.TrimSpace(command.SelectedTierID)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditWheelReward(ctx, command)
}
// CreditRoomTurnoverReward 发放每周房间流水奖励金币;结算归 activity-service钱包只负责幂等入账。
func (s *Service) CreditRoomTurnoverReward(ctx context.Context, command ledger.RoomTurnoverRewardCommand) (ledger.RoomTurnoverRewardReceipt, error) {
// 钱包只接受 activity-service 已经生成好的结算命令;缺 settlement、room 或正向金额时直接拒绝,避免钱包自己推断活动规则。
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.SettlementID == "" || command.RoomID == "" {
return ledger.RoomTurnoverRewardReceipt{}, xerr.New(xerr.InvalidArgument, "room turnover reward command is incomplete")
}
// 周期、流水和档位是账务元数据的一部分,必须随交易落库,后续对账才能从钱包交易反查是哪一周、哪个房间、哪个档位。
if command.PeriodStartMS <= 0 || command.PeriodEndMS <= command.PeriodStartMS || command.CoinSpent <= 0 || command.TierID <= 0 {
return ledger.RoomTurnoverRewardReceipt{}, xerr.New(xerr.InvalidArgument, "room turnover reward period or tier is invalid")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.RoomTurnoverRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.RoomTurnoverRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.SettlementID = strings.TrimSpace(command.SettlementID)
command.RoomID = strings.TrimSpace(command.RoomID)
command.TierCode = strings.TrimSpace(command.TierCode)
// AppCode 归一化后写入 context让 repository 的交易、账户和 outbox 都落到同一个 App 分区。
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditRoomTurnoverReward(ctx, command)
}
// CreditInviteActivityReward 发放邀请活动金币奖励;达标和重复领取判断属于 activity-service钱包只负责幂等入账。
func (s *Service) CreditInviteActivityReward(ctx context.Context, command ledger.InviteActivityRewardCommand) (ledger.InviteActivityRewardReceipt, error) {
// 钱包入口再次校验最小账务语义,避免上游 bug 生成空 claim 或 0 金额交易。
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.ClaimID == "" || command.TierID <= 0 || command.CycleKey == "" {
return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward command is incomplete")
}
command.RewardType = strings.ToLower(strings.TrimSpace(command.RewardType))
if command.RewardType != "recharge" && command.RewardType != "valid_invite" && command.RewardType != "invite_inviter" && command.RewardType != "invite_invitee" {
return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward_type is invalid")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.ClaimID = strings.TrimSpace(command.ClaimID)
command.TierCode = strings.TrimSpace(command.TierCode)
command.CycleKey = strings.TrimSpace(command.CycleKey)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditInviteActivityReward(ctx, command)
}
// CreditAgencyOpeningReward 发放代理开业流水档位金币奖励;命中的档位序号和金额由 activity-service 固化,钱包只做幂等入账。
func (s *Service) CreditAgencyOpeningReward(ctx context.Context, command ledger.AgencyOpeningRewardCommand) (ledger.AgencyOpeningRewardReceipt, error) {
if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.ApplicationID == "" || command.CycleID == "" || command.AgencyID <= 0 || command.RankNo <= 0 {
return ledger.AgencyOpeningRewardReceipt{}, xerr.New(xerr.InvalidArgument, "agency opening reward command is incomplete")
}
if command.ScoreCoins <= 0 {
return ledger.AgencyOpeningRewardReceipt{}, xerr.New(xerr.InvalidArgument, "agency opening score is required")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
return ledger.AgencyOpeningRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
}
if s.repository == nil {
return ledger.AgencyOpeningRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.ApplicationID = strings.TrimSpace(command.ApplicationID)
command.CycleID = strings.TrimSpace(command.CycleID)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreditAgencyOpeningReward(ctx, command)
}
// DebitCPBreakupFee 扣除解除关系费用;关系是否可解除仍由 user-service 的 pending/confirm 流程决定。
func (s *Service) DebitCPBreakupFee(ctx context.Context, command ledger.CPBreakupFeeCommand) (ledger.CPBreakupFeeReceipt, error) {
if command.CommandID == "" || command.UserID <= 0 || strings.TrimSpace(command.RelationshipID) == "" || strings.TrimSpace(command.RelationType) == "" {
return ledger.CPBreakupFeeReceipt{}, xerr.New(xerr.InvalidArgument, "cp breakup fee command is incomplete")
}
if len(command.CommandID) > 128 {
return ledger.CPBreakupFeeReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
}
if command.Amount <= 0 {
return ledger.CPBreakupFeeReceipt{}, xerr.New(xerr.InvalidArgument, "cp breakup fee amount must be positive")
}
if s.repository == nil {
return ledger.CPBreakupFeeReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.RelationshipID = strings.TrimSpace(command.RelationshipID)
command.RelationType = strings.ToLower(strings.TrimSpace(command.RelationType))
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.DebitCPBreakupFee(ctx, command)
}
// DebitWheelDraw 扣除转盘抽奖消耗金币;抽奖命中必须在 activity-service 完成wallet 只提供可幂等复用的扣费事实。
func (s *Service) DebitWheelDraw(ctx context.Context, command ledger.WheelDrawDebitCommand) (ledger.WheelDrawDebitReceipt, error) {
if command.CommandID == "" || command.UserID <= 0 || strings.TrimSpace(command.WheelID) == "" {
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.InvalidArgument, "wheel draw debit command is incomplete")
}
if len(command.CommandID) > 128 {
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
}
if command.DrawCount <= 0 {
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.InvalidArgument, "draw_count must be positive")
}
if command.Amount <= 0 {
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.InvalidArgument, "wheel draw debit amount must be positive")
}
if s.repository == nil {
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.WheelID = strings.TrimSpace(command.WheelID)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.DebitWheelDraw(ctx, command)
}