Files
36-character-flower/src/api/finance-api.ts
JiaJun 2c1a69dd9a feat(game): 添加手续费处理功能并优化提现界面
- 在 API 类型定义中添加 handling_fee 字段支持
- 更新提现组件中的快速金额卡片布局和样式
- 重构 QuickAmountCard 组件以显示金额、钻石和手续费信息
- 在预览区域添加手续费显示行
- 实现手续费计算逻辑和格式化函数
- 更新多语言文件中的手续费相关文本
- 修复提交按钮文本国际化引用路径错误
- 调整桌面和移动端提现界面的宽度和间距参数
2026-07-10 17:28:18 +08:00

496 lines
15 KiB
TypeScript

import {
API_SUCCESS_CODE,
DEFAULT_LIST_PAGE_SIZE,
FINANCE_API_ENDPOINTS,
} from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import type {
ApiResponse,
DepositCreateRequestDto,
DepositCreateResponseDto,
DepositTierItem,
DepositTierItemDto,
DepositWithdrawConfig,
DepositWithdrawConfigDto,
FinanceCurrencyConfigDto,
FinanceDepositConfigDto,
FinanceDepositFieldsDto,
FinanceDepositMethodDto,
FinanceOrderItemDto,
FinanceOrderList,
FinanceOrderListDto,
FinancePayChannelDto,
FinanceRateConfigDto,
FinanceWithdrawBankDto,
FinanceWithdrawConfigDto,
WalletRecordItemDto,
WalletRecordList,
WalletRecordListDto,
WalletRecordType,
WithdrawCreateRequestDto,
WithdrawCreateResponseDto,
} from '@/type'
function unwrapFinanceEnvelope<T>(
response: ApiResponse<T>,
fallbackMessage = 'Finance request failed',
) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
const responseMessage =
typeof response.message === 'string' && response.message.length > 0
? response.message
: typeof response.msg === 'string' && response.msg.length > 0
? response.msg
: fallbackMessage
throw new ApiError({
data: response,
message: responseMessage,
})
}
function toFiniteNumber(value: string | number | null | undefined) {
const numericValue = Number(value)
return Number.isFinite(numericValue) ? numericValue : 0
}
const FINANCE_ASSET_BASE_URL = 'https://zihua-api.h55555game.top'
function normalizeFinanceAssetUrl(value: string | null | undefined) {
const assetPath = value?.trim()
if (!assetPath) {
return ''
}
if (/^https?:\/\//i.test(assetPath)) {
return assetPath
}
if (assetPath.startsWith('/')) {
return `${FINANCE_ASSET_BASE_URL}${assetPath}`
}
return assetPath
}
function normalizeCurrency(dto: FinanceCurrencyConfigDto) {
return {
code: dto.code,
depositCoinsPerFiat: dto.deposit_coins_per_fiat,
depositCoinsPerFiatValue: toFiniteNumber(dto.deposit_coins_per_fiat),
label: dto.label,
withdrawCoinsPerFiat: dto.withdraw_coins_per_fiat,
withdrawCoinsPerFiatValue: toFiniteNumber(dto.withdraw_coins_per_fiat),
}
}
function normalizeRate(dto: FinanceRateConfigDto) {
return {
currency: dto.currency,
diamondsPerFiatUnit: dto.diamonds_per_fiat_unit,
diamondsPerFiatUnitValue: toFiniteNumber(dto.diamonds_per_fiat_unit),
}
}
function normalizePayChannel(dto: FinancePayChannelDto) {
return {
banks: (dto.banks ?? []).map(normalizeWithdrawBank),
code: dto.code,
currencyCodes: Array.isArray(dto.currency_codes) ? dto.currency_codes : [],
depositBanks: (dto.deposit_banks ?? []).map(normalizeWithdrawBank),
feeNote: dto.fee_note ?? '',
handlingFee: dto.handling_fee ?? '0.00',
methods: (dto.methods ?? []).map(normalizeDepositMethod),
minBank: dto.min_bank ?? '',
minEwallet: dto.min_ewallet ?? '',
name: dto.name,
processingNote: dto.processing_note ?? '',
rateHint: dto.rate_hint ?? '',
rateMode: dto.rate_mode ?? '',
reviewThresholdCoin: dto.review_threshold_coin ?? '0',
sort: Number.isFinite(dto.sort) ? dto.sort : 0,
status: typeof dto.status === 'number' ? dto.status : 1,
tierIds: Array.isArray(dto.tier_ids) ? dto.tier_ids : [],
}
}
function normalizeWithdrawBank(dto: FinanceWithdrawBankDto, index: number) {
const code = dto.code ?? dto.id ?? dto.name ?? `bank-${index + 1}`
const label = dto.label ?? dto.name ?? code
return {
code,
currencyCode: dto.currency_code ?? null,
gatewayCode: dto.gateway_code ?? null,
label,
logo: normalizeFinanceAssetUrl(dto.logo),
paymentTypes: Array.isArray(dto.payment_types) ? dto.payment_types : [],
sort:
typeof dto.sort === 'number' && Number.isFinite(dto.sort)
? dto.sort
: index,
status: typeof dto.status === 'number' ? dto.status : 1,
}
}
function normalizeDepositMethod(dto: FinanceDepositMethodDto) {
return {
code: dto.code,
currencyCode: dto.currency_code,
gatewayCode: dto.gateway_code,
maximumAmount: dto.maximum_amount ?? '',
minimumAmount: dto.minimum_amount ?? '',
name: dto.name,
requiresBank: dto.requires_bank,
requiresBankAccount: dto.requires_bank_account,
requiresDepositorName: dto.requires_depositor_name,
requiresFromAddress: dto.requires_from_address,
}
}
function normalizeDepositFields(dto: FinanceDepositFieldsDto | undefined = {}) {
return {
channelCodeParam: dto.channel_code_param ?? 'channel_code',
depositBankAccountParam:
dto.deposit_bank_account_param ?? 'deposit_bank_account',
depositBankParam: dto.deposit_bank_param ?? 'deposit_bank',
depositFromAddressParam:
dto.deposit_from_address_param ?? 'deposit_from_address',
depositNameServerSide: dto.deposit_name_server_side ?? false,
paymentTypeParam: dto.payment_type_param ?? 'payment_type',
requireChannelCode: dto.require_channel_code ?? true,
requireIdempotencyKey: dto.require_idempotency_key ?? true,
requirePaymentType: dto.require_payment_type ?? false,
}
}
function normalizeDepositConfig(
dto: FinanceDepositConfigDto | undefined,
fallbackPayChannels: FinancePayChannelDto[] = [],
) {
const payChannels =
dto?.pay_channels && dto.pay_channels.length > 0
? dto.pay_channels
: fallbackPayChannels
return {
banks: (dto?.banks ?? []).map(normalizeWithdrawBank),
dadapayPendingExpireSeconds: dto?.dadapay_pending_expire_seconds ?? 0,
defaultChannelCode: dto?.default_channel_code ?? '',
defaultChannelByCurrency: dto?.default_channel_by_currency ?? {},
fields: normalizeDepositFields(dto?.fields),
methods: (dto?.methods ?? []).map(normalizeDepositMethod),
payChannels: payChannels.map(normalizePayChannel),
pendingExpireSeconds: dto?.pending_expire_seconds ?? 0,
}
}
function normalizeWithdrawFields(dto: FinanceWithdrawConfigDto['fields'] = {}) {
return {
paymentTypeParam: dto.payment_type_param ?? 'payment_type',
receiveTypeBankOnly: dto.receive_type_bank_only ?? true,
requireBankBranch: dto.require_bank_branch ?? false,
requireBankCode: dto.require_bank_code ?? true,
requireChannelCode: dto.require_channel_code ?? true,
requirePaymentType: dto.require_payment_type ?? false,
requireReceiveAccount: dto.require_receive_account ?? true,
requireReceiverEmail: dto.require_receiver_email ?? true,
requireReceiverMobile: dto.require_receiver_mobile ?? true,
requireReceiverName: dto.require_receiver_name ?? true,
}
}
function normalizeWithdrawConfig(dto: FinanceWithdrawConfigDto) {
return {
banks: (dto.banks ?? []).map(normalizeWithdrawBank),
feeNote: dto.fee_note,
fields: normalizeWithdrawFields(dto.fields),
methods: (dto.methods ?? []).map(normalizeDepositMethod),
minBank: dto.min_bank,
minEwallet: dto.min_ewallet,
payChannels: (dto.pay_channels ?? []).map(normalizePayChannel),
processingNote: dto.processing_note,
rateHint: dto.rate_hint,
rateMode: dto.rate_mode,
reviewThresholdCoin: dto.review_threshold_coin ?? '0',
}
}
function normalizeDepositWithdrawConfig(
dto: DepositWithdrawConfigDto,
): DepositWithdrawConfig {
return {
currencies: (dto.currencies ?? []).map(normalizeCurrency),
defaultDepositChannelCode: dto.default_deposit_channel_code ?? '',
deposit: normalizeDepositConfig(dto.deposit, dto.pay_channels ?? []),
payChannels: (dto.pay_channels ?? []).map(normalizePayChannel),
platformCoinLabel: dto.platform_coin_label,
rates: (dto.rates ?? []).map(normalizeRate),
withdraw: normalizeWithdrawConfig(dto.withdraw),
}
}
function normalizeDepositTierItem(
dto: DepositTierItemDto,
index: number,
): DepositTierItem {
const id = String(dto.id ?? dto.tier_id ?? `tier-${index + 1}`)
const amount = toFiniteNumber(dto.amount)
const bonusAmount = toFiniteNumber(dto.bonus_amount ?? dto.bonus_coins)
const payAmount = toFiniteNumber(dto.pay_amount ?? dto.amount)
const totalAmount = toFiniteNumber(
dto.total_amount ?? dto.coins ?? amount + bonusAmount,
)
const coins = totalAmount
const currency =
typeof dto.currency === 'string' && dto.currency.length > 0
? dto.currency
: null
const payChannelCode =
typeof dto.pay_channel_code === 'string' && dto.pay_channel_code.length > 0
? dto.pay_channel_code
: null
const channelCode =
typeof dto.channel_code === 'string' && dto.channel_code.length > 0
? dto.channel_code
: (payChannelCode ?? null)
const sort = toFiniteNumber(dto.sort ?? dto.pay_amount ?? dto.amount)
const status = toFiniteNumber(dto.status ?? 1)
const channels = (dto.channels ?? [])
.map((channel, channelIndex) => ({
code: channel.code ?? `channel-${channelIndex + 1}`,
name: channel.name ?? channel.code ?? '--',
sort: toFiniteNumber(channel.sort ?? channelIndex),
}))
.sort((left, right) => left.sort - right.sort)
return {
amount,
bonusAmount,
channelCode,
channels,
coins,
currency,
desc: dto.desc ?? '',
id,
name:
typeof dto.name === 'string' && dto.name.length > 0
? dto.name
: `${amount}`,
payAmount,
payChannelCode,
sort,
status,
tierKey:
typeof dto.tier_key === 'string' && dto.tier_key.length > 0
? dto.tier_key
: null,
totalAmount,
title:
typeof dto.title === 'string' && dto.title.length > 0
? dto.title
: typeof dto.name === 'string' && dto.name.length > 0
? dto.name
: `${amount}`,
}
}
function normalizeFinanceOrderItem(dto: FinanceOrderItemDto) {
return {
amount: String(dto.amount ?? ''),
bonusAmount: String(dto.bonus_amount ?? ''),
orderNo: dto.order_no,
status: String(dto.status ?? ''),
}
}
function normalizeFinanceOrderList(dto: FinanceOrderListDto): FinanceOrderList {
return {
list: (dto.list ?? []).map(normalizeFinanceOrderItem),
pagination: {
page: dto.pagination?.page ?? 1,
page_size: dto.pagination?.page_size ?? DEFAULT_LIST_PAGE_SIZE,
total: dto.pagination?.total ?? 0,
},
}
}
function stringifyNullableValue(value: unknown) {
return value === null || value === undefined ? '' : String(value)
}
function normalizeWalletRecordItem(dto: WalletRecordItemDto, index: number) {
const createdAt = dto.created_at ?? dto.create_time ?? dto.time ?? null
const id =
dto.id ??
dto.record_id ??
dto.wallet_record_id ??
dto.order_no ??
`${createdAt ?? 'wallet-record'}-${index + 1}`
return {
amount: stringifyNullableValue(
dto.amount ?? dto.change_amount ?? dto.coin ?? '',
),
balanceAfter: stringifyNullableValue(
dto.balance_after ?? dto.after_balance ?? dto.balance ?? '',
),
balanceBefore: stringifyNullableValue(
dto.balance_before ?? dto.before_balance ?? '',
),
createdAt,
id: String(id),
remark: stringifyNullableValue(dto.remark ?? dto.description ?? dto.memo),
type: stringifyNullableValue(
dto.type ?? dto.change_type ?? dto.biz_type ?? dto.scene,
),
}
}
function normalizeWalletRecordList(dto: WalletRecordListDto): WalletRecordList {
return {
list: (dto.list ?? []).map(normalizeWalletRecordItem),
pagination: {
page: dto.pagination?.page ?? dto.page ?? 1,
page_size:
dto.pagination?.page_size ?? dto.page_size ?? DEFAULT_LIST_PAGE_SIZE,
total: dto.pagination?.total ?? dto.total ?? 0,
},
}
}
export async function getDepositWithdrawConfig() {
const response = await api.post<DepositWithdrawConfigDto>(
FINANCE_API_ENDPOINTS.depositWithdrawConfig,
)
const dto = unwrapFinanceEnvelope(
response as ApiResponse<DepositWithdrawConfigDto>,
'Failed to load deposit and withdrawal config',
)
return normalizeDepositWithdrawConfig(dto)
}
export async function getDepositTierList() {
const response = await api.post<
| DepositTierItemDto[]
| { items?: DepositTierItemDto[]; list?: DepositTierItemDto[] },
undefined
>(FINANCE_API_ENDPOINTS.depositTierList)
const dto = unwrapFinanceEnvelope(
response as ApiResponse<
| DepositTierItemDto[]
| { items?: DepositTierItemDto[]; list?: DepositTierItemDto[] }
>,
'Failed to load deposit tier list',
)
const tierItems = Array.isArray(dto) ? dto : (dto?.list ?? dto?.items ?? [])
return tierItems
.map(normalizeDepositTierItem)
.filter((item) => item.status === 1)
.sort((left, right) => left.sort - right.sort)
}
export async function createDeposit(payload: DepositCreateRequestDto) {
const response = await api.post<
DepositCreateResponseDto,
DepositCreateRequestDto
>(FINANCE_API_ENDPOINTS.depositCreate, {
json: payload,
})
const dto = unwrapFinanceEnvelope(
response as ApiResponse<DepositCreateResponseDto>,
'Failed to create deposit',
)
return dto
}
export async function getDepositOrderList(params?: {
page?: number
pageSize?: number
}) {
const response = await api.get<FinanceOrderListDto>(
FINANCE_API_ENDPOINTS.depositList,
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
},
},
)
const dto = unwrapFinanceEnvelope(
response as ApiResponse<FinanceOrderListDto>,
'Failed to load deposit order list',
)
return normalizeFinanceOrderList(dto)
}
export async function getWithdrawOrderList(params?: {
page?: number
pageSize?: number
}) {
const response = await api.get<FinanceOrderListDto>(
FINANCE_API_ENDPOINTS.withdrawList,
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
},
},
)
const dto = unwrapFinanceEnvelope(
response as ApiResponse<FinanceOrderListDto>,
'Failed to load withdraw order list',
)
return normalizeFinanceOrderList(dto)
}
export async function getWalletRecordList(params?: {
page?: number
pageSize?: number
type?: WalletRecordType
}) {
const response = await api.get<WalletRecordListDto>(
FINANCE_API_ENDPOINTS.walletRecordList,
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
type: params?.type ?? 'payout',
},
},
)
const dto = unwrapFinanceEnvelope(
response as ApiResponse<WalletRecordListDto>,
'Failed to load wallet record list',
)
return normalizeWalletRecordList(dto)
}
export async function createWithdraw(payload: WithdrawCreateRequestDto) {
const response = await api.post<
WithdrawCreateResponseDto,
WithdrawCreateRequestDto
>(FINANCE_API_ENDPOINTS.withdrawCreate, {
json: payload,
})
const dto = unwrapFinanceEnvelope(
response as ApiResponse<WithdrawCreateResponseDto>,
'Failed to create withdraw',
)
return dto
}