2026-06-02 16:05:38 +08:00

785 lines
30 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 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,
AssetUrl: item.AssetURL,
PreviewUrl: item.PreviewURL,
AnimationUrl: item.AnimationURL,
})
}
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(),
VisibleRegionID: req.GetVisibleRegionId(),
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
}