hyapp-server/server/admin/internal/modules/payment/finance_coin_seller_recharge.go
2026-07-09 19:57:39 +08:00

682 lines
22 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 payment
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"hyapp-admin-server/internal/model"
"hyapp-admin-server/internal/repository"
walletv1 "hyapp.local/api/proto/wallet/v1"
)
type financeCoinSellerRechargeQuery struct {
RechargeType string
Status string
PaidState string
Keyword string
RegionID int64
StartAtMS int64
EndAtMS int64
TzOffsetMinutes int32
TargetUserID int64
HasUserOnlyFilter bool
Page int
PageSize int
MaxPageSize int
RequestID string
UserID int64
}
type financeCoinSellerRechargeStats struct {
Total rechargeBillSummaryBucketDTO
Daily map[string]rechargeBillSummaryBucketDTO
Regions []rechargeBillRegionBucketDTO
}
const (
financeCoinSellerRechargeMaxPageSize = 100
financeRechargeMergeMaxPrefetch = 5000
)
func (h *Handler) applyFinanceCoinSellerSummary(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, summary *rechargeBillSummaryDTO) error {
if summary == nil {
return nil
}
rechargeType := normalizedRechargeType(query.RechargeType)
if rechargeType != "" && rechargeType != "coin_seller" {
summary.CoinSeller = rechargeBillSummaryBucketDTO{}
legacyRecomputeSummaryTotal(summary)
return nil
}
stats, err := h.financeCoinSellerRechargeStats(ctx, appCode, query)
if err != nil {
return err
}
summary.CoinSeller = stats.Total
if rechargeType == "coin_seller" {
summary.GooglePlay = rechargeBillSummaryBucketDTO{}
summary.ThirdParty = rechargeBillSummaryBucketDTO{}
}
legacyRecomputeSummaryTotal(summary)
return nil
}
func (h *Handler) applyFinanceCoinSellerOverview(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, overview *rechargeBillOverviewDTO) error {
if overview == nil {
return nil
}
rechargeType := normalizedRechargeType(query.RechargeType)
if rechargeType != "" && rechargeType != "coin_seller" {
for i := range overview.Daily {
overview.Daily[i].CoinSellerUsdMinor = 0
overview.Daily[i].CoinSellerCoinAmount = 0
}
return nil
}
stats, err := h.financeCoinSellerRechargeStats(ctx, appCode, query)
if err != nil {
return err
}
byDate := map[string]int{}
for i := range overview.Daily {
byDate[overview.Daily[i].Date] = i
// 财务工作台的币商充值只认后台币商订单 + H5 币商身份充值wallet 库存流水和 legacy dashboard 旧聚合先清掉再重填。
overview.Daily[i].CoinSellerUsdMinor = 0
overview.Daily[i].CoinSellerCoinAmount = 0
}
for date, bucket := range stats.Daily {
if date == "" {
continue
}
index, ok := byDate[date]
if !ok {
overview.Daily = append(overview.Daily, rechargeBillDailyBucketDTO{Date: date})
index = len(overview.Daily) - 1
byDate[date] = index
}
overview.Daily[index].CoinSellerUsdMinor = bucket.USDMinorAmount
overview.Daily[index].CoinSellerCoinAmount = bucket.CoinAmount
}
sort.Slice(overview.Daily, func(i, j int) bool {
return overview.Daily[i].Date < overview.Daily[j].Date
})
if rechargeType == "coin_seller" {
overview.Regions = h.fillFinanceCoinSellerRegionNames(ctx, appCode, stats.Regions)
return nil
}
if rechargeType == "" {
overview.Regions = h.fillFinanceCoinSellerRegionNames(ctx, appCode, mergeRechargeRegionBuckets(overview.Regions, stats.Regions))
}
return nil
}
func (h *Handler) financeCoinSellerRechargeStats(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery) (financeCoinSellerRechargeStats, error) {
stats := financeCoinSellerRechargeStats{Daily: map[string]rechargeBillSummaryBucketDTO{}}
rechargeType := normalizedRechargeType(query.RechargeType)
if rechargeType != "" && rechargeType != "coin_seller" {
return stats, nil
}
if !financeCoinSellerSucceededStatus(query.Status) || strings.TrimSpace(query.PaidState) != "" || query.HasUserOnlyFilter {
return stats, nil
}
if h != nil && h.store != nil {
repoStats, err := h.store.CoinSellerRechargeOrderStats(repository.CoinSellerRechargeOrderStatsOptions{
AppCode: appCode,
RegionID: query.RegionID,
TargetUserID: query.TargetUserID,
Keyword: query.Keyword,
CreatedFromMS: query.StartAtMS,
CreatedToMS: query.EndAtMS,
TzOffsetMinutes: query.TzOffsetMinutes,
})
if err != nil {
return stats, err
}
stats.addBucket(rechargeBillSummaryBucketDTO{
BillCount: repoStats.VerifiedCount,
CoinAmount: repoStats.VerifiedCoinAmount,
USDMinorAmount: repoStats.VerifiedUSDMinor,
})
for date, bucket := range repoStats.VerifiedDaily {
stats.addDaily(date, rechargeBillSummaryBucketDTO{BillCount: bucket.Count, CoinAmount: bucket.CoinAmount, USDMinorAmount: bucket.USDMinor})
}
for _, row := range repoStats.VerifiedRegionBuckets {
stats.addRegion(row.RegionID, row.Count, row.USDMinor)
}
}
if h != nil && h.walletDB != nil {
if err := h.addH5CoinSellerRechargeStats(ctx, appCode, query, &stats); err != nil {
return stats, err
}
}
sort.Slice(stats.Regions, func(i, j int) bool {
return stats.Regions[i].UsdMinorAmount > stats.Regions[j].UsdMinorAmount
})
return stats, nil
}
func (s *financeCoinSellerRechargeStats) addBucket(bucket rechargeBillSummaryBucketDTO) {
if s == nil {
return
}
s.Total.BillCount += bucket.BillCount
s.Total.CoinAmount += bucket.CoinAmount
s.Total.USDMinorAmount += bucket.USDMinorAmount
}
func (s *financeCoinSellerRechargeStats) addDaily(date string, bucket rechargeBillSummaryBucketDTO) {
if s == nil || date == "" {
return
}
if s.Daily == nil {
s.Daily = map[string]rechargeBillSummaryBucketDTO{}
}
current := s.Daily[date]
current.BillCount += bucket.BillCount
current.CoinAmount += bucket.CoinAmount
current.USDMinorAmount += bucket.USDMinorAmount
s.Daily[date] = current
}
func (s *financeCoinSellerRechargeStats) addRegion(regionID int64, billCount int64, usdMinor int64) {
if s == nil || regionID <= 0 {
return
}
for i := range s.Regions {
if s.Regions[i].RegionID == regionID {
s.Regions[i].BillCount += billCount
s.Regions[i].UsdMinorAmount += usdMinor
return
}
}
s.Regions = append(s.Regions, rechargeBillRegionBucketDTO{RegionID: regionID, BillCount: billCount, UsdMinorAmount: usdMinor})
}
func (h *Handler) addH5CoinSellerRechargeStats(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, stats *financeCoinSellerRechargeStats) error {
where, args := h5CoinSellerRechargeWhere(appCode, query)
var total rechargeBillSummaryBucketDTO
if err := h.walletDB.QueryRowContext(ctx, `
SELECT COUNT(*), COALESCE(SUM(eo.coin_amount), 0), COALESCE(SUM(eo.usd_minor_amount), 0)
FROM external_recharge_orders eo
JOIN wallet_transactions wt ON wt.app_code = eo.app_code AND wt.transaction_id = eo.wallet_transaction_id
`+where, args...).Scan(&total.BillCount, &total.CoinAmount, &total.USDMinorAmount); err != nil {
return err
}
stats.addBucket(total)
tzOffsetMS := int64(query.TzOffsetMinutes) * 60_000
dailyArgs := append([]any{tzOffsetMS}, args...)
rows, err := h.walletDB.QueryContext(ctx, `
SELECT DATE_FORMAT(FROM_UNIXTIME((eo.updated_at_ms + ?) / 1000), '%Y-%m-%d') AS date,
COUNT(*), COALESCE(SUM(eo.coin_amount), 0), COALESCE(SUM(eo.usd_minor_amount), 0)
FROM external_recharge_orders eo
JOIN wallet_transactions wt ON wt.app_code = eo.app_code AND wt.transaction_id = eo.wallet_transaction_id
`+where+`
GROUP BY date
ORDER BY date ASC`, dailyArgs...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var date string
var bucket rechargeBillSummaryBucketDTO
if err := rows.Scan(&date, &bucket.BillCount, &bucket.CoinAmount, &bucket.USDMinorAmount); err != nil {
return err
}
stats.addDaily(date, bucket)
}
if err := rows.Err(); err != nil {
return err
}
regionRows, err := h.walletDB.QueryContext(ctx, `
SELECT eo.target_region_id, COUNT(*), COALESCE(SUM(eo.usd_minor_amount), 0)
FROM external_recharge_orders eo
JOIN wallet_transactions wt ON wt.app_code = eo.app_code AND wt.transaction_id = eo.wallet_transaction_id
`+where+`
GROUP BY eo.target_region_id
ORDER BY 3 DESC`, args...)
if err != nil {
return err
}
defer regionRows.Close()
for regionRows.Next() {
var regionID, billCount, usdMinor int64
if err := regionRows.Scan(&regionID, &billCount, &usdMinor); err != nil {
return err
}
stats.addRegion(regionID, billCount, usdMinor)
}
return regionRows.Err()
}
func (h *Handler) listFinanceCoinSellerRechargeBills(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery) ([]rechargeBillDTO, int64, error) {
if !financeCoinSellerSucceededStatus(query.Status) || strings.TrimSpace(query.PaidState) != "" || query.HasUserOnlyFilter {
return []rechargeBillDTO{}, 0, nil
}
page, pageSize := financeCoinSellerPage(query.Page, query.PageSize, query.MaxPageSize)
prefetch := financeRechargePrefetch(page, pageSize, query.MaxPageSize)
items := make([]rechargeBillDTO, 0, prefetch*2)
var total int64
if h != nil && h.store != nil {
orders, orderTotal, err := h.store.ListCoinSellerRechargeOrders(repository.CoinSellerRechargeOrderListOptions{
Page: 1,
PageSize: prefetch,
PageSizeLimit: prefetch,
AppCode: appCode,
VerifyStatus: model.CoinSellerRechargeVerifyStatusVerified,
Keyword: query.Keyword,
TargetUserID: query.TargetUserID,
RegionID: query.RegionID,
CreatedFromMS: query.StartAtMS,
CreatedToMS: query.EndAtMS,
})
if err != nil {
return nil, 0, err
}
total += orderTotal
for _, order := range orders {
if query.RegionID > 0 && order.TargetRegionID != query.RegionID {
continue
}
items = append(items, financeCoinSellerRechargeOrderBill(order))
}
}
if h != nil && h.walletDB != nil {
h5Items, h5Total, err := h.listH5CoinSellerRechargeBills(ctx, appCode, query, prefetch)
if err != nil {
return nil, 0, err
}
total += h5Total
items = append(items, h5Items...)
}
sortRechargeBills(items)
offset := (page - 1) * pageSize
if offset >= len(items) {
return []rechargeBillDTO{}, total, nil
}
end := offset + pageSize
if end > len(items) {
end = len(items)
}
return items[offset:end], total, nil
}
func (h *Handler) listFinanceAllRechargeBills(ctx context.Context, appCode string, source RechargeBillSource, query financeCoinSellerRechargeQuery) ([]rechargeBillDTO, int64, error) {
page, pageSize := financeCoinSellerPage(query.Page, query.PageSize, query.MaxPageSize)
prefetch := financeRechargePrefetch(page, pageSize, query.MaxPageSize)
items := make([]rechargeBillDTO, 0, prefetch*3)
var total int64
if source != nil {
if !query.HasUserOnlyFilter && query.TargetUserID == 0 {
// legacy 普通充值源没有 user/seller 过滤能力;有用户筛选时宁可不展示普通源,避免 all 视图返回未过滤全量。
ordinary, ordinaryTotal, err := source.ListRechargeBills(ctx, legacyRechargeBillQuery{
Keyword: query.Keyword,
RechargeType: "",
PaidState: query.PaidState,
RegionID: query.RegionID,
StartAtMS: query.StartAtMS,
EndAtMS: query.EndAtMS,
TzOffsetMinutes: query.TzOffsetMinutes,
Page: 1,
PageSize: prefetch,
})
if err != nil {
return nil, 0, err
}
items = append(items, ordinary...)
total += ordinaryTotal
}
} else if h != nil && h.wallet != nil {
// wallet 的空 recharge_type 会并入 coin_seller_stock_recordsall 视图必须拆成普通三方和 Google 两路,币商改走财务新口径。
for _, rechargeType := range []string{"third_party", "google_play_recharge"} {
resp, err := h.wallet.ListRechargeBills(ctx, &walletv1.ListRechargeBillsRequest{
RequestId: query.RequestID,
AppCode: appCode,
UserId: query.UserID,
SellerUserId: query.TargetUserID,
RegionId: query.RegionID,
RechargeType: rechargeType,
PaidState: query.PaidState,
Status: query.Status,
Keyword: query.Keyword,
StartAtMs: query.StartAtMS,
EndAtMs: query.EndAtMS,
Page: 1,
PageSize: int32(prefetch),
})
if err != nil {
return nil, 0, err
}
total += resp.GetTotal()
for _, item := range resp.GetBills() {
items = append(items, rechargeBillFromProto(item))
}
}
}
coinItems, coinTotal, err := h.listFinanceCoinSellerRechargeBills(ctx, appCode, financeCoinSellerRechargeQuery{
RechargeType: "coin_seller",
Status: query.Status,
PaidState: query.PaidState,
Keyword: query.Keyword,
RegionID: query.RegionID,
StartAtMS: query.StartAtMS,
EndAtMS: query.EndAtMS,
TzOffsetMinutes: query.TzOffsetMinutes,
TargetUserID: query.TargetUserID,
HasUserOnlyFilter: query.HasUserOnlyFilter,
Page: 1,
PageSize: prefetch,
MaxPageSize: prefetch,
})
if err != nil {
return nil, 0, err
}
items = append(items, coinItems...)
total += coinTotal
sortRechargeBills(items)
offset := (page - 1) * pageSize
if offset >= len(items) {
return []rechargeBillDTO{}, total, nil
}
end := offset + pageSize
if end > len(items) {
end = len(items)
}
return items[offset:end], total, nil
}
func (h *Handler) walletOrdinaryRechargeOverviewRegions(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery) ([]rechargeBillRegionBucketDTO, error) {
if h == nil || h.wallet == nil {
return []rechargeBillRegionBucketDTO{}, nil
}
if !financeWalletOverviewSupportsOrdinaryFilters(query) {
// wallet overview proto 没有用户、币商、关键词和 paid_state 字段;过滤条件存在时返回空区域,避免展示未过滤的全量区域。
return []rechargeBillRegionBucketDTO{}, nil
}
var out []rechargeBillRegionBucketDTO
for _, rechargeType := range []string{"third_party", "google_play_recharge"} {
resp, err := h.wallet.GetRechargeBillOverview(ctx, &walletv1.GetRechargeBillOverviewRequest{
RequestId: query.RequestID,
AppCode: appCode,
RegionId: query.RegionID,
RechargeType: rechargeType,
Status: query.Status,
StartAtMs: query.StartAtMS,
EndAtMs: query.EndAtMS,
TzOffsetMinutes: query.TzOffsetMinutes,
})
if err != nil {
return nil, err
}
for _, bucket := range resp.GetRegions() {
out = append(out, rechargeBillRegionBucketDTO{
RegionID: bucket.GetRegionId(),
BillCount: bucket.GetBillCount(),
UsdMinorAmount: bucket.GetUsdMinorAmount(),
})
}
}
return mergeRechargeRegionBuckets(out), nil
}
func financeWalletOverviewSupportsOrdinaryFilters(query financeCoinSellerRechargeQuery) bool {
return query.UserID == 0 && query.TargetUserID == 0 && strings.TrimSpace(query.Keyword) == "" && strings.TrimSpace(query.PaidState) == ""
}
func (h *Handler) listH5CoinSellerRechargeBills(ctx context.Context, appCode string, query financeCoinSellerRechargeQuery, limit int) ([]rechargeBillDTO, int64, error) {
where, args := h5CoinSellerRechargeWhere(appCode, query)
var total int64
if err := h.walletDB.QueryRowContext(ctx, `
SELECT COUNT(*)
FROM external_recharge_orders eo
JOIN wallet_transactions wt ON wt.app_code = eo.app_code AND wt.transaction_id = eo.wallet_transaction_id
`+where, args...).Scan(&total); err != nil {
return nil, 0, err
}
if limit < 1 {
return []rechargeBillDTO{}, total, nil
}
rows, err := h.walletDB.QueryContext(ctx, `
SELECT eo.app_code, eo.order_id, wt.command_id, eo.target_user_id, eo.target_region_id,
eo.product_id, eo.product_code, eo.product_name, eo.coin_amount, eo.usd_minor_amount,
eo.provider_code, eo.currency_code, eo.provider_amount_minor, eo.provider_order_id,
eo.wallet_transaction_id, eo.updated_at_ms, eo.created_at_ms
FROM external_recharge_orders eo
JOIN wallet_transactions wt ON wt.app_code = eo.app_code AND wt.transaction_id = eo.wallet_transaction_id
`+where+`
ORDER BY eo.updated_at_ms DESC, eo.order_id DESC
LIMIT ?`, append(args, limit)...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
items := make([]rechargeBillDTO, 0, limit)
for rows.Next() {
var item rechargeBillDTO
var productID int64
var productCode, productName, providerOrderID, walletTransactionID string
var createdAtMS int64
if err := rows.Scan(
&item.AppCode,
&item.ExternalRef,
&item.CommandID,
&item.SellerUserID,
&item.SellerRegionID,
&productID,
&productCode,
&productName,
&item.CoinAmount,
&item.USDMinorAmount,
&item.ProviderCode,
&item.UserPaidCurrencyCode,
&item.ProviderAmountMinor,
&providerOrderID,
&walletTransactionID,
&item.CreatedAtMS,
&createdAtMS,
); err != nil {
return nil, 0, err
}
item.TransactionID = firstNonEmptyPaymentString(walletTransactionID, item.ExternalRef)
item.RechargeType = "coin_seller"
item.Status = "succeeded"
item.TargetRegionID = item.SellerRegionID
item.PolicyID = productID
item.PolicyVersion = productCode
item.CurrencyCode = "USD"
item.UserPaidAmountMicro = item.ProviderAmountMinor * 10_000
item.PaidSyncedAtMS = item.CreatedAtMS
if item.UserPaidCurrencyCode == "" {
item.UserPaidCurrencyCode = "USD"
item.UserPaidAmountMicro = item.USDMinorAmount * 10_000
}
if providerOrderID != "" {
item.ExternalRef = providerOrderID
}
if item.CreatedAtMS == 0 {
item.CreatedAtMS = createdAtMS
}
items = append(items, item)
}
return items, total, rows.Err()
}
func financeCoinSellerRechargeOrderBill(order model.CoinSellerRechargeOrder) rechargeBillDTO {
currency := firstNonEmptyPaymentString(order.ProviderCurrencyCode, "USD")
providerAmountMinor := order.ProviderAmountMinor
userPaidAmountMicro := providerAmountMinor * 10_000
if providerAmountMinor == 0 {
userPaidAmountMicro = order.USDMinorAmount * 10_000
}
return rechargeBillDTO{
AppCode: order.AppCode,
TransactionID: firstNonEmptyPaymentString(order.WalletTransactionID, fmt.Sprintf("coin-seller-order:%d", order.ID)),
CommandID: order.WalletCommandID,
RechargeType: "coin_seller",
Status: "succeeded",
ExternalRef: firstNonEmptyPaymentString(order.ProviderOrderID, order.ExternalOrderNo),
SellerUserID: order.TargetUserID,
SellerRegionID: order.TargetRegionID,
TargetRegionID: order.TargetRegionID,
CurrencyCode: "USD",
CoinAmount: order.CoinAmount,
USDMinorAmount: order.USDMinorAmount,
ProviderAmountMinor: providerAmountMinor,
CreatedAtMS: order.CreatedAtMS,
ProviderCode: order.ProviderCode,
UserPaidCurrencyCode: currency,
UserPaidAmountMicro: userPaidAmountMicro,
PaidSyncedAtMS: order.CreatedAtMS,
}
}
func h5CoinSellerRechargeWhere(appCode string, query financeCoinSellerRechargeQuery) (string, []any) {
where := []string{
"eo.app_code = ?",
"eo.audience_type = 'coin_seller'",
"eo.status = 'credited'",
"eo.wallet_transaction_id <> ''",
"wt.biz_type = 'coin_seller_recharge'",
"wt.status = 'succeeded'",
}
args := []any{appCode}
if query.TargetUserID > 0 {
where = append(where, "eo.target_user_id = ?")
args = append(args, query.TargetUserID)
}
if query.RegionID > 0 {
where = append(where, "eo.target_region_id = ?")
args = append(args, query.RegionID)
}
if query.StartAtMS > 0 {
where = append(where, "eo.updated_at_ms >= ?")
args = append(args, query.StartAtMS)
}
if query.EndAtMS > 0 {
where = append(where, "eo.updated_at_ms < ?")
args = append(args, query.EndAtMS)
}
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
like := "%" + keyword + "%"
where = append(where, "(eo.order_id LIKE ? OR eo.provider_order_id LIKE ? OR eo.wallet_transaction_id LIKE ? OR eo.product_code LIKE ? OR eo.product_name LIKE ? OR eo.tx_hash LIKE ? OR CAST(eo.target_user_id AS CHAR) LIKE ?)")
args = append(args, like, like, like, like, like, like, like)
}
return "WHERE " + strings.Join(where, " AND "), args
}
func financeCoinSellerSucceededStatus(status string) bool {
switch strings.ToLower(strings.TrimSpace(status)) {
case "", "all", "succeeded", "success":
return true
default:
return false
}
}
func financeCoinSellerPage(page int, pageSize int, maxPageSize int) (int, int) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 10
}
if maxPageSize <= 0 {
maxPageSize = financeCoinSellerRechargeMaxPageSize
}
if pageSize > maxPageSize {
pageSize = maxPageSize
}
return page, pageSize
}
func financeRechargePrefetch(page int, pageSize int, maxPageSize int) int {
prefetch := page * pageSize
limit := financeRechargeMergeMaxPrefetch
if maxPageSize > limit {
limit = maxPageSize
}
if prefetch > limit {
return limit
}
return prefetch
}
func sortRechargeBills(items []rechargeBillDTO) {
sort.Slice(items, func(i, j int) bool {
if items[i].CreatedAtMS == items[j].CreatedAtMS {
return items[i].TransactionID > items[j].TransactionID
}
return items[i].CreatedAtMS > items[j].CreatedAtMS
})
}
func mergeRechargeRegionBuckets(groups ...[]rechargeBillRegionBucketDTO) []rechargeBillRegionBucketDTO {
byRegion := map[int64]*rechargeBillRegionBucketDTO{}
for _, group := range groups {
for _, row := range group {
if row.RegionID <= 0 {
continue
}
bucket, ok := byRegion[row.RegionID]
if !ok {
copy := row
byRegion[row.RegionID] = &copy
continue
}
bucket.BillCount += row.BillCount
bucket.UsdMinorAmount += row.UsdMinorAmount
if bucket.Name == "" {
bucket.Name = row.Name
}
}
}
out := make([]rechargeBillRegionBucketDTO, 0, len(byRegion))
for _, bucket := range byRegion {
out = append(out, *bucket)
}
sort.Slice(out, func(i, j int) bool {
return out[i].UsdMinorAmount > out[j].UsdMinorAmount
})
return out
}
func (h *Handler) fillFinanceCoinSellerRegionNames(ctx context.Context, appCode string, buckets []rechargeBillRegionBucketDTO) []rechargeBillRegionBucketDTO {
if len(buckets) == 0 {
return buckets
}
names := map[int64]string{}
if h != nil {
if regions, err := h.listRechargeBillRegions(ctx, appCode); err == nil {
for _, region := range regions {
name := firstNonEmptyPaymentString(region.Name, region.RegionCode, strconv.FormatInt(region.RegionID, 10))
names[region.RegionID] = name
}
}
}
for i := range buckets {
if name := names[buckets[i].RegionID]; name != "" {
buckets[i].Name = name
continue
}
buckets[i].Name = strconv.FormatInt(buckets[i].RegionID, 10)
}
return buckets
}
func firstNonEmptyPaymentString(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}