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

369 lines
14 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 { 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();
}