package http import ( "hyapp/services/gateway-service/internal/transport/http/httpkit" "net/http" "strings" userv1 "hyapp.local/api/proto/user/v1" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/auth" ) type rechargeProductData struct { ProductID int64 `json:"product_id"` ProductCode string `json:"product_code"` ProductName string `json:"product_name"` Description string `json:"description"` Platform string `json:"platform"` Channel string `json:"channel"` CurrencyCode string `json:"currency_code"` CoinAmount int64 `json:"coin_amount"` AmountMinor int64 `json:"amount_minor"` AmountMicro int64 `json:"amount_micro"` PolicyVersion string `json:"policy_version"` RegionIDs []int64 `json:"region_ids"` ResourceAssetType string `json:"resource_asset_type"` Status string `json:"status"` Enabled bool `json:"enabled"` SortOrder int32 `json:"sort_order"` } type diamondExchangeRuleData struct { ExchangeType string `json:"exchange_type"` FromAssetType string `json:"from_asset_type"` ToAssetType string `json:"to_asset_type"` FromAmount int64 `json:"from_amount"` ToAmount int64 `json:"to_amount"` Enabled bool `json:"enabled"` } type walletOverviewData struct { Balances []assetBalanceData `json:"balances"` FeatureFlags walletFeatureFlagsData `json:"feature_flags"` } type walletFeatureFlagsData struct { RechargeEnabled bool `json:"recharge_enabled"` DiamondExchangeEnabled bool `json:"diamond_exchange_enabled"` WithdrawEnabled bool `json:"withdraw_enabled"` } type walletTransactionData struct { EntryID int64 `json:"entry_id"` TransactionID string `json:"transaction_id"` BizType string `json:"biz_type"` AssetType string `json:"asset_type"` AvailableDelta int64 `json:"available_delta"` FrozenDelta int64 `json:"frozen_delta"` AvailableAfter int64 `json:"available_after"` FrozenAfter int64 `json:"frozen_after"` CounterpartyUserID int64 `json:"counterparty_user_id"` RoomID string `json:"room_id"` CreatedAtMS int64 `json:"created_at_ms"` } type giftWallData struct { Items []giftWallItemData `json:"items"` GiftKindCount int64 `json:"gift_kind_count"` GiftTotalCount int64 `json:"gift_total_count"` TotalValue int64 `json:"total_value"` TotalCoinValue int64 `json:"total_coin_value"` TotalDiamondValue int64 `json:"total_diamond_value"` TotalGiftPoint int64 `json:"total_gift_point"` TotalHeatValue int64 `json:"total_heat_value"` } type giftWallItemData struct { GiftID string `json:"gift_id"` GiftName string `json:"gift_name"` ResourceID int64 `json:"resource_id"` Resource *resourceData `json:"resource,omitempty"` ResourceSnapshotJSON string `json:"resource_snapshot_json"` GiftTypeCode string `json:"gift_type_code"` PresentationJSON string `json:"presentation_json"` GiftCount int64 `json:"gift_count"` TotalValue int64 `json:"total_value"` TotalCoinValue int64 `json:"total_coin_value"` TotalDiamondValue int64 `json:"total_diamond_value"` TotalGiftPoint int64 `json:"total_gift_point"` TotalHeatValue int64 `json:"total_heat_value"` ChargeAssetType string `json:"charge_asset_type"` LastPriceVersion string `json:"last_price_version"` FirstReceivedAtMS int64 `json:"first_received_at_ms"` LastReceivedAtMS int64 `json:"last_received_at_ms"` SortOrder int32 `json:"sort_order"` } type withdrawalApplyRequestBody struct { CommandID string `json:"command_id"` CommandIDAlt string `json:"commandId"` Amount int64 `json:"amount"` PayoutAccount string `json:"payout_account"` PayoutAlt string `json:"payoutAccount"` Reason string `json:"reason"` } type withdrawalData struct { WithdrawalID string `json:"withdrawal_id"` UserID int64 `json:"user_id"` AssetType string `json:"asset_type"` Amount int64 `json:"amount"` Status string `json:"status"` PayoutAccount string `json:"payout_account"` Reason string `json:"reason"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } type vipPurchaseRequestBody struct { CommandID string `json:"command_id"` CommandIDAlt string `json:"commandId"` Level int32 `json:"level"` } type vipPackageData struct { Level int32 `json:"level"` Name string `json:"name"` Status string `json:"status"` PriceCoin int64 `json:"price_coin"` DurationMS int64 `json:"duration_ms"` RewardResourceGroupID int64 `json:"reward_resource_group_id"` RewardItems []vipRewardItemData `json:"reward_items"` CanPurchase bool `json:"can_purchase"` SortOrder int32 `json:"sort_order"` } type vipRewardItemData struct { ResourceID int64 `json:"resource_id"` ResourceCode string `json:"resource_code"` ResourceType string `json:"resource_type"` Name string `json:"name"` Quantity int64 `json:"quantity"` ExpiresAtMS int64 `json:"expires_at_ms"` } // getWalletOverview 返回钱包二级页摘要;我的页首屏使用更轻的 WalletValueSummary。 func (h *Handler) getWalletOverview(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resp, err := h.walletClient.GetWalletOverview(request.Context(), &walletv1.GetWalletOverviewRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } httpkit.WriteOK(writer, request, walletOverviewFromProto(resp)) } // listRechargeProducts 返回用户当前区域可用的充值档位。 func (h *Handler) listRechargeProducts(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil || h.userProfileClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } userID := auth.UserIDFromContext(request.Context()) profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: authRequestMeta(request, ""), UserId: userID, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } resp, err := h.walletClient.ListRechargeProducts(request.Context(), &walletv1.ListRechargeProductsRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: userID, RegionId: profileResp.GetUser().GetRegionId(), Platform: firstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } products := make([]rechargeProductData, 0, len(resp.GetProducts())) for _, product := range resp.GetProducts() { products = append(products, rechargeProductFromProto(product)) } httpkit.WriteOK(writer, request, map[string]any{"channels": resp.GetChannels(), "products": products}) } // getDiamondExchangeConfig 返回钻石兑换配置。 func (h *Handler) getDiamondExchangeConfig(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resp, err := h.walletClient.GetDiamondExchangeConfig(request.Context(), &walletv1.GetDiamondExchangeConfigRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } rules := make([]diamondExchangeRuleData, 0, len(resp.GetRules())) for _, rule := range resp.GetRules() { rules = append(rules, diamondExchangeRuleFromProto(rule)) } httpkit.WriteOK(writer, request, map[string]any{"rules": rules}) } // getMyGiftWall 返回当前登录用户已经收到的礼物墙;gateway 不接受 user_id,避免越权查询他人收礼事实。 func (h *Handler) getMyGiftWall(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resp, err := h.walletClient.GetUserGiftWall(request.Context(), &walletv1.GetUserGiftWallRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } httpkit.WriteOK(writer, request, giftWallFromProto(resp)) } // listWalletTransactions 返回当前用户钱包流水分页。 func (h *Handler) listWalletTransactions(writer http.ResponseWriter, request *http.Request) { h.listWalletTransactionsByAsset(writer, request, strings.TrimSpace(request.URL.Query().Get("asset_type")), 20) } // listCoinTransactions 返回当前用户金币流水分页;默认 page_size=30 对齐 App 最近 30 条展示。 func (h *Handler) listCoinTransactions(writer http.ResponseWriter, request *http.Request) { h.listWalletTransactionsByAsset(writer, request, "COIN", 30) } func (h *Handler) listWalletTransactionsByAsset(writer http.ResponseWriter, request *http.Request, assetType string, defaultPageSize int32) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } page, ok := parsePositiveInt32Query(request, "page", 1) if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } pageSize, ok := parsePositiveInt32Query(request, "page_size", defaultPageSize) if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.walletClient.ListWalletTransactions(request.Context(), &walletv1.ListWalletTransactionsRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), AssetType: assetType, Page: page, PageSize: pageSize, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } items := make([]walletTransactionData, 0, len(resp.GetTransactions())) for _, item := range resp.GetTransactions() { items = append(items, walletTransactionFromProto(item)) } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize}) } // applyWithdrawal 创建待人工审核的提现申请。 func (h *Handler) applyWithdrawal(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } var body withdrawalApplyRequestBody if !decode(writer, request, &body) { return } commandID := strings.TrimSpace(body.CommandID) if commandID == "" { commandID = strings.TrimSpace(body.CommandIDAlt) } payoutAccount := strings.TrimSpace(body.PayoutAccount) if payoutAccount == "" { payoutAccount = strings.TrimSpace(body.PayoutAlt) } if commandID == "" || body.Amount <= 0 || payoutAccount == "" { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.walletClient.ApplyWithdrawal(request.Context(), &walletv1.ApplyWithdrawalRequest{ CommandId: commandID, AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), Amount: body.Amount, PayoutAccount: payoutAccount, Reason: strings.TrimSpace(body.Reason), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } httpkit.WriteOK(writer, request, map[string]any{ "withdrawal": withdrawalFromProto(resp.GetWithdrawal()), "balance": balanceFromProto(resp.GetBalance()), }) } func rechargeProductFromProto(product *walletv1.RechargeProduct) rechargeProductData { if product == nil { return rechargeProductData{} } return rechargeProductData{ ProductID: product.GetProductId(), ProductCode: product.GetProductCode(), ProductName: product.GetProductName(), Description: product.GetDescription(), Platform: product.GetPlatform(), Channel: product.GetChannel(), CurrencyCode: product.GetCurrencyCode(), CoinAmount: product.GetCoinAmount(), AmountMinor: product.GetAmountMinor(), AmountMicro: product.GetAmountMicro(), PolicyVersion: product.GetPolicyVersion(), RegionIDs: product.GetRegionIds(), ResourceAssetType: product.GetResourceAssetType(), Status: product.GetStatus(), Enabled: product.GetEnabled(), SortOrder: product.GetSortOrder(), } } func diamondExchangeRuleFromProto(rule *walletv1.DiamondExchangeRule) diamondExchangeRuleData { if rule == nil { return diamondExchangeRuleData{} } return diamondExchangeRuleData{ ExchangeType: rule.GetExchangeType(), FromAssetType: rule.GetFromAssetType(), ToAssetType: rule.GetToAssetType(), FromAmount: rule.GetFromAmount(), ToAmount: rule.GetToAmount(), Enabled: rule.GetEnabled(), } } func walletOverviewFromProto(resp *walletv1.GetWalletOverviewResponse) walletOverviewData { if resp == nil { return walletOverviewData{} } balances := make([]assetBalanceData, 0, len(resp.GetBalances())) for _, balance := range resp.GetBalances() { balances = append(balances, balanceFromProto(balance)) } flags := resp.GetFeatureFlags() return walletOverviewData{ Balances: balances, FeatureFlags: walletFeatureFlagsData{ RechargeEnabled: flags.GetRechargeEnabled(), DiamondExchangeEnabled: flags.GetDiamondExchangeEnabled(), WithdrawEnabled: flags.GetWithdrawEnabled(), }, } } func walletTransactionFromProto(item *walletv1.WalletTransaction) walletTransactionData { if item == nil { return walletTransactionData{} } return walletTransactionData{ EntryID: item.GetEntryId(), TransactionID: item.GetTransactionId(), BizType: item.GetBizType(), AssetType: item.GetAssetType(), AvailableDelta: item.GetAvailableDelta(), FrozenDelta: item.GetFrozenDelta(), AvailableAfter: item.GetAvailableAfter(), FrozenAfter: item.GetFrozenAfter(), CounterpartyUserID: item.GetCounterpartyUserId(), RoomID: item.GetRoomId(), CreatedAtMS: item.GetCreatedAtMs(), } } func giftWallFromProto(resp *walletv1.GetUserGiftWallResponse) giftWallData { if resp == nil { return giftWallData{} } items := make([]giftWallItemData, 0, len(resp.GetItems())) for _, item := range resp.GetItems() { items = append(items, giftWallItemFromProto(item)) } return giftWallData{ Items: items, GiftKindCount: resp.GetGiftKindCount(), GiftTotalCount: resp.GetGiftTotalCount(), TotalValue: resp.GetTotalValue(), TotalCoinValue: resp.GetTotalCoinValue(), TotalDiamondValue: resp.GetTotalDiamondValue(), TotalGiftPoint: resp.GetTotalGiftPoint(), TotalHeatValue: resp.GetTotalHeatValue(), } } func giftWallItemFromProto(item *walletv1.GiftWallItem) giftWallItemData { if item == nil { return giftWallItemData{} } return giftWallItemData{ GiftID: item.GetGiftId(), GiftName: item.GetGiftName(), ResourceID: item.GetResourceId(), Resource: resourcePointerFromProto(item.GetResource()), ResourceSnapshotJSON: item.GetResourceSnapshotJson(), GiftTypeCode: item.GetGiftTypeCode(), PresentationJSON: item.GetPresentationJson(), GiftCount: item.GetGiftCount(), TotalValue: item.GetTotalValue(), TotalCoinValue: item.GetTotalCoinValue(), TotalDiamondValue: item.GetTotalDiamondValue(), TotalGiftPoint: item.GetTotalGiftPoint(), TotalHeatValue: item.GetTotalHeatValue(), ChargeAssetType: item.GetChargeAssetType(), LastPriceVersion: item.GetLastPriceVersion(), FirstReceivedAtMS: item.GetFirstReceivedAtMs(), LastReceivedAtMS: item.GetLastReceivedAtMs(), SortOrder: item.GetSortOrder(), } } func withdrawalFromProto(item *walletv1.WithdrawalRequest) withdrawalData { if item == nil { return withdrawalData{} } return withdrawalData{ WithdrawalID: item.GetWithdrawalId(), UserID: item.GetUserId(), AssetType: item.GetAssetType(), Amount: item.GetAmount(), Status: item.GetStatus(), PayoutAccount: item.GetPayoutAccount(), Reason: item.GetReason(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), } } func balanceFromProto(balance *walletv1.AssetBalance) assetBalanceData { if balance == nil { return assetBalanceData{} } return assetBalanceData{ AssetType: balance.GetAssetType(), AvailableAmount: balance.GetAvailableAmount(), FrozenAmount: balance.GetFrozenAmount(), Version: balance.GetVersion(), } }