153 lines
8.2 KiB
Go
153 lines
8.2 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
resourcedomain "hyapp/services/wallet-service/internal/domain/resource"
|
|
)
|
|
|
|
// 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)
|
|
TransferCoinFromSeller(ctx context.Context, command ledger.CoinSellerTransferCommand) (ledger.CoinSellerTransferReceipt, error)
|
|
ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error)
|
|
ListResources(ctx context.Context, query resourcedomain.ListResourcesQuery) ([]resourcedomain.Resource, int64, error)
|
|
GetResource(ctx context.Context, resourceID int64) (resourcedomain.Resource, error)
|
|
CreateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error)
|
|
UpdateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error)
|
|
SetResourceStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error)
|
|
ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error)
|
|
GetResourceGroup(ctx context.Context, groupID int64) (resourcedomain.ResourceGroup, error)
|
|
CreateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error)
|
|
UpdateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error)
|
|
SetResourceGroupStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.ResourceGroup, error)
|
|
ListGiftConfigs(ctx context.Context, query resourcedomain.ListGiftConfigsQuery) ([]resourcedomain.GiftConfig, int64, error)
|
|
CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error)
|
|
UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error)
|
|
SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error)
|
|
GrantResource(ctx context.Context, command resourcedomain.GrantResourceCommand) (resourcedomain.ResourceGrant, error)
|
|
GrantResourceGroup(ctx context.Context, command resourcedomain.GrantResourceGroupCommand) (resourcedomain.ResourceGrant, error)
|
|
ListUserResources(ctx context.Context, query resourcedomain.ListUserResourcesQuery) ([]resourcedomain.UserResourceEntitlement, error)
|
|
EquipUserResource(ctx context.Context, command resourcedomain.EquipUserResourceCommand) (resourcedomain.UserResourceEntitlement, error)
|
|
ListResourceGrants(ctx context.Context, query resourcedomain.ListResourceGrantsQuery) ([]resourcedomain.ResourceGrant, int64, 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")
|
|
}
|
|
command.AppCode = appcode.Normalize(command.AppCode)
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
|
|
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")
|
|
}
|
|
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
|
|
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")
|
|
}
|
|
command.AppCode = appcode.Normalize(command.AppCode)
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
|
|
return s.repository.AdminCreditAsset(ctx, command)
|
|
}
|
|
|
|
// TransferCoinFromSeller 执行币商向玩家转金币;身份和区域都由 gateway/user-service 校验并传入。
|
|
func (s *Service) TransferCoinFromSeller(ctx context.Context, command ledger.CoinSellerTransferCommand) (ledger.CoinSellerTransferReceipt, error) {
|
|
if command.CommandID == "" || command.SellerUserID <= 0 || command.TargetUserID <= 0 || command.Amount <= 0 {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller transfer command is incomplete")
|
|
}
|
|
if command.SellerUserID == command.TargetUserID {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "seller and target must be different")
|
|
}
|
|
if command.SellerRegionID <= 0 || command.TargetRegionID <= 0 {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "seller and target region are required")
|
|
}
|
|
if command.SellerRegionID != command.TargetRegionID {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Conflict, "seller and target region must match")
|
|
}
|
|
if strings.TrimSpace(command.Reason) == "" {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required")
|
|
}
|
|
if s.repository == nil {
|
|
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
command.AppCode = appcode.Normalize(command.AppCode)
|
|
command.Reason = strings.TrimSpace(command.Reason)
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
|
|
return s.repository.TransferCoinFromSeller(ctx, command)
|
|
}
|
|
|
|
// ListRechargeBills 返回充值账单只读列表;所有充值来源必须先在 wallet-service 落充值记录。
|
|
func (s *Service) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) {
|
|
if s.repository == nil {
|
|
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
query.AppCode = appcode.Normalize(query.AppCode)
|
|
ctx = appcode.WithContext(ctx, query.AppCode)
|
|
|
|
return s.repository.ListRechargeBills(ctx, query)
|
|
}
|