package wallet import ( "context" "strings" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" ) // Repository 隔离账务存储,扣费必须由 repository 保证余额和幂等。 type Repository interface { DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) GetBalances(ctx context.Context, userID int64, assetTypes []string) ([]ledger.AssetBalance, error) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) } // Service 承载钱包账务用例。 type Service struct { repository Repository } // New 创建钱包服务。 func New(repository Repository) *Service { return &Service{repository: repository} } // DebitGift 校验送礼扣费命令,并交给 repository 做原子落账和幂等。 func (s *Service) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) { if command.CommandID == "" || command.RoomID == "" || command.SenderUserID <= 0 || command.TargetUserID <= 0 || command.GiftID == "" { return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "billing command is incomplete") } if command.SenderUserID == command.TargetUserID { return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "sender and target must be different") } if command.GiftCount <= 0 { return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "gift_count must be positive") } if s.repository == nil { return ledger.Receipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } return s.repository.DebitGift(ctx, command) } // GetBalances 返回用户多资产余额;鉴权范围由 gateway 或后台入口控制。 func (s *Service) GetBalances(ctx context.Context, userID int64, assetTypes []string) ([]ledger.AssetBalance, error) { if userID <= 0 { return nil, xerr.New(xerr.InvalidArgument, "user_id is required") } if s.repository == nil { return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } normalized := make([]string, 0, len(assetTypes)) for _, assetType := range assetTypes { assetType = strings.ToUpper(strings.TrimSpace(assetType)) if assetType == "" { continue } if !ledger.ValidAssetType(assetType) { return nil, xerr.New(xerr.InvalidArgument, "asset_type is invalid") } normalized = append(normalized, assetType) } return s.repository.GetBalances(ctx, userID, normalized) } // AdminCreditAsset 执行后台手动入账;所有审计字段必须随交易一起落库。 func (s *Service) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) { if command.CommandID == "" || command.TargetUserID <= 0 || command.OperatorUserID <= 0 || command.Amount <= 0 { return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "admin credit command is incomplete") } command.AssetType = strings.ToUpper(strings.TrimSpace(command.AssetType)) if !ledger.ValidAssetType(command.AssetType) { return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "asset_type is invalid") } if command.Reason == "" || command.EvidenceRef == "" { return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "reason and evidence_ref are required") } if s.repository == nil { return ledger.AssetBalance{}, "", xerr.New(xerr.Unavailable, "wallet repository is not configured") } return s.repository.AdminCreditAsset(ctx, command) }