package grpc import ( "context" "time" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" walletservice "hyapp/services/wallet-service/internal/service/wallet" ) // Server 把 wallet-service 用例层适配为 gRPC 接口。 type Server struct { walletv1.UnimplementedWalletServiceServer svc *walletservice.Service } // NewServer 创建 wallet gRPC server。 func NewServer(svc *walletservice.Service) *Server { return &Server{svc: svc} } // DebitGift 只负责 protobuf 转换和错误映射,扣费规则在 service/repository。 func (s *Server) DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.DebitGift(ctx, ledger.DebitGiftCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), RoomID: req.GetRoomId(), SenderUserID: req.GetSenderUserId(), TargetUserID: req.GetTargetUserId(), GiftID: req.GetGiftId(), GiftCount: req.GetGiftCount(), PriceVersion: req.GetPriceVersion(), RegionID: req.GetRegionId(), TargetIsHost: req.GetTargetIsHost(), // 工资政策区域来自 user-service host profile,不能用房间 visible_region_id 兜底。 TargetHostRegionID: req.GetTargetHostRegionId(), TargetAgencyOwnerUserID: req.GetTargetAgencyOwnerUserId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.DebitGiftResponse{ BillingReceiptId: receipt.BillingReceiptID, BalanceAfter: receipt.BalanceAfter, TransactionId: receipt.TransactionID, CoinSpent: receipt.CoinSpent, ChargeAssetType: receipt.ChargeAssetType, ChargeAmount: receipt.ChargeAmount, GiftPointAdded: receipt.GiftPointAdded, HeatValue: receipt.HeatValue, GiftTypeCode: receipt.GiftTypeCode, PriceVersion: receipt.PriceVersion, HostPeriodDiamondAdded: receipt.HostPeriodDiamondAdded, HostPeriodCycleKey: receipt.HostPeriodCycleKey, }, nil } // GetBalances 返回用户多资产余额投影。 func (s *Server) GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) balances, err := s.svc.GetBalances(ctx, req.GetUserId(), req.GetAssetTypes()) if err != nil { return nil, xerr.ToGRPCError(err) } response := &walletv1.GetBalancesResponse{Balances: make([]*walletv1.AssetBalance, 0, len(balances))} for _, balance := range balances { response.Balances = append(response.Balances, &walletv1.AssetBalance{ AppCode: balance.AppCode, AssetType: balance.AssetType, AvailableAmount: balance.AvailableAmount, FrozenAmount: balance.FrozenAmount, Version: balance.Version, }) } return response, nil } // GetWalletOverview 返回钱包首页摘要,供我的页和钱包二级页复用。 func (s *Server) GetWalletOverview(ctx context.Context, req *walletv1.GetWalletOverviewRequest) (*walletv1.GetWalletOverviewResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) overview, err := s.svc.GetWalletOverview(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.GetWalletOverviewResponse{ Balances: balancesToProto(overview.Balances), FeatureFlags: walletFeatureFlagsToProto(overview.FeatureFlags), }, nil } // GetWalletValueSummary 返回我的页钱包卡片最小金币余额。 func (s *Server) GetWalletValueSummary(ctx context.Context, req *walletv1.GetWalletValueSummaryRequest) (*walletv1.GetWalletValueSummaryResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) summary, err := s.svc.GetWalletValueSummary(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.GetWalletValueSummaryResponse{Summary: &walletv1.WalletValueSummary{ CoinAmount: summary.CoinAmount, UpdatedAtMs: summary.UpdatedAtMS, }}, nil } // GetUserGiftWall 返回当前用户收到礼物的聚合墙;调用方负责限定 user_id 为当前登录用户。 func (s *Server) GetUserGiftWall(ctx context.Context, req *walletv1.GetUserGiftWallRequest) (*walletv1.GetUserGiftWallResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) wall, err := s.svc.GetUserGiftWall(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.GetUserGiftWallResponse{ Items: make([]*walletv1.GiftWallItem, 0, len(wall.Items)), GiftKindCount: wall.GiftKindCount, GiftTotalCount: wall.GiftTotalCount, TotalValue: wall.TotalValue, TotalCoinValue: wall.TotalCoinValue, TotalDiamondValue: wall.TotalDiamondValue, TotalGiftPoint: wall.TotalGiftPoint, TotalHeatValue: wall.TotalHeatValue, } for _, item := range wall.Items { resp.Items = append(resp.Items, giftWallItemToProto(item)) } return resp, nil } // ListRechargeProducts 返回当前用户区域可用的充值渠道和档位。 func (s *Server) ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) products, channels, err := s.svc.ListRechargeProducts(ctx, req.GetUserId(), req.GetRegionId(), req.GetPlatform()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.ListRechargeProductsResponse{Channels: channels, Products: make([]*walletv1.RechargeProduct, 0, len(products))} for _, product := range products { resp.Products = append(resp.Products, rechargeProductToProto(product)) } return resp, nil } // ConfirmGooglePayment 校验 Google Play purchase token 并完成钱包入账。 func (s *Server) ConfirmGooglePayment(ctx context.Context, req *walletv1.ConfirmGooglePaymentRequest) (*walletv1.ConfirmGooglePaymentResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.ConfirmGooglePayment(ctx, ledger.GooglePaymentCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), RegionID: req.GetRegionId(), ProductID: req.GetProductId(), ProductCode: req.GetProductCode(), PackageName: req.GetPackageName(), PurchaseToken: req.GetPurchaseToken(), OrderID: req.GetOrderId(), PurchaseTimeMS: req.GetPurchaseTimeMs(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.ConfirmGooglePaymentResponse{ PaymentOrderId: receipt.PaymentOrderID, TransactionId: receipt.TransactionID, Status: receipt.Status, ProductId: receipt.ProductID, ProductCode: receipt.ProductCode, CoinAmount: receipt.CoinAmount, Balance: balanceToProto(receipt.Balance), IdempotentReplay: receipt.IdempotentReplay, ConsumeState: receipt.ConsumeState, }, nil } // ListAdminRechargeProducts 返回后台内购配置列表。 func (s *Server) ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) products, total, err := s.svc.ListAdminRechargeProducts(ctx, ledger.ListRechargeProductsQuery{ AppCode: req.GetAppCode(), Status: req.GetStatus(), Platform: req.GetPlatform(), RegionID: req.GetRegionId(), Keyword: req.GetKeyword(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.ListAdminRechargeProductsResponse{Products: make([]*walletv1.RechargeProduct, 0, len(products)), Total: total} for _, product := range products { resp.Products = append(resp.Products, rechargeProductToProto(product)) } return resp, nil } // CreateRechargeProduct 创建后台内购商品配置。 func (s *Server) CreateRechargeProduct(ctx context.Context, req *walletv1.CreateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) product, err := s.svc.CreateRechargeProduct(ctx, ledger.RechargeProductCommand{ AppCode: req.GetAppCode(), AmountMicro: req.GetAmountMicro(), CoinAmount: req.GetCoinAmount(), ProductName: req.GetProductName(), Description: req.GetDescription(), Platform: req.GetPlatform(), RegionIDs: req.GetRegionIds(), Enabled: req.GetEnabled(), OperatorUserID: req.GetOperatorUserId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.RechargeProductResponse{Product: rechargeProductToProto(product)}, nil } // UpdateRechargeProduct 修改后台内购商品配置。 func (s *Server) UpdateRechargeProduct(ctx context.Context, req *walletv1.UpdateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) product, err := s.svc.UpdateRechargeProduct(ctx, ledger.RechargeProductCommand{ AppCode: req.GetAppCode(), ProductID: req.GetProductId(), AmountMicro: req.GetAmountMicro(), CoinAmount: req.GetCoinAmount(), ProductName: req.GetProductName(), Description: req.GetDescription(), Platform: req.GetPlatform(), RegionIDs: req.GetRegionIds(), Enabled: req.GetEnabled(), OperatorUserID: req.GetOperatorUserId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.RechargeProductResponse{Product: rechargeProductToProto(product)}, nil } // DeleteRechargeProduct 删除后台内购商品配置。 func (s *Server) DeleteRechargeProduct(ctx context.Context, req *walletv1.DeleteRechargeProductRequest) (*walletv1.DeleteRechargeProductResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) if err := s.svc.DeleteRechargeProduct(ctx, req.GetAppCode(), req.GetProductId()); err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.DeleteRechargeProductResponse{Deleted: true}, nil } // GetDiamondExchangeConfig 返回钻石兑换配置。 func (s *Server) GetDiamondExchangeConfig(ctx context.Context, req *walletv1.GetDiamondExchangeConfigRequest) (*walletv1.GetDiamondExchangeConfigResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) rules, err := s.svc.GetDiamondExchangeConfig(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.GetDiamondExchangeConfigResponse{Rules: make([]*walletv1.DiamondExchangeRule, 0, len(rules))} for _, rule := range rules { resp.Rules = append(resp.Rules, &walletv1.DiamondExchangeRule{ ExchangeType: rule.ExchangeType, FromAssetType: rule.FromAssetType, ToAssetType: rule.ToAssetType, FromAmount: rule.FromAmount, ToAmount: rule.ToAmount, Enabled: rule.Enabled, }) } return resp, nil } func rechargeProductToProto(product ledger.RechargeProduct) *walletv1.RechargeProduct { return &walletv1.RechargeProduct{ AppCode: product.AppCode, ProductId: product.ProductID, ProductCode: product.ProductCode, ProductName: product.ProductName, Description: product.Description, Platform: product.Platform, Channel: product.Channel, CurrencyCode: product.CurrencyCode, AmountMicro: product.AmountMicro, AmountMinor: product.AmountMinor, CoinAmount: product.CoinAmount, PolicyVersion: product.PolicyVersion, Status: product.Status, Enabled: product.Enabled, SortOrder: product.SortOrder, RegionIds: product.RegionIDs, ResourceAssetType: product.ResourceAssetType, CreatedByUserId: product.CreatedByUserID, UpdatedByUserId: product.UpdatedByUserID, CreatedAtMs: product.CreatedAtMS, UpdatedAtMs: product.UpdatedAtMS, } } // ListWalletTransactions 返回当前用户钱包流水。 func (s *Server) ListWalletTransactions(ctx context.Context, req *walletv1.ListWalletTransactionsRequest) (*walletv1.ListWalletTransactionsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) items, total, err := s.svc.ListWalletTransactions(ctx, ledger.ListWalletTransactionsQuery{ AppCode: req.GetAppCode(), UserID: req.GetUserId(), AssetType: req.GetAssetType(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.ListWalletTransactionsResponse{Transactions: make([]*walletv1.WalletTransaction, 0, len(items)), Total: total} for _, item := range items { resp.Transactions = append(resp.Transactions, &walletv1.WalletTransaction{ EntryId: item.EntryID, TransactionId: item.TransactionID, BizType: item.BizType, AssetType: item.AssetType, AvailableDelta: item.AvailableDelta, FrozenDelta: item.FrozenDelta, AvailableAfter: item.AvailableAfter, FrozenAfter: item.FrozenAfter, CounterpartyUserId: item.CounterpartyUserID, RoomId: item.RoomID, CreatedAtMs: item.CreatedAtMS, }) } return resp, nil } // ApplyWithdrawal 创建提现申请并冻结美元余额。 func (s *Server) ApplyWithdrawal(ctx context.Context, req *walletv1.ApplyWithdrawalRequest) (*walletv1.ApplyWithdrawalResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) withdrawal, balance, err := s.svc.ApplyWithdrawal(ctx, ledger.WithdrawalCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), Amount: req.GetAmount(), PayoutAccount: req.GetPayoutAccount(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.ApplyWithdrawalResponse{ Withdrawal: withdrawalToProto(withdrawal), Balance: balanceToProto(balance), }, nil } // ListVipPackages 返回当前 VIP 和可购买包列表。 func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) current, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.ListVipPackagesResponse{CurrentVip: vipToProto(current), Packages: make([]*walletv1.VipLevel, 0, len(levels))} for _, level := range levels { resp.Packages = append(resp.Packages, vipLevelToProto(level)) } return resp, nil } // GetMyVip 返回当前登录用户 VIP 状态。 func (s *Server) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) vip, err := s.svc.GetMyVip(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.GetMyVipResponse{Vip: vipToProto(vip)}, nil } // PurchaseVip 购买、续期或升级 VIP。 func (s *Server) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.PurchaseVip(ctx, ledger.PurchaseVipCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), Level: req.GetLevel(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.PurchaseVipResponse{ OrderId: receipt.OrderID, TransactionId: receipt.TransactionID, Vip: vipToProto(receipt.Vip), CoinSpent: receipt.CoinSpent, CoinBalanceAfter: receipt.CoinBalanceAfter, RewardItems: vipRewardItemsToProto(receipt.RewardItems), }, nil } // GrantVip 统一处理活动和后台赠送 VIP。 func (s *Server) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.GrantVip(ctx, ledger.GrantVipCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), TargetUserID: req.GetTargetUserId(), Level: req.GetLevel(), GrantSource: req.GetGrantSource(), OperatorUserID: req.GetOperatorUserId(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.GrantVipResponse{ TransactionId: receipt.TransactionID, Vip: vipToProto(receipt.Vip), RewardItems: vipRewardItemsToProto(receipt.RewardItems), ServerTimeMs: time.Now().UnixMilli(), }, nil } // ListAdminVipLevels 返回后台 VIP 配置。 func (s *Server) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) levels, err := s.svc.ListAdminVipLevels(ctx) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.ListAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli()} for _, level := range levels { resp.Levels = append(resp.Levels, vipLevelToProto(level)) } return resp, nil } // UpdateAdminVipLevels 保存后台 VIP 配置。 func (s *Server) UpdateAdminVipLevels(ctx context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) commands := make([]ledger.AdminVipLevelCommand, 0, len(req.GetLevels())) for _, item := range req.GetLevels() { if item == nil { continue } commands = append(commands, ledger.AdminVipLevelCommand{ Level: item.GetLevel(), Name: item.GetName(), Status: item.GetStatus(), PriceCoin: item.GetPriceCoin(), DurationMS: item.GetDurationMs(), RewardResourceGroupID: item.GetRewardResourceGroupId(), RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(), SortOrder: item.GetSortOrder(), }) } levels, err := s.svc.UpdateAdminVipLevels(ctx, commands, req.GetOperatorUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &walletv1.UpdateAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli()} for _, level := range levels { resp.Levels = append(resp.Levels, vipLevelToProto(level)) } return resp, nil } // AdminCreditAsset 处理后台手动调账,公网权限和审计入口由 gateway/admin 层控制。 func (s *Server) AdminCreditAsset(ctx context.Context, req *walletv1.AdminCreditAssetRequest) (*walletv1.AdminCreditAssetResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) balance, transactionID, err := s.svc.AdminCreditAsset(ctx, ledger.AdminCreditAssetCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), TargetUserID: req.GetTargetUserId(), AssetType: req.GetAssetType(), Amount: req.GetAmount(), OperatorUserID: req.GetOperatorUserId(), Reason: req.GetReason(), EvidenceRef: req.GetEvidenceRef(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.AdminCreditAssetResponse{ TransactionId: transactionID, Balance: &walletv1.AssetBalance{ AppCode: balance.AppCode, AssetType: balance.AssetType, AvailableAmount: balance.AvailableAmount, FrozenAmount: balance.FrozenAmount, Version: balance.Version, }, }, nil } func balancesToProto(balances []ledger.AssetBalance) []*walletv1.AssetBalance { items := make([]*walletv1.AssetBalance, 0, len(balances)) for _, balance := range balances { items = append(items, balanceToProto(balance)) } return items } func balanceToProto(balance ledger.AssetBalance) *walletv1.AssetBalance { return &walletv1.AssetBalance{ AppCode: balance.AppCode, AssetType: balance.AssetType, AvailableAmount: balance.AvailableAmount, FrozenAmount: balance.FrozenAmount, Version: balance.Version, } } func giftWallItemToProto(item ledger.GiftWallItem) *walletv1.GiftWallItem { return &walletv1.GiftWallItem{ GiftId: item.GiftID, GiftName: item.GiftName, ResourceId: item.ResourceID, Resource: giftWallResourceToProto(item.Resource), ResourceSnapshotJson: item.ResourceSnapshotJSON, GiftTypeCode: item.GiftTypeCode, PresentationJson: item.PresentationJSON, GiftCount: item.GiftCount, TotalValue: item.TotalValue, TotalCoinValue: item.TotalCoinValue, TotalDiamondValue: item.TotalDiamondValue, TotalGiftPoint: item.TotalGiftPoint, TotalHeatValue: item.TotalHeatValue, ChargeAssetType: item.ChargeAssetType, LastPriceVersion: item.LastPriceVersion, FirstReceivedAtMs: item.FirstReceivedAtMS, LastReceivedAtMs: item.LastReceivedAtMS, SortOrder: item.SortOrder, } } func giftWallResourceToProto(resource ledger.GiftWallResource) *walletv1.Resource { if resource.ResourceID == 0 { return nil } return &walletv1.Resource{ AppCode: resource.AppCode, ResourceId: resource.ResourceID, ResourceCode: resource.ResourceCode, ResourceType: resource.ResourceType, Name: resource.Name, Status: resource.Status, Grantable: resource.Grantable, GrantStrategy: resource.GrantStrategy, WalletAssetType: resource.WalletAssetType, WalletAssetAmount: resource.WalletAssetAmount, UsageScopes: resource.UsageScopes, AssetUrl: resource.AssetURL, PreviewUrl: resource.PreviewURL, AnimationUrl: resource.AnimationURL, MetadataJson: resource.MetadataJSON, SortOrder: resource.SortOrder, CreatedAtMs: resource.CreatedAtMS, UpdatedAtMs: resource.UpdatedAtMS, } } func walletFeatureFlagsToProto(flags ledger.WalletFeatureFlags) *walletv1.WalletFeatureFlags { return &walletv1.WalletFeatureFlags{ RechargeEnabled: flags.RechargeEnabled, DiamondExchangeEnabled: flags.DiamondExchangeEnabled, WithdrawEnabled: flags.WithdrawEnabled, } } func withdrawalToProto(withdrawal ledger.WithdrawalRequest) *walletv1.WithdrawalRequest { return &walletv1.WithdrawalRequest{ WithdrawalId: withdrawal.WithdrawalID, UserId: withdrawal.UserID, AssetType: withdrawal.AssetType, Amount: withdrawal.Amount, Status: withdrawal.Status, PayoutAccount: withdrawal.PayoutAccount, Reason: withdrawal.Reason, CreatedAtMs: withdrawal.CreatedAtMS, UpdatedAtMs: withdrawal.UpdatedAtMS, } } func vipToProto(vip ledger.UserVip) *walletv1.UserVip { return &walletv1.UserVip{ UserId: vip.UserID, Level: vip.Level, Name: vip.Name, Active: vip.Active, StartedAtMs: vip.StartedAtMS, ExpiresAtMs: vip.ExpiresAtMS, UpdatedAtMs: vip.UpdatedAtMS, } } func vipLevelToProto(level ledger.VipLevel) *walletv1.VipLevel { return &walletv1.VipLevel{ Level: level.Level, Name: level.Name, Status: level.Status, PriceCoin: level.PriceCoin, DurationMs: level.DurationMS, RewardResourceGroupId: level.RewardResourceGroupID, RewardItems: vipRewardItemsToProto(level.RewardItems), CanPurchase: level.CanPurchase, SortOrder: level.SortOrder, RechargeGateRequired: level.RechargeGateRequired, RequiredRechargeCoinAmount: level.RequiredRechargeCoinAmount, UserRechargeCoinAmount: level.UserRechargeCoinAmount, PurchaseLockedReason: level.PurchaseLockedReason, CreatedAtMs: level.CreatedAtMS, UpdatedAtMs: level.UpdatedAtMS, } } func vipRewardItemsToProto(items []ledger.VipRewardItem) []*walletv1.VipRewardItem { resp := make([]*walletv1.VipRewardItem, 0, len(items)) for _, item := range items { resp = append(resp, &walletv1.VipRewardItem{ ResourceId: item.ResourceID, ResourceCode: item.ResourceCode, ResourceType: item.ResourceType, Name: item.Name, Quantity: item.Quantity, ExpiresAtMs: item.ExpiresAtMS, }) } return resp } // CreditTaskReward 处理 activity-service 任务奖励金币入账。 func (s *Server) CreditTaskReward(ctx context.Context, req *walletv1.CreditTaskRewardRequest) (*walletv1.CreditTaskRewardResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.CreditTaskReward(ctx, ledger.TaskRewardCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), TargetUserID: req.GetTargetUserId(), Amount: req.GetAmount(), TaskType: req.GetTaskType(), TaskID: req.GetTaskId(), CycleKey: req.GetCycleKey(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.CreditTaskRewardResponse{ TransactionId: receipt.TransactionID, Amount: receipt.Amount, GrantedAtMs: receipt.GrantedAtMS, Balance: &walletv1.AssetBalance{ AppCode: receipt.Balance.AppCode, AssetType: receipt.Balance.AssetType, AvailableAmount: receipt.Balance.AvailableAmount, FrozenAmount: receipt.Balance.FrozenAmount, Version: receipt.Balance.Version, }, }, nil } // CreditLuckyGiftReward 处理 activity-service 幸运礼物抽奖 outbox 的中奖金币入账。 func (s *Server) CreditLuckyGiftReward(ctx context.Context, req *walletv1.CreditLuckyGiftRewardRequest) (*walletv1.CreditLuckyGiftRewardResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.CreditLuckyGiftReward(ctx, ledger.LuckyGiftRewardCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), TargetUserID: req.GetTargetUserId(), Amount: req.GetAmount(), DrawID: req.GetDrawId(), RoomID: req.GetRoomId(), GiftID: req.GetGiftId(), PoolID: req.GetPoolId(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.CreditLuckyGiftRewardResponse{ TransactionId: receipt.TransactionID, Amount: receipt.Amount, GrantedAtMs: receipt.GrantedAtMS, Balance: &walletv1.AssetBalance{ AppCode: receipt.Balance.AppCode, AssetType: receipt.Balance.AssetType, AvailableAmount: receipt.Balance.AvailableAmount, FrozenAmount: receipt.Balance.FrozenAmount, Version: receipt.Balance.Version, }, }, nil } // ApplyGameCoinChange 处理 game-service 发起的游戏专用金币改账。 func (s *Server) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.ApplyGameCoinChange(ctx, ledger.GameCoinChangeCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), PlatformCode: req.GetPlatformCode(), GameID: req.GetGameId(), ProviderOrderID: req.GetProviderOrderId(), ProviderRoundID: req.GetProviderRoundId(), OpType: req.GetOpType(), CoinAmount: req.GetCoinAmount(), RoomID: req.GetRoomId(), RequestHash: req.GetRequestHash(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.ApplyGameCoinChangeResponse{ WalletTransactionId: receipt.TransactionID, BalanceAfter: receipt.BalanceAfter, IdempotentReplay: receipt.IdempotentReplay, }, nil } // AdminCreditCoinSellerStock 处理后台币商进货或金币补偿,币商身份由 admin-server 先行校验。 func (s *Server) AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.AdminCreditCoinSellerStock(ctx, ledger.CoinSellerStockCreditCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), SellerUserID: req.GetSellerUserId(), StockType: req.GetStockType(), CoinAmount: req.GetCoinAmount(), PaidCurrencyCode: req.GetPaidCurrencyCode(), PaidAmountMicro: req.GetPaidAmountMicro(), PaymentRef: req.GetPaymentRef(), EvidenceRef: req.GetEvidenceRef(), OperatorUserID: req.GetOperatorUserId(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.AdminCreditCoinSellerStockResponse{ TransactionId: receipt.TransactionID, SellerUserId: receipt.SellerUserID, StockType: receipt.StockType, CoinAmount: receipt.CoinAmount, PaidCurrencyCode: receipt.PaidCurrencyCode, PaidAmountMicro: receipt.PaidAmountMicro, CountsAsSellerRecharge: receipt.CountsAsSellerRecharge, BalanceAfter: receipt.BalanceAfter, CreatedAtMs: receipt.CreatedAtMS, }, nil } // TransferCoinFromSeller 处理币商转玩家金币,调用方必须先完成币商身份校验。 func (s *Server) TransferCoinFromSeller(ctx context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) receipt, err := s.svc.TransferCoinFromSeller(ctx, ledger.CoinSellerTransferCommand{ AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), SellerUserID: req.GetSellerUserId(), TargetUserID: req.GetTargetUserId(), SellerRegionID: req.GetSellerRegionId(), TargetRegionID: req.GetTargetRegionId(), Amount: req.GetAmount(), Reason: req.GetReason(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &walletv1.TransferCoinFromSellerResponse{ TransactionId: receipt.TransactionID, SellerBalanceAfter: receipt.SellerBalanceAfter, TargetBalanceAfter: receipt.TargetBalanceAfter, Amount: receipt.Amount, RechargeUsdMinor: receipt.RechargeUSDMinor, RechargeCurrencyCode: receipt.RechargeCurrencyCode, RechargePolicyId: receipt.RechargePolicyID, RechargePolicyVersion: receipt.RechargePolicyVersion, RechargePolicyCoinAmount: receipt.RechargePolicyCoinAmount, RechargePolicyUsdMinorAmount: receipt.RechargePolicyUSDMinorUnit, }, nil }