2026-07-14 14:07:17 +08:00

647 lines
26 KiB
JavaScript
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.

import {
COIN_SELLER_RECHARGE_CHAINS,
COIN_SELLER_RECHARGE_GRANT_STATUS,
COIN_SELLER_RECHARGE_PROVIDERS,
COIN_SELLER_RECHARGE_STATUS,
COIN_SELLER_RECHARGE_VERIFY_STATUS,
WITHDRAWAL_STATUS,
} from "./constants.js";
export function normalizeWithdrawalApplication(item = {}) {
return {
appCode: stringValue(item.appCode ?? item.app_code),
appName: stringValue(item.appName ?? item.app_name),
approvedAtMs: numberOrNull(item.approvedAtMs ?? item.approved_at_ms),
approverName: stringValue(
item.approverName ?? item.approver_name ?? item.approverAccount ?? item.approver_account,
),
approverUserId: item.approverUserId ?? item.approver_user_id,
auditImageUrl: stringValue(item.auditImageUrl ?? item.audit_image_url),
auditRemark: stringValue(item.auditRemark ?? item.audit_remark),
auditTransactionId: stringValue(item.auditTransactionId ?? item.audit_transaction_id),
createdAtMs: numberOrNull(item.createdAtMs ?? item.created_at_ms),
freezeTransactionId: stringValue(item.freezeTransactionId ?? item.freeze_transaction_id),
id: item.id ?? item.withdrawalApplicationId ?? item.withdrawal_application_id,
salaryAssetType: stringValue(item.salaryAssetType ?? item.salary_asset_type),
status: stringValue(item.status),
updatedAtMs: numberOrNull(item.updatedAtMs ?? item.updated_at_ms),
userId: stringValue(item.userId ?? item.user_id),
withdrawAddress: stringValue(item.withdrawAddress ?? item.withdraw_address),
withdrawAmount: numberValue(item.withdrawAmount ?? item.withdraw_amount),
withdrawAmountMinor: numberValue(item.withdrawAmountMinor ?? item.withdraw_amount_minor),
withdrawMethod: stringValue(item.withdrawMethod ?? item.withdraw_method),
};
}
export function normalizeWithdrawalApplicationPage(data = {}) {
const items = Array.isArray(data.items) ? data.items : [];
return {
items: items.map(normalizeWithdrawalApplication),
page: Number(data.page || 1),
pageSize: Number(data.pageSize ?? data.page_size ?? items.length),
total: Number(data.total ?? items.length),
};
}
export function buildCoinSellerRechargeOrderPayload(form) {
const providerCode = stringValue(form.providerCode);
const providerCurrencyCode = stringValue(form.providerCurrencyCode).toUpperCase();
const providerAmountMinor = providerAmountMinorFromForm(form);
const payload = {
appCode: stringValue(form.appCode),
chain: providerCode === "usdt" ? stringValue(form.chain).toUpperCase() : "",
externalOrderNo: stringValue(form.externalOrderNo),
isMakeup: Boolean(form.isMakeup),
orderDateMs: numberValue(form.orderDateMs),
providerAmountMinor: requiresThirdPartyProviderAmount(providerCode) ? providerAmountMinor : "",
providerCode,
providerCurrencyCode: requiresThirdPartyProviderAmount(providerCode) ? providerCurrencyCode : "",
remark: stringValue(form.remark),
targetUserId: stringValue(form.targetUserId),
usdAmount: numberValue(form.usdAmount),
};
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== ""));
}
export function buildCoinSellerRechargeReceiptPayload(form) {
const providerCode = stringValue(form.providerCode);
const providerCurrencyCode = stringValue(form.providerCurrencyCode).toUpperCase();
const providerAmountMinor = providerAmountMinorFromForm(form);
return Object.fromEntries(
Object.entries({
appCode: stringValue(form.appCode),
chain: providerCode === "usdt" ? stringValue(form.chain).toUpperCase() : "",
externalOrderNo: stringValue(form.externalOrderNo),
orderDateMs: numberValue(form.orderDateMs),
providerAmountMinor: requiresThirdPartyProviderAmount(providerCode) ? providerAmountMinor : "",
providerCode,
providerCurrencyCode: requiresThirdPartyProviderAmount(providerCode) ? providerCurrencyCode : "",
usdAmount: numberValue(form.usdAmount),
}).filter(([, value]) => value !== ""),
);
}
export function validateCoinSellerRechargeOrderPayload(payload) {
if (!payload.appCode) {
return "请选择 APP";
}
if (!payload.targetUserId) {
return "请填写目标用户 ID";
}
if (!Number.isFinite(payload.usdAmount) || payload.usdAmount <= 0) {
return "请填写大于 0 的 USD 金额";
}
if (!Number.isInteger(payload.orderDateMs) || payload.orderDateMs <= 0) {
return "请选择订单时间";
}
if (!payload.providerCode) {
return "请选择支付渠道";
}
if (!payload.externalOrderNo) {
return "请填写三方订单号";
}
if (payload.providerCode === "usdt" && !payload.chain) {
return "请选择 USDT 链";
}
if (requiresThirdPartyProviderAmount(payload.providerCode)) {
if (!payload.providerCurrencyCode) {
return "请填写币种";
}
if (!Number.isInteger(payload.providerAmountMinor) || payload.providerAmountMinor <= 0) {
return "请填写大于 0 的币种金额";
}
}
if (payload.remark && payload.remark.length > 512) {
return "备注不能超过 512 字";
}
return "";
}
export function buildCoinSellerRechargeQuotePayload(form) {
return {
appCode: stringValue(form.appCode),
targetUserId: stringValue(form.targetUserId),
usdAmount: numberValue(form.usdAmount),
};
}
export function buildCoinSellerRechargeExchangeRatePayload(config) {
return {
tiers: (config.tiers || []).map((item) => ({
coinsPerUsd: numberValue(item.coinsPerUsd),
maxUsdAmount: numberValue(item.maxUsdAmount),
minUsdAmount: numberValue(item.minUsdAmount),
})),
whitelist: (config.whitelist || []).map((item) => ({
coinsPerUsd: numberValue(item.coinsPerUsd),
userId: stringValue(item.userId),
})),
};
}
export function validateCoinSellerRechargeExchangeRatePayload(payload) {
if (!payload.tiers.length) {
return "至少添加一个 USD 金额区间";
}
const tiers = [...payload.tiers].sort((left, right) => left.minUsdAmount - right.minUsdAmount);
for (let index = 0; index < tiers.length; index += 1) {
const item = tiers[index];
if (
!Number.isFinite(item.minUsdAmount) ||
item.minUsdAmount <= 0 ||
!Number.isFinite(item.maxUsdAmount) ||
item.maxUsdAmount < item.minUsdAmount ||
!Number.isInteger(item.coinsPerUsd) ||
item.coinsPerUsd <= 0
) {
return "请填写正确的 USD 金额区间和金币汇率";
}
if (index > 0 && item.minUsdAmount <= tiers[index - 1].maxUsdAmount) {
return "USD 金额区间不能重叠";
}
}
const userIds = new Set();
for (const item of payload.whitelist) {
if (!item.userId || !Number.isInteger(item.coinsPerUsd) || item.coinsPerUsd <= 0) {
return "请填写正确的白名单用户 ID 和金币汇率";
}
if (userIds.has(item.userId)) {
return `白名单用户 ID 重复:${item.userId}`;
}
userIds.add(item.userId);
}
return "";
}
export function normalizeCoinSellerRechargeOrder(item = {}) {
return {
appCode: stringValue(item.appCode ?? item.app_code),
chain: stringValue(item.chain),
coinAmount: numberValue(item.coinAmount ?? item.coin_amount),
createdAtMs: numberOrNull(item.createdAtMs ?? item.created_at_ms),
externalOrderNo: stringValue(item.externalOrderNo ?? item.external_order_no),
failureReason: stringValue(item.failureReason ?? item.failure_reason),
grantStatus: stringValue(item.grantStatus ?? item.grant_status) || "pending",
grantedAtMs: numberOrNull(item.grantedAtMs ?? item.granted_at_ms),
grantedByName: stringValue(item.grantedByName ?? item.granted_by_name),
id: stringValue(item.id ?? item.orderId ?? item.order_id),
operatorName: stringValue(item.operatorName ?? item.operator_name),
orderDateMs: numberOrNull(item.orderDateMs ?? item.order_date_ms),
providerAmountMinor: numberOrNull(item.providerAmountMinor ?? item.provider_amount_minor),
providerCode: stringValue(item.providerCode ?? item.provider_code),
providerCurrencyCode: stringValue(item.providerCurrencyCode ?? item.provider_currency_code),
providerOrderId: stringValue(item.providerOrderId ?? item.provider_order_id),
providerPayloadJson: stringValue(item.providerPayloadJson ?? item.provider_payload_json),
providerStatus: stringValue(item.providerStatus ?? item.provider_status),
receiveAddress: stringValue(item.receiveAddress ?? item.receive_address),
remark: stringValue(item.remark),
status: stringValue(item.status) || "pending",
targetDisplayUserId: stringValue(item.targetDisplayUserId ?? item.target_display_user_id),
targetUserId: stringValue(item.targetUserId ?? item.target_user_id),
usdAmount: item.usdAmount ?? item.usd_amount ?? "",
usdMinorAmount: numberOrNull(item.usdMinorAmount ?? item.usd_minor_amount),
verifiedAtMs: numberOrNull(item.verifiedAtMs ?? item.verified_at_ms),
verifiedByName: stringValue(item.verifiedByName ?? item.verified_by_name),
verifyStatus: stringValue(item.verifyStatus ?? item.verify_status) || "pending",
walletAmountDelta: numberOrNull(item.walletAmountDelta ?? item.wallet_amount_delta),
walletAssetType: stringValue(item.walletAssetType ?? item.wallet_asset_type),
walletBalanceAfter: numberOrNull(item.walletBalanceAfter ?? item.wallet_balance_after),
walletCommandId: stringValue(item.walletCommandId ?? item.wallet_command_id),
walletTransactionId: stringValue(item.walletTransactionId ?? item.wallet_transaction_id),
};
}
export function normalizeCoinSellerRechargeOrderPage(data = {}) {
const items = Array.isArray(data.items) ? data.items : [];
return {
items: items.map(normalizeCoinSellerRechargeOrder),
page: Number(data.page || 1),
pageSize: Number(data.pageSize ?? data.page_size ?? items.length),
total: Number(data.total ?? items.length),
};
}
export function normalizeRechargeBill(item = {}) {
return {
appCode: stringValue(item.appCode ?? item.app_code),
appName: stringValue(item.appName ?? item.app_name),
billAmountMinor: numberOrNull(item.billAmountMinor ?? item.bill_amount_minor),
billAmountText: stringValue(
item.billAmountText ?? item.bill_amount_text ?? item.billAmount ?? item.bill_amount,
),
coinAmount: numberValue(item.coinAmount ?? item.coin_amount),
commandId: stringValue(item.commandId ?? item.command_id),
createdAtMs: numberOrNull(item.createdAtMs ?? item.created_at_ms),
currencyCode: stringValue(item.currencyCode ?? item.currency_code) || "USD",
exchangeCoinAmount: numberValue(item.exchangeCoinAmount ?? item.exchange_coin_amount),
exchangeUsdMinorAmount: numberValue(item.exchangeUsdMinorAmount ?? item.exchange_usd_minor_amount),
externalRef: stringValue(item.externalRef ?? item.external_ref),
id: stringValue(item.id ?? item.transactionId ?? item.transaction_id),
paidSyncedAtMs: numberOrNull(item.paidSyncedAtMs ?? item.paid_synced_at_ms),
providerAmountMinor: numberOrNull(item.providerAmountMinor ?? item.provider_amount_minor),
providerCode: stringValue(item.providerCode ?? item.provider_code),
providerFeeMicro: numberOrNull(item.providerFeeMicro ?? item.provider_fee_micro),
providerNetMicro: numberOrNull(item.providerNetMicro ?? item.provider_net_micro),
providerTaxMicro: numberOrNull(item.providerTaxMicro ?? item.provider_tax_micro),
rechargeType: stringValue(item.rechargeType ?? item.recharge_type),
sellerRegionId: numberOrNull(item.sellerRegionId ?? item.seller_region_id),
sellerUserId: numberOrNull(item.sellerUserId ?? item.seller_user_id),
status: stringValue(item.status),
targetRegionId: numberOrNull(item.targetRegionId ?? item.target_region_id),
taxDeductionMinor: numberOrNull(
item.taxDeductionMinor ??
item.tax_deduction_minor ??
item.thirdPartyTaxDeductionMinor ??
item.third_party_tax_deduction_minor,
),
taxDeductionText: stringValue(
item.taxDeductionText ??
item.tax_deduction_text ??
item.thirdPartyTaxDeduction ??
item.third_party_tax_deduction,
),
taxRate: numberOrNull(item.taxRate ?? item.tax_rate ?? item.thirdPartyTaxRate ?? item.third_party_tax_rate),
transactionId: stringValue(item.transactionId ?? item.transaction_id),
usdMinorAmount: numberValue(item.usdMinorAmount ?? item.usd_minor_amount),
user: {
displayUserId: stringValue(item.user?.displayUserId ?? item.user?.display_user_id),
userId: stringValue(item.user?.userId ?? item.user?.user_id) || stringValue(item.userId ?? item.user_id),
username: stringValue(item.user?.username),
},
userPaidAmountMicro: numberOrNull(
item.userPaidAmountMicro ?? item.user_paid_amount_micro ?? item.paidAmountMicro ?? item.paid_amount_micro,
),
userPaidAmountMinor: numberOrNull(
item.userPaidAmountMinor ??
item.user_paid_amount_minor ??
item.paidAmountMinor ??
item.paid_amount_minor ??
item.providerAmountMinor ??
item.provider_amount_minor,
),
userPaidCurrencyCode: stringValue(item.userPaidCurrencyCode ?? item.user_paid_currency_code),
userPaidText: stringValue(
item.userPaidText ??
item.user_paid_text ??
item.userPaidAmount ??
item.user_paid_amount ??
item.paidAmount ??
item.paid_amount,
),
};
}
export function normalizeRechargeBillPage(data = {}) {
const rawItems = Array.isArray(data.items) ? data.items : Array.isArray(data.bills) ? data.bills : [];
return {
items: rawItems.map(normalizeRechargeBill),
page: Number(data.page || 1),
pageSize: Number(data.pageSize ?? data.page_size ?? rawItems.length),
total: Number(data.total ?? rawItems.length),
};
}
const EMPTY_RECHARGE_SUMMARY_BUCKET = { billCount: 0, coinAmount: 0, usdMinorAmount: 0 };
export const EMPTY_RECHARGE_SUMMARY = {
coinSeller: EMPTY_RECHARGE_SUMMARY_BUCKET,
googlePlay: EMPTY_RECHARGE_SUMMARY_BUCKET,
thirdParty: EMPTY_RECHARGE_SUMMARY_BUCKET,
total: EMPTY_RECHARGE_SUMMARY_BUCKET,
};
function normalizeRechargeSummaryBucket(bucket = {}) {
return {
billCount: numberValue(bucket.billCount ?? bucket.bill_count) || 0,
coinAmount: numberValue(bucket.coinAmount ?? bucket.coin_amount) || 0,
usdMinorAmount: numberValue(bucket.usdMinorAmount ?? bucket.usd_minor_amount) || 0,
};
}
export function normalizeRechargeSummary(data = {}) {
return {
coinSeller: normalizeRechargeSummaryBucket(data.coinSeller ?? data.coin_seller),
googlePlay: normalizeRechargeSummaryBucket(data.googlePlay ?? data.google_play),
thirdParty: normalizeRechargeSummaryBucket(data.thirdParty ?? data.third_party),
total: normalizeRechargeSummaryBucket(data.total),
};
}
export function mergeRechargeSummaries(summaries = []) {
const merged = normalizeRechargeSummary({});
for (const summary of summaries) {
for (const key of ["coinSeller", "googlePlay", "thirdParty", "total"]) {
merged[key] = {
billCount: merged[key].billCount + summary[key].billCount,
coinAmount: merged[key].coinAmount + summary[key].coinAmount,
usdMinorAmount: merged[key].usdMinorAmount + summary[key].usdMinorAmount,
};
}
}
return merged;
}
export const EMPTY_RECHARGE_OVERVIEW = {
daily: [],
googlePaid: {
coveredUsdMinor: 0,
estFeeUsdMinor: 0,
estNetUsdMinor: 0,
estTaxUsdMinor: 0,
googleBillCount: 0,
syncedCount: 0,
unsyncedCount: 0,
},
regions: [],
withdrawal: {
approvedCount: 0,
approvedUsdMinor: 0,
totalUsdMinor: 0,
transferCount: 0,
transferUsdMinor: 0,
},
};
export function normalizeRechargeOverview(data = {}) {
const daily = Array.isArray(data.daily) ? data.daily : [];
const regions = Array.isArray(data.regions) ? data.regions : [];
const paid = data.googlePaid ?? data.google_paid ?? {};
const withdrawal = data.withdrawal ?? {};
return {
daily: daily.map((item) => ({
coinSellerCoinAmount: numberValue(item.coinSellerCoinAmount ?? item.coin_seller_coin_amount) || 0,
coinSellerUsdMinor: numberValue(item.coinSellerUsdMinor ?? item.coin_seller_usd_minor) || 0,
date: stringValue(item.date),
googleCoinAmount: numberValue(item.googleCoinAmount ?? item.google_coin_amount) || 0,
googleUsdMinor: numberValue(item.googleUsdMinor ?? item.google_usd_minor) || 0,
thirdPartyCoinAmount: numberValue(item.thirdPartyCoinAmount ?? item.third_party_coin_amount) || 0,
thirdPartyUsdMinor: numberValue(item.thirdPartyUsdMinor ?? item.third_party_usd_minor) || 0,
})),
googlePaid: {
coveredUsdMinor: numberValue(paid.coveredUsdMinor ?? paid.covered_usd_minor) || 0,
estFeeUsdMinor: numberValue(paid.estFeeUsdMinor ?? paid.est_fee_usd_minor) || 0,
estNetUsdMinor: numberValue(paid.estNetUsdMinor ?? paid.est_net_usd_minor) || 0,
estTaxUsdMinor: numberValue(paid.estTaxUsdMinor ?? paid.est_tax_usd_minor) || 0,
googleBillCount: numberValue(paid.googleBillCount ?? paid.google_bill_count) || 0,
syncedCount: numberValue(paid.syncedCount ?? paid.synced_count) || 0,
unsyncedCount: numberValue(paid.unsyncedCount ?? paid.unsynced_count) || 0,
},
regions: regions.map((item) => ({
billCount: numberValue(item.billCount ?? item.bill_count) || 0,
name: stringValue(item.name),
regionId: numberOrNull(item.regionId ?? item.region_id),
usdMinorAmount: numberValue(item.usdMinorAmount ?? item.usd_minor_amount) || 0,
})),
withdrawal: {
approvedCount: numberValue(withdrawal.approvedCount ?? withdrawal.approved_count) || 0,
approvedUsdMinor: numberValue(withdrawal.approvedUsdMinor ?? withdrawal.approved_usd_minor) || 0,
totalUsdMinor: numberValue(withdrawal.totalUsdMinor ?? withdrawal.total_usd_minor) || 0,
transferCount: numberValue(withdrawal.transferCount ?? withdrawal.transfer_count) || 0,
transferUsdMinor: numberValue(withdrawal.transferUsdMinor ?? withdrawal.transfer_usd_minor) || 0,
},
};
}
// mergeRechargeOverviews 把多 App 概览合并成全局口径daily 按日期求和googlePaid 求和;
// 区域 ID 属于单 App 语义,跨 App 合并时丢弃区域分布。
export function mergeRechargeOverviews(overviews = []) {
const dailyByDate = new Map();
const merged = normalizeRechargeOverview({});
for (const overview of overviews) {
for (const bucket of overview.daily) {
const existing = dailyByDate.get(bucket.date);
if (!existing) {
dailyByDate.set(bucket.date, { ...bucket });
continue;
}
for (const key of [
"coinSellerCoinAmount",
"coinSellerUsdMinor",
"googleCoinAmount",
"googleUsdMinor",
"thirdPartyCoinAmount",
"thirdPartyUsdMinor",
]) {
existing[key] += bucket[key];
}
}
for (const key of Object.keys(merged.googlePaid)) {
merged.googlePaid[key] += overview.googlePaid[key];
}
for (const key of Object.keys(merged.withdrawal)) {
merged.withdrawal[key] += overview.withdrawal[key];
}
}
merged.daily = [...dailyByDate.values()].sort((left, right) => left.date.localeCompare(right.date));
return merged;
}
export function normalizeRechargeRegions(data = {}) {
const items = Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : [];
return items
.map((item) => {
const regionId = numberOrNull(item.regionId ?? item.region_id);
if (!regionId) {
return null;
}
return {
name: stringValue(item.name),
regionCode: stringValue(item.regionCode ?? item.region_code),
regionId,
};
})
.filter(Boolean);
}
export function rechargeTypeLabel(value) {
if (value === "coin_seller_transfer") {
return "币商充值";
}
if (value === "coin_seller_stock_purchase") {
return "币商进货";
}
if (value === "coin_seller_stock_deduction") {
return "币商冲回";
}
if (value === "google_play_recharge") {
return "谷歌充值";
}
if (value === "mifapay") {
return "三方充值 · MiFaPay";
}
if (value === "v5pay") {
return "三方充值 · V5Pay";
}
if (value === "usdt_trc20") {
return "三方充值 · USDT";
}
if (value === "apple_recharge") {
return "苹果充值";
}
if (value === "huawei_recharge") {
return "华为充值";
}
if (value === "telegram_recharge") {
return "Telegram 充值";
}
if (value === "web_recharge") {
return "三方充值 · Web";
}
return value || "-";
}
// rechargeSourceTone 决定来源徽章与 KPI 卡片的配色分组google/thirdparty/coinseller。
export function rechargeSourceTone(rechargeType) {
if (rechargeType === "google_play_recharge") {
return "google";
}
if (
rechargeType === "coin_seller_stock_purchase" ||
rechargeType === "coin_seller_stock_deduction" ||
rechargeType === "coin_seller_transfer"
) {
return "coinseller";
}
return "thirdparty";
}
export function statusLabel(value) {
return WITHDRAWAL_STATUS.find(([status]) => status === value)?.[1] || value || "-";
}
export function coinSellerRechargeProviderLabel(value) {
return COIN_SELLER_RECHARGE_PROVIDERS.find(([code]) => code === value)?.[1] || value || "-";
}
export function coinSellerRechargeChainLabel(value) {
return COIN_SELLER_RECHARGE_CHAINS.find(([code]) => code === value)?.[1] || value || "-";
}
export function coinSellerRechargeStatusLabel(value) {
return COIN_SELLER_RECHARGE_STATUS.find(([status]) => status === value)?.[1] || value || "-";
}
export function coinSellerRechargeVerifyStatusLabel(value) {
return COIN_SELLER_RECHARGE_VERIFY_STATUS.find(([status]) => status === value)?.[1] || value || "-";
}
export function coinSellerRechargeGrantStatusLabel(value) {
return COIN_SELLER_RECHARGE_GRANT_STATUS.find(([status]) => status === value)?.[1] || value || "-";
}
export function coinSellerRechargeStatusTone(value) {
if (["granted", "verified", "success", "succeeded", "completed", "passed"].includes(stringValue(value))) {
return "success";
}
if (["failed", "rejected", "canceled", "cancelled"].includes(stringValue(value))) {
return "danger";
}
return "warning";
}
export function isCoinSellerRechargeVerified(order) {
return (
["verified", "success", "succeeded", "completed", "passed"].includes(stringValue(order?.verifyStatus)) ||
stringValue(order?.status) === "verified"
);
}
export function isCoinSellerRechargeGranted(order) {
return (
["granted", "success", "succeeded", "completed"].includes(stringValue(order?.grantStatus)) ||
stringValue(order?.status) === "granted"
);
}
export function formatAmount(value) {
const number = Number(value);
if (!Number.isFinite(number)) {
return "-";
}
return number.toLocaleString("zh-CN", { maximumFractionDigits: 2 });
}
export function formatTime(ms) {
const value = Number(ms);
if (!Number.isFinite(value) || value <= 0) {
return "-";
}
return new Intl.DateTimeFormat("zh-CN", {
day: "2-digit",
hour: "2-digit",
hourCycle: "h23",
minute: "2-digit",
month: "2-digit",
timeZone: "Asia/Shanghai",
year: "numeric",
}).format(value);
}
export function formatUsdMinor(value, currency = "USD") {
const number = Number(value);
if (!Number.isFinite(number)) {
return "-";
}
return `${currency || "USD"} ${(number / 100).toLocaleString("zh-CN", {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})}`;
}
export function formatMicroMoney(value, currency = "USD") {
const number = Number(value);
if (!Number.isFinite(number)) {
return "-";
}
return `${currency || "USD"} ${(number / 1_000_000).toLocaleString("zh-CN", {
maximumFractionDigits: 6,
minimumFractionDigits: 2,
})}`;
}
export function formatPercent(value) {
const number = Number(value);
if (!Number.isFinite(number)) {
return "";
}
const percent = number > 1 ? number : number * 100;
return `${percent.toLocaleString("zh-CN", { maximumFractionDigits: 4 })}%`;
}
function numberValue(value) {
if (value === "" || value === null || value === undefined) {
return Number.NaN;
}
return Number(value);
}
function numberOrNull(value) {
const nextValue = Number(value);
return Number.isFinite(nextValue) ? nextValue : null;
}
function providerMoneyToMinor(value) {
const number = numberValue(value);
if (!Number.isFinite(number)) {
return Number.NaN;
}
return Math.round(number * 100);
}
function providerAmountMinorFromForm(form) {
// 表单输入是常规金额,已构建 payload 重用时则直接沿用 minor避免校验请求二次放大金额。
if (stringValue(form.providerAmount) !== "") {
return providerMoneyToMinor(form.providerAmount);
}
return numberValue(form.providerAmountMinor);
}
function requiresThirdPartyProviderAmount(providerCode) {
return ["mifapay", "v5pay"].includes(stringValue(providerCode).toLowerCase());
}
function stringValue(value) {
return String(value ?? "").trim();
}