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"` ProviderAmountMinor int64 `json:"providerAmountMinor,omitempty"` CreatedAtMS int64 `json:"createdAtMs"` ProviderCode string `json:"providerCode"` UserPaidCurrencyCode string `json:"userPaidCurrencyCode"` UserPaidAmountMicro int64 `json:"userPaidAmountMicro"` ProviderFeeMicro int64 `json:"providerFeeMicro"` ProviderTaxMicro int64 `json:"providerTaxMicro"` ProviderNetMicro int64 `json:"providerNetMicro"` PaidSyncedAtMS int64 `json:"paidSyncedAtMs"` 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"` } type thirdPartyPaymentMethodSyncDTO struct { ProviderCode string `json:"providerCode"` ScannedCountryCount int32 `json:"scannedCountryCount"` FetchedCountryCount int32 `json:"fetchedCountryCount"` CreatedCount int32 `json:"createdCount"` UpdatedCount int32 `json:"updatedCount"` SkippedCount int32 `json:"skippedCount"` FailedCountryCount int `json:"failedCountryCount"` Issues []thirdPartyPaymentMethodIssueDTO `json:"issues"` } type thirdPartyPaymentMethodIssueDTO struct { CountryCode string `json:"countryCode"` CurrencyCode string `json:"currencyCode"` Code string `json:"code"` Message string `json:"message"` } type temporaryPaymentLinkDTO struct { OrderID string `json:"orderId"` AppCode string `json:"appCode"` RegionID int64 `json:"regionId"` OwnerUserID uint `json:"ownerUserId,omitempty"` OwnerUsername string `json:"ownerUsername,omitempty"` OwnerName string `json:"ownerName,omitempty"` CommandID string `json:"commandId,omitempty"` AudienceType string `json:"audienceType"` ProductName string `json:"productName"` CoinAmount int64 `json:"coinAmount"` USDMinorAmount int64 `json:"usdMinorAmount"` ProviderCode string `json:"providerCode"` PaymentMethodID int64 `json:"paymentMethodId"` CountryCode string `json:"countryCode"` CurrencyCode string `json:"currencyCode"` ProviderAmountMinor int64 `json:"providerAmountMinor"` PayWay string `json:"payWay"` PayType string `json:"payType"` PayURL string `json:"payUrl"` ProviderOrderID string `json:"providerOrderId"` TxHash string `json:"txHash"` Status string `json:"status"` FailureReason string `json:"failureReason"` TransactionID string `json:"transactionId"` 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(), ProviderAmountMinor: item.GetProviderAmountMinor(), CreatedAtMS: item.GetCreatedAtMs(), ProviderCode: item.GetProviderCode(), UserPaidCurrencyCode: item.GetUserPaidCurrencyCode(), UserPaidAmountMicro: item.GetUserPaidAmountMicro(), ProviderFeeMicro: item.GetProviderFeeMicro(), ProviderTaxMicro: item.GetProviderTaxMicro(), ProviderNetMicro: item.GetProviderNetMicro(), PaidSyncedAtMS: item.GetPaidSyncedAtMs(), } } // 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 thirdPartyPaymentMethodSyncFromProto(item *walletv1.SyncThirdPartyPaymentMethodsResponse) thirdPartyPaymentMethodSyncDTO { if item == nil { return thirdPartyPaymentMethodSyncDTO{} } dto := thirdPartyPaymentMethodSyncDTO{ ProviderCode: item.GetProviderCode(), ScannedCountryCount: item.GetScannedCountryCount(), FetchedCountryCount: item.GetFetchedCountryCount(), CreatedCount: item.GetCreatedCount(), UpdatedCount: item.GetUpdatedCount(), SkippedCount: item.GetSkippedCount(), FailedCountryCount: len(item.GetIssues()), Issues: make([]thirdPartyPaymentMethodIssueDTO, 0, len(item.GetIssues())), } for _, issue := range item.GetIssues() { dto.Issues = append(dto.Issues, thirdPartyPaymentMethodIssueDTO{ CountryCode: issue.GetCountryCode(), CurrencyCode: issue.GetCurrencyCode(), Code: issue.GetCode(), Message: issue.GetMessage(), }) } return dto } func temporaryPaymentLinkFromProto(item *walletv1.ExternalRechargeOrder) temporaryPaymentLinkDTO { if item == nil { return temporaryPaymentLinkDTO{} } return temporaryPaymentLinkDTO{ OrderID: item.GetOrderId(), AppCode: item.GetAppCode(), AudienceType: item.GetAudienceType(), ProductName: item.GetProductName(), CoinAmount: item.GetCoinAmount(), USDMinorAmount: item.GetUsdMinorAmount(), ProviderCode: item.GetProviderCode(), PaymentMethodID: item.GetPaymentMethodId(), CountryCode: item.GetCountryCode(), CurrencyCode: item.GetCurrencyCode(), ProviderAmountMinor: item.GetProviderAmountMinor(), PayWay: item.GetPayWay(), PayType: item.GetPayType(), PayURL: item.GetPayUrl(), ProviderOrderID: item.GetProviderOrderId(), TxHash: item.GetTxHash(), Status: item.GetStatus(), FailureReason: item.GetFailureReason(), TransactionID: item.GetTransactionId(), 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")) }