import { getMe, refreshSession } from "@/features/auth/api"; import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; import { apiRequest, getAccessToken } from "@/shared/api/request"; import { FINANCE_PERMISSIONS } from "./constants.js"; import { mergeRechargeOverviews, mergeRechargeSummaries, normalizeCoinSellerRechargeOrder, normalizeCoinSellerRechargeOrderPage, normalizeRechargeBillPage, normalizeRechargeOverview, normalizeRechargeRegions, normalizeRechargeSummary, normalizeWithdrawalApplicationPage } from "./format.js"; const appCodeHeader = "X-App-Code"; export async function fetchFinanceSession() { const session = await currentSession(); const permissions = Array.isArray(session.permissions) ? session.permissions : []; const canAuditWithdrawalApplications = permissions.includes(FINANCE_PERMISSIONS.auditWithdrawalApplications); const canViewWithdrawalApplications = permissions.includes(FINANCE_PERMISSIONS.viewWithdrawalApplications) || canAuditWithdrawalApplications; const canViewAppRechargeDetails = permissions.includes(FINANCE_PERMISSIONS.viewAppRechargeDetails); const canViewCoinSellerRechargeOrders = permissions.includes(FINANCE_PERMISSIONS.viewCoinSellerRechargeOrders); const canCreateCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.createCoinSellerRechargeOrder); const canVerifyCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.verifyCoinSellerRechargeOrder); const canGrantCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.grantCoinSellerRechargeOrder); const canConfigureCoinSellerRechargeExchangeRate = permissions.includes(FINANCE_PERMISSIONS.configureCoinSellerRechargeExchangeRate); return { ...session, canAccessFinance: canViewWithdrawalApplications || canViewAppRechargeDetails || canViewCoinSellerRechargeOrders, canAuditWithdrawalApplications, canConfigureCoinSellerRechargeExchangeRate, canCreateCoinSellerRechargeOrder, canGrantCoinSellerRechargeOrder, canVerifyCoinSellerRechargeOrder, canViewAppRechargeDetails, canViewCoinSellerRechargeOrders, canViewWithdrawalApplications, permissions }; } export async function fetchFinanceOptions() { const endpoint = API_ENDPOINTS.listApps; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listApps), { method: endpoint.method }); return normalizeOptions({ apps: data }); } // 充值详情的 App 目录与其他财务页不同:包含 Yumi 这类 legacy 外部账单源,只用于账单查询。 export async function fetchFinanceRechargeApps() { const endpoint = API_ENDPOINTS.listRechargeApps; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeApps), { method: endpoint.method }); return normalizeApps(data); } export async function listFinanceWithdrawalApplications(query = {}) { const endpoint = API_ENDPOINTS.listFinanceWithdrawalApplications; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listFinanceWithdrawalApplications), { method: endpoint.method, query }); return normalizeWithdrawalApplicationPage(data); } export async function listFinanceCoinSellerRechargeOrders(query = {}) { const endpoint = API_ENDPOINTS.listFinanceCoinSellerRechargeOrders; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listFinanceCoinSellerRechargeOrders), { method: endpoint.method, query }); return normalizeCoinSellerRechargeOrderPage(data); } export async function createFinanceCoinSellerRechargeOrder(payload) { const endpoint = API_ENDPOINTS.createFinanceCoinSellerRechargeOrder; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.createFinanceCoinSellerRechargeOrder), { body: payload, method: endpoint.method }); return normalizeCoinSellerRechargeOrder(data); } export async function quoteFinanceCoinSellerRechargeOrder(payload) { const endpoint = API_ENDPOINTS.quoteFinanceCoinSellerRechargeOrder; return apiRequest(apiEndpointPath(API_OPERATIONS.quoteFinanceCoinSellerRechargeOrder), { body: cleanPayload(payload), method: endpoint.method }); } export async function getFinanceCoinSellerRechargeExchangeRate(appCode) { const endpoint = API_ENDPOINTS.getFinanceCoinSellerRechargeExchangeRate; return apiRequest(apiEndpointPath(API_OPERATIONS.getFinanceCoinSellerRechargeExchangeRate, { app_code: appCode }), { method: endpoint.method }); } export async function replaceFinanceCoinSellerRechargeExchangeRate(appCode, payload) { const endpoint = API_ENDPOINTS.replaceFinanceCoinSellerRechargeExchangeRate; return apiRequest(apiEndpointPath(API_OPERATIONS.replaceFinanceCoinSellerRechargeExchangeRate, { app_code: appCode }), { body: cleanPayload(payload), method: endpoint.method }); } export async function verifyFinanceCoinSellerRechargeReceipt(payload) { const endpoint = API_ENDPOINTS.verifyFinanceCoinSellerRechargeReceipt; return apiRequest(apiEndpointPath(API_OPERATIONS.verifyFinanceCoinSellerRechargeReceipt), { body: cleanPayload(payload), method: endpoint.method }); } export async function verifyFinanceCoinSellerRechargeOrder(orderId) { const endpoint = API_ENDPOINTS.verifyFinanceCoinSellerRechargeOrder; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.verifyFinanceCoinSellerRechargeOrder, { order_id: orderId }), { method: endpoint.method }); return normalizeCoinSellerRechargeOrder(data); } export async function grantFinanceCoinSellerRechargeOrder(orderId) { const endpoint = API_ENDPOINTS.grantFinanceCoinSellerRechargeOrder; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.grantFinanceCoinSellerRechargeOrder, { order_id: orderId }), { method: endpoint.method }); return normalizeCoinSellerRechargeOrder(data); } export async function listFinanceRechargeDetails(query = {}, apps = []) { const page = positiveInteger(query.page, 1); const pageSize = positiveInteger(query.page_size ?? query.pageSize, 50); const selectedAppCode = stringValue(query.app_code ?? query.appCode); const cleanQuery = cleanRechargeBillQuery(query); if (selectedAppCode) { const data = await listRechargeBillsForApp(selectedAppCode, { ...cleanQuery, page, page_size: pageSize }); return { ...data, page, pageSize }; } const appCodes = normalizeApps(apps).map((app) => app.appCode); if (!appCodes.length) { return { items: [], page, pageSize, total: 0 }; } const mergedPageSize = page * pageSize; const pageResults = await Promise.allSettled( appCodes.map((appCode) => listRechargeBillsForApp(appCode, { ...cleanQuery, page: 1, page_size: mergedPageSize })) ); const pages = pageResults.filter((result) => result.status === "fulfilled").map((result) => result.value); const items = pages .flatMap((data) => data.items || []) .sort((left, right) => (right.createdAtMs || 0) - (left.createdAtMs || 0) || String(right.transactionId).localeCompare(String(left.transactionId))); const start = (page - 1) * pageSize; return { items: items.slice(start, start + pageSize), page, pageSize, total: pages.reduce((sum, data) => sum + Number(data.total || 0), 0) }; } export async function fetchFinanceRechargeSummary(query = {}, apps = []) { const selectedAppCode = stringValue(query.app_code ?? query.appCode); const cleanQuery = cleanRechargeBillQuery(query); if (selectedAppCode) { return getRechargeSummaryForApp(selectedAppCode, cleanQuery); } const appCodes = normalizeApps(apps).map((app) => app.appCode); if (!appCodes.length) { return mergeRechargeSummaries([]); } const summaries = await Promise.all(appCodes.map((appCode) => getRechargeSummaryForApp(appCode, cleanQuery))); return mergeRechargeSummaries(summaries); } export async function listFinanceRechargeRegions(appCode) { const endpoint = API_ENDPOINTS.listRechargeRegions; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeRegions), { headers: { [appCodeHeader]: appCode }, method: endpoint.method }); return normalizeRechargeRegions(data); } // fetchFinanceRechargeSummaries 返回逐 App 的汇总(App 对比表用)和全局合并值。 export async function fetchFinanceRechargeSummaries(query = {}, apps = []) { const selectedAppCode = stringValue(query.app_code ?? query.appCode); const cleanQuery = cleanRechargeBillQuery(query); const appList = selectedAppCode ? normalizeApps(apps).filter((app) => app.appCode === selectedAppCode) : normalizeApps(apps); if (!appList.length && selectedAppCode) { appList.push({ appCode: selectedAppCode, appName: selectedAppCode }); } // 单个 App 查询失败(如后端灰度中)按空汇总降级,不拖垮其余 App 的数据。 const results = await Promise.allSettled(appList.map((app) => getRechargeSummaryForApp(app.appCode, cleanQuery))); const summaries = results.map((result) => result.status === "fulfilled" ? result.value : normalizeRechargeSummary({}) ); const perApp = appList.map((app, index) => ({ ...app, summary: summaries[index] })); return { merged: mergeRechargeSummaries(summaries), perApp }; } export async function fetchFinanceRechargeOverview(query = {}, apps = []) { const selectedAppCode = stringValue(query.app_code ?? query.appCode); const cleanQuery = { ...cleanRechargeBillQuery(query), tz_offset_minutes: 480 }; if (selectedAppCode) { return getRechargeOverviewForApp(selectedAppCode, cleanQuery); } const appCodes = normalizeApps(apps).map((app) => app.appCode); if (!appCodes.length) { return normalizeRechargeOverview({}); } const results = await Promise.allSettled(appCodes.map((appCode) => getRechargeOverviewForApp(appCode, cleanQuery))); return mergeRechargeOverviews( results.filter((result) => result.status === "fulfilled").map((result) => result.value) ); } export async function exportFinanceRechargeBills(appCode, query = {}) { const endpoint = API_ENDPOINTS.exportRechargeBills; return apiRequest(apiEndpointPath(API_OPERATIONS.exportRechargeBills), { headers: { [appCodeHeader]: appCode }, method: endpoint.method, query: cleanRechargeBillQuery(query), raw: true }); } export async function refreshFinanceGoogleRechargePaid(appCode, transactionIds) { const endpoint = API_ENDPOINTS.refreshGoogleRechargePaid; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.refreshGoogleRechargePaid), { body: { transactionIds }, headers: { [appCodeHeader]: appCode }, method: endpoint.method }); return Array.isArray(data?.items) ? data.items : []; } export async function approveFinanceWithdrawalApplication(applicationId, payload) { const endpoint = API_ENDPOINTS.approveFinanceWithdrawalApplication; return apiRequest(apiEndpointPath(API_OPERATIONS.approveFinanceWithdrawalApplication, { application_id: applicationId }), { body: payload, method: endpoint.method }); } export async function rejectFinanceWithdrawalApplication(applicationId, payload) { const endpoint = API_ENDPOINTS.rejectFinanceWithdrawalApplication; return apiRequest(apiEndpointPath(API_OPERATIONS.rejectFinanceWithdrawalApplication, { application_id: applicationId }), { body: payload, method: endpoint.method }); } async function currentSession() { try { return await refreshSession(); } catch { if (getAccessToken()) { return getMe(); } throw new Error("登录已失效"); } } async function getRechargeOverviewForApp(appCode, query = {}) { const endpoint = API_ENDPOINTS.getRechargeBillOverview; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.getRechargeBillOverview), { headers: { [appCodeHeader]: appCode }, method: endpoint.method, query }); return normalizeRechargeOverview(data); } async function getRechargeSummaryForApp(appCode, query = {}) { const endpoint = API_ENDPOINTS.getRechargeBillSummary; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.getRechargeBillSummary), { headers: { [appCodeHeader]: appCode }, method: endpoint.method, query }); return normalizeRechargeSummary(data); } async function listRechargeBillsForApp(appCode, query = {}) { const endpoint = API_ENDPOINTS.listRechargeBills; const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeBills), { headers: { [appCodeHeader]: appCode }, method: endpoint.method, query }); return normalizeRechargeBillPage(data); } function normalizeOptions(data = {}) { return { apps: normalizeApps(data.apps) }; } function normalizeApps(data) { return listItems(data) .map((item) => { const appCode = stringValue(item.appCode ?? item.app_code ?? item.code); if (!appCode) { return null; } return { appCode, appName: stringValue(item.appName ?? item.app_name ?? item.name) || appCode, logoUrl: stringValue(item.logoUrl ?? item.logo_url) }; }) .filter(Boolean); } function listItems(data) { if (Array.isArray(data)) { return data; } if (Array.isArray(data?.items)) { return data.items; } return []; } function cleanRechargeBillQuery(query = {}) { const { app_code: _appCode, appCode: _camelAppCode, page: _page, page_size: _pageSize, pageSize: _camelPageSize, ...rest } = query; return rest; } function cleanPayload(payload = {}) { return Object.fromEntries( Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== "") ); } function positiveInteger(value, fallback) { const number = Number(value); return Number.isInteger(number) && number > 0 ? number : fallback; } function stringValue(value) { return String(value ?? "").trim(); }