package payment import ( "fmt" "strconv" "strings" walletv1 "hyapp.local/api/proto/wallet/v1" ) type rechargeBillDTO struct { AppCode string `json:"appCode"` TransactionID string `json:"transactionId"` CommandID string `json:"commandId"` RechargeType string `json:"rechargeType"` Status string `json:"status"` ExternalRef string `json:"externalRef"` UserID int64 `json:"userId,string"` SellerUserID int64 `json:"sellerUserId,string"` SellerRegionID int64 `json:"sellerRegionId"` TargetRegionID int64 `json:"targetRegionId"` PolicyID int64 `json:"policyId"` PolicyVersion string `json:"policyVersion"` CurrencyCode string `json:"currencyCode"` CoinAmount int64 `json:"coinAmount"` USDMinorAmount int64 `json:"usdMinorAmount"` ExchangeCoinAmount int64 `json:"exchangeCoinAmount"` ExchangeUSDMinorAmount int64 `json:"exchangeUsdMinorAmount"` CreatedAtMS int64 `json:"createdAtMs"` User rechargeBillUserDTO `json:"user"` Seller rechargeBillUserDTO `json:"seller"` } type rechargeBillUserDTO struct { UserID string `json:"userId"` DisplayUserID string `json:"displayUserId"` Username string `json:"username"` Avatar string `json:"avatar"` CountryCode string `json:"countryCode"` CountryName string `json:"countryName"` CountryDisplayName string `json:"countryDisplayName"` } type rechargeProductDTO struct { AppCode string `json:"appCode"` ProductID int64 `json:"productId"` ProductCode string `json:"productCode"` ProductName string `json:"productName"` Description string `json:"description"` AudienceType string `json:"audienceType"` Platform string `json:"platform"` Channel string `json:"channel"` CurrencyCode string `json:"currencyCode"` AmountUSDT string `json:"amountUsdt"` AmountUSDTMicro int64 `json:"amountUsdtMicro"` AmountMicro int64 `json:"amountMicro"` AmountMinor int64 `json:"amountMinor"` CoinAmount int64 `json:"coinAmount"` PolicyVersion string `json:"policyVersion"` RegionIDs []int64 `json:"regionIds"` ResourceAssetType string `json:"resourceAssetType"` Status string `json:"status"` Enabled bool `json:"enabled"` SortOrder int32 `json:"sortOrder"` CreatedByUserID int64 `json:"createdByUserId"` UpdatedByUserID int64 `json:"updatedByUserId"` CreatedAtMS int64 `json:"createdAtMs"` UpdatedAtMS int64 `json:"updatedAtMs"` } type thirdPartyPaymentChannelDTO struct { AppCode string `json:"appCode"` ProviderCode string `json:"providerCode"` ProviderName string `json:"providerName"` Status string `json:"status"` SortOrder int32 `json:"sortOrder"` Methods []thirdPartyPaymentMethodDTO `json:"methods"` } type thirdPartyPaymentMethodDTO struct { MethodID int64 `json:"methodId"` AppCode string `json:"appCode"` ProviderCode string `json:"providerCode"` ProviderName string `json:"providerName"` CountryCode string `json:"countryCode"` CountryName string `json:"countryName"` CurrencyCode string `json:"currencyCode"` PayWay string `json:"payWay"` PayType string `json:"payType"` MethodName string `json:"methodName"` LogoURL string `json:"logoUrl"` Status string `json:"status"` USDToCurrencyRate string `json:"usdToCurrencyRate"` SortOrder int32 `json:"sortOrder"` CreatedAtMS int64 `json:"createdAtMs"` UpdatedAtMS int64 `json:"updatedAtMs"` } func rechargeBillFromProto(item *walletv1.RechargeBill) rechargeBillDTO { if item == nil { return rechargeBillDTO{} } return rechargeBillDTO{ AppCode: item.GetAppCode(), TransactionID: item.GetTransactionId(), CommandID: item.GetCommandId(), RechargeType: item.GetRechargeType(), Status: item.GetStatus(), ExternalRef: item.GetExternalRef(), UserID: item.GetUserId(), SellerUserID: item.GetSellerUserId(), SellerRegionID: item.GetSellerRegionId(), TargetRegionID: item.GetTargetRegionId(), PolicyID: item.GetPolicyId(), PolicyVersion: item.GetPolicyVersion(), CurrencyCode: item.GetCurrencyCode(), CoinAmount: item.GetCoinAmount(), USDMinorAmount: item.GetUsdMinorAmount(), ExchangeCoinAmount: item.GetExchangeCoinAmount(), ExchangeUSDMinorAmount: item.GetExchangeUsdMinorAmount(), CreatedAtMS: item.GetCreatedAtMs(), } } // collectRechargeBillUserIDs 把付款用户和币商用户放进同一个去重列表;后续只查一次 users 表即可覆盖两列展示。 func collectRechargeBillUserIDs(items []rechargeBillDTO) []int64 { seen := make(map[int64]struct{}, len(items)*2) userIDs := make([]int64, 0, len(items)*2) for _, item := range items { for _, userID := range []int64{item.UserID, item.SellerUserID} { if userID <= 0 { continue } if _, ok := seen[userID]; ok { continue } seen[userID] = struct{}{} userIDs = append(userIDs, userID) } } return userIDs } // rechargeBillUserOrFallback 保证用户资料缺失时仍返回长 ID,列表和导出不会因为历史用户资料缺失而显示空白。 func rechargeBillUserOrFallback(profile rechargeBillUserDTO, userID int64) rechargeBillUserDTO { if userID <= 0 { return rechargeBillUserDTO{} } if profile.UserID != "" { return profile } return rechargeBillUserDTO{UserID: strconv.FormatInt(userID, 10)} } // int64Args 将用户 ID 列表转换为 database/sql 可展开的参数切片,避免为 IN 条件拼接原始数值。 func int64Args(values []int64) []any { args := make([]any, 0, len(values)) for _, value := range values { args = append(args, value) } return args } // placeholders 只生成固定数量的问号占位符;实际用户 ID 仍通过参数绑定传入,避免 SQL 注入。 func placeholders(count int) string { if count <= 0 { return "" } items := make([]string, count) for index := range items { items[index] = "?" } return strings.Join(items, ",") } func rechargeProductFromProto(item *walletv1.RechargeProduct) rechargeProductDTO { if item == nil { return rechargeProductDTO{} } return rechargeProductDTO{ AppCode: item.GetAppCode(), ProductID: item.GetProductId(), ProductCode: item.GetProductCode(), ProductName: item.GetProductName(), Description: item.GetDescription(), AudienceType: item.GetAudienceType(), Platform: item.GetPlatform(), Channel: item.GetChannel(), CurrencyCode: item.GetCurrencyCode(), AmountUSDT: formatUSDTMicro(item.GetAmountMicro()), AmountUSDTMicro: item.GetAmountMicro(), AmountMicro: item.GetAmountMicro(), AmountMinor: item.GetAmountMinor(), CoinAmount: item.GetCoinAmount(), PolicyVersion: item.GetPolicyVersion(), RegionIDs: append([]int64(nil), item.GetRegionIds()...), ResourceAssetType: item.GetResourceAssetType(), Status: item.GetStatus(), Enabled: item.GetEnabled(), SortOrder: item.GetSortOrder(), CreatedByUserID: item.GetCreatedByUserId(), UpdatedByUserID: item.GetUpdatedByUserId(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), } } func thirdPartyPaymentChannelFromProto(item *walletv1.ThirdPartyPaymentChannel) thirdPartyPaymentChannelDTO { if item == nil { return thirdPartyPaymentChannelDTO{} } dto := thirdPartyPaymentChannelDTO{ AppCode: item.GetAppCode(), ProviderCode: item.GetProviderCode(), ProviderName: item.GetProviderName(), Status: item.GetStatus(), SortOrder: item.GetSortOrder(), Methods: make([]thirdPartyPaymentMethodDTO, 0, len(item.GetMethods())), } for _, method := range item.GetMethods() { dto.Methods = append(dto.Methods, thirdPartyPaymentMethodFromProto(method)) } return dto } func thirdPartyPaymentMethodFromProto(item *walletv1.ThirdPartyPaymentMethod) thirdPartyPaymentMethodDTO { if item == nil { return thirdPartyPaymentMethodDTO{} } return thirdPartyPaymentMethodDTO{ MethodID: item.GetMethodId(), AppCode: item.GetAppCode(), ProviderCode: item.GetProviderCode(), ProviderName: item.GetProviderName(), CountryCode: item.GetCountryCode(), CountryName: item.GetCountryName(), CurrencyCode: item.GetCurrencyCode(), PayWay: item.GetPayWay(), PayType: item.GetPayType(), MethodName: item.GetMethodName(), LogoURL: item.GetLogoUrl(), Status: item.GetStatus(), USDToCurrencyRate: item.GetUsdToCurrencyRate(), SortOrder: item.GetSortOrder(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), } } func formatUSDTMicro(amount int64) string { if amount <= 0 { return "0" } whole := amount / usdtMicroUnit fraction := amount % usdtMicroUnit if fraction == 0 { return strconv.FormatInt(whole, 10) } return fmt.Sprintf("%d.%s", whole, strings.TrimRight(fmt.Sprintf("%06d", fraction), "0")) }