2026-05-26 01:02:12 +08:00

743 lines
28 KiB
Go

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(),
})
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,
}, 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
}
// 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
}