财务模块

This commit is contained in:
zhx 2026-07-03 13:08:57 +08:00
parent 7025ae9e30
commit 88eae83dbe
8 changed files with 573 additions and 22 deletions

View File

@ -2825,6 +2825,103 @@
"x-permissions": ["payment-bill:view"]
}
},
"/admin/payment/recharge-bills/summary": {
"get": {
"operationId": "getRechargeBillSummary",
"parameters": [
{
"in": "query",
"name": "keyword",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "recharge_type",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "status",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "region_id",
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "start_at_ms",
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "end_at_ms",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
}
},
"x-permission": "payment-bill:view",
"x-permissions": ["payment-bill:view"]
}
},
"/admin/payment/recharge-bills/google-paid/refresh": {
"post": {
"operationId": "refreshGoogleRechargePaid",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"transactionIds": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["transactionIds"]
}
}
},
"required": true
},
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
}
},
"x-permission": "payment-bill:view",
"x-permissions": ["payment-bill:view"]
}
},
"/admin/payment/recharge-regions": {
"get": {
"operationId": "listRechargeRegions",
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
}
},
"x-permission": "payment-bill:view",
"x-permissions": ["payment-bill:view"]
}
},
"/admin/payment/recharge-products": {
"get": {
"operationId": "listRechargeProducts",

View File

@ -7,12 +7,15 @@ import {
approveFinanceWithdrawalApplication,
createFinanceApplication,
fetchFinanceApplicationOptions,
fetchFinanceRechargeSummary,
fetchFinanceSession,
filterOperationsByPermissions,
listFinanceApplications,
listFinanceRechargeDetails,
listFinanceRechargeRegions,
listMyFinanceApplications,
listFinanceWithdrawalApplications,
refreshFinanceGoogleRechargePaid,
rejectFinanceApplication,
rejectFinanceWithdrawalApplication
} from "./api.js";
@ -24,7 +27,7 @@ import { FinanceRechargeDetailList } from "./components/FinanceRechargeDetailLis
import { FinanceShell } from "./components/FinanceShell.jsx";
import { FinanceWithdrawalApplicationList } from "./components/FinanceWithdrawalApplicationList.jsx";
import { DEFAULT_APPLICATION_FORM } from "./constants.js";
import { buildApplicationPayload, validateApplicationPayload } from "./format.js";
import { EMPTY_RECHARGE_SUMMARY, buildApplicationPayload, validateApplicationPayload } from "./format.js";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
const emptyOptions = { apps: [], operations: [], walletIdentities: [] };
@ -35,11 +38,14 @@ export function FinanceApp() {
const [sessionState, setSessionState] = useState({ error: "", loading: true, session: null });
const [optionsState, setOptionsState] = useState({ data: emptyOptions, error: "", loading: false });
const [rechargeDetailsState, setRechargeDetailsState] = useState({ data: { items: [], page: 1, pageSize, total: 0 }, error: "", loading: false });
const [rechargeSummaryState, setRechargeSummaryState] = useState({ data: EMPTY_RECHARGE_SUMMARY, error: "", loading: false });
const [rechargeRegionsState, setRechargeRegionsState] = useState({ appCode: "", items: [], loading: false });
const [googlePaidRefreshing, setGooglePaidRefreshing] = useState(false);
const [myApplicationsState, setMyApplicationsState] = useState({ data: { items: [], page: 1, pageSize, total: 0 }, error: "", loading: false });
const [applicationsState, setApplicationsState] = useState({ data: { items: [], page: 1, pageSize, total: 0 }, error: "", loading: false });
const [withdrawalsState, setWithdrawalsState] = useState({ data: { items: [], page: 1, pageSize, total: 0 }, error: "", loading: false });
const [activeView, setActiveView] = useState("create");
const [rechargeDetailFilters, setRechargeDetailFilters] = useState({ appCode: "", keyword: "", page: 1, timeRange: { endMs: "", startMs: "" } });
const [rechargeDetailFilters, setRechargeDetailFilters] = useState({ appCode: "", keyword: "", page: 1, regionId: "", timeRange: { endMs: "", startMs: "" } });
const [myFilters, setMyFilters] = useState({ appCode: "", keyword: "", operation: "", page: 1, status: "" });
const [filters, setFilters] = useState({ appCode: "", keyword: "", operation: "", page: 1, status: "pending" });
const [withdrawalFilters, setWithdrawalFilters] = useState({ appCode: "", keyword: "", page: 1 });
@ -88,6 +94,7 @@ export function FinanceApp() {
keyword: rechargeDetailFilters.keyword,
page: rechargeDetailFilters.page,
page_size: pageSize,
region_id: rechargeDetailFilters.appCode ? rechargeDetailFilters.regionId : "",
start_at_ms: rechargeDetailFilters.timeRange.startMs,
stat_tz: "Asia/Shanghai",
status: "succeeded"
@ -165,6 +172,58 @@ export function FinanceApp() {
}
}, [optionsState.data.apps, rechargeDetailQuery, session?.canViewAppRechargeDetails]);
const loadRechargeSummary = useCallback(async () => {
if (!session?.canViewAppRechargeDetails) {
return;
}
setRechargeSummaryState((current) => ({ ...current, error: "", loading: true }));
try {
const data = await fetchFinanceRechargeSummary(rechargeDetailQuery, optionsState.data.apps);
setRechargeSummaryState({ data, error: "", loading: false });
} catch (err) {
setRechargeSummaryState((current) => ({ ...current, error: err.message || "加载充值汇总失败", loading: false }));
}
}, [optionsState.data.apps, rechargeDetailQuery, session?.canViewAppRechargeDetails]);
const rechargeAppCode = rechargeDetailFilters.appCode;
const loadRechargeRegions = useCallback(async () => {
if (!session?.canViewAppRechargeDetails || !rechargeAppCode) {
setRechargeRegionsState({ appCode: "", items: [], loading: false });
return;
}
setRechargeRegionsState((current) => ({ ...current, appCode: rechargeAppCode, loading: true }));
try {
const items = await listFinanceRechargeRegions(rechargeAppCode);
setRechargeRegionsState({ appCode: rechargeAppCode, items, loading: false });
} catch {
setRechargeRegionsState({ appCode: rechargeAppCode, items: [], loading: false });
}
}, [rechargeAppCode, session?.canViewAppRechargeDetails]);
const refreshGooglePaid = useCallback(
async (transactionIds) => {
if (!rechargeAppCode || !transactionIds?.length || googlePaidRefreshing) {
return;
}
setGooglePaidRefreshing(true);
try {
const results = await refreshFinanceGoogleRechargePaid(rechargeAppCode, transactionIds);
const failed = results.filter((item) => item.error).length;
const succeeded = results.length - failed;
showToast(
failed ? `谷歌实付同步完成:成功 ${succeeded} 笔,失败 ${failed}` : `谷歌实付同步完成:${succeeded}`,
failed ? "warning" : "success"
);
await loadRechargeDetails();
} catch (err) {
showToast(err.message || "查询谷歌实付失败", "error");
} finally {
setGooglePaidRefreshing(false);
}
},
[googlePaidRefreshing, loadRechargeDetails, rechargeAppCode, showToast]
);
const loadApplications = useCallback(async () => {
if (!session?.canAuditApplication) {
return;
@ -203,6 +262,14 @@ export function FinanceApp() {
loadRechargeDetails();
}, [loadRechargeDetails]);
useEffect(() => {
loadRechargeSummary();
}, [loadRechargeSummary]);
useEffect(() => {
loadRechargeRegions();
}, [loadRechargeRegions]);
useEffect(() => {
loadApplications();
}, [loadApplications]);
@ -330,6 +397,7 @@ export function FinanceApp() {
onRefresh={() => {
loadOptions();
loadRechargeDetails();
loadRechargeSummary();
loadMyApplications();
loadApplications();
loadWithdrawals();
@ -341,10 +409,18 @@ export function FinanceApp() {
bills={rechargeDetailsState.data}
error={rechargeDetailsState.error}
filters={rechargeDetailFilters}
googlePaidRefreshing={googlePaidRefreshing}
loading={rechargeDetailsState.loading}
options={optionsState.data}
regions={rechargeRegionsState.items}
summary={rechargeSummaryState.data}
summaryLoading={rechargeSummaryState.loading}
onFiltersChange={updateRechargeDetailFilters}
onReload={loadRechargeDetails}
onRefreshGooglePaid={refreshGooglePaid}
onReload={() => {
loadRechargeDetails();
loadRechargeSummary();
}}
/>
) : activeView === "applications" && session.canAuditApplication ? (
<FinanceApplicationList

View File

@ -2,7 +2,14 @@ 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_OPERATIONS, FINANCE_PERMISSIONS, WALLET_IDENTITIES } from "./constants.js";
import { normalizeApplicationPage, normalizeRechargeBillPage, normalizeWithdrawalApplicationPage } from "./format.js";
import {
mergeRechargeSummaries,
normalizeApplicationPage,
normalizeRechargeBillPage,
normalizeRechargeRegions,
normalizeRechargeSummary,
normalizeWithdrawalApplicationPage
} from "./format.js";
const appCodeHeader = "X-App-Code";
@ -96,6 +103,41 @@ export async function listFinanceRechargeDetails(query = {}, apps = []) {
};
}
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);
}
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 createFinanceApplication(payload) {
const endpoint = API_ENDPOINTS.createFinanceApplication;
return apiRequest(apiEndpointPath(API_OPERATIONS.createFinanceApplication), {
@ -152,6 +194,16 @@ async function currentSession() {
}
}
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), {

View File

@ -1,3 +1,4 @@
import CloudSyncOutlined from "@mui/icons-material/CloudSyncOutlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
@ -10,29 +11,48 @@ import TableRow from "@mui/material/TableRow";
import TextField from "@mui/material/TextField";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import {
EMPTY_RECHARGE_SUMMARY,
formatAmount,
formatMicroMoney,
formatTime,
formatUsdMinor,
formatPercent,
rechargeTypeLabel,
} from "../format.js";
export function FinanceRechargeDetailList({ bills, error, filters, loading, onFiltersChange, onReload, options }) {
export function FinanceRechargeDetailList({
bills,
error,
filters,
googlePaidRefreshing,
loading,
onFiltersChange,
onRefreshGooglePaid,
onReload,
options,
regions,
summary,
summaryLoading,
}) {
const items = bills.items || [];
const page = Number(bills.page || filters.page || 1);
const pageSize = Number(bills.pageSize || 50);
const total = Number(bills.total || 0);
const hasNextPage = page * pageSize < total;
const regionOptions = regions || [];
const pendingGoogleTxIds = items
.filter((item) => item.rechargeType === "google_play_recharge" && !item.paidSyncedAtMs)
.map((item) => item.transactionId)
.filter(Boolean);
return (
<section className="finance-panel finance-list">
<RechargeSummaryCards loading={summaryLoading} summary={summary} />
<div className="finance-toolbar finance-toolbar--recharge-details">
<TextField
label="APP"
select
value={filters.appCode}
onChange={(event) => onFiltersChange({ appCode: event.target.value })}
onChange={(event) => onFiltersChange({ appCode: event.target.value, regionId: "" })}
>
<MenuItem value="">全部 APP</MenuItem>
{options.apps.map((app) => (
@ -41,6 +61,21 @@ export function FinanceRechargeDetailList({ bills, error, filters, loading, onFi
</MenuItem>
))}
</TextField>
<TextField
disabled={!filters.appCode}
helperText={filters.appCode ? "" : "先选择 APP"}
label="区域"
select
value={filters.regionId}
onChange={(event) => onFiltersChange({ regionId: event.target.value })}
>
<MenuItem value="">全部区域</MenuItem>
{regionOptions.map((region) => (
<MenuItem key={region.regionId} value={String(region.regionId)}>
{region.name || region.regionCode || region.regionId}
</MenuItem>
))}
</TextField>
<TimeRangeFilter
label="充值时间 · 中国时区"
value={filters.timeRange}
@ -51,6 +86,15 @@ export function FinanceRechargeDetailList({ bills, error, filters, loading, onFi
value={filters.keyword}
onChange={(event) => onFiltersChange({ keyword: event.target.value })}
/>
<Button
disabled={googlePaidRefreshing || !filters.appCode || !pendingGoogleTxIds.length}
startIcon={<CloudSyncOutlined fontSize="small" />}
title={filters.appCode ? "查询本页谷歌订单的用户实付币种和金额" : "先选择 APP 后可查询"}
variant="outlined"
onClick={() => onRefreshGooglePaid(pendingGoogleTxIds)}
>
{googlePaidRefreshing ? "查询中…" : "查询谷歌实付"}
</Button>
<Button startIcon={<RefreshOutlined fontSize="small" />} variant="outlined" onClick={onReload}>
刷新列表
</Button>
@ -131,6 +175,32 @@ export function FinanceRechargeDetailList({ bills, error, filters, loading, onFi
);
}
function RechargeSummaryCards({ loading, summary }) {
const data = summary || EMPTY_RECHARGE_SUMMARY;
const cards = [
{ bucket: data.total, key: "total", label: "充值总和" },
{ bucket: data.thirdParty, key: "thirdParty", label: "三方充值" },
{ bucket: data.googlePlay, key: "googlePlay", label: "谷歌充值" },
];
return (
<div className="finance-summary-grid">
{cards.map(({ bucket, key, label }) => (
<div className="finance-summary-card" key={key}>
<span className="finance-summary-card__label">{label}</span>
<strong className="finance-summary-card__value">
{loading ? "…" : formatUsdMinor(bucket.usdMinorAmount, "USD")}
</strong>
<small>
{loading
? "统计中"
: `${formatAmount(bucket.billCount)} 笔 · ${formatAmount(bucket.coinAmount)} 金币`}
</small>
</div>
))}
</div>
);
}
function Stacked({ primary, secondary }) {
return (
<span className="finance-stack">
@ -176,27 +246,58 @@ function userPaidText(item) {
if (item.userPaidText) {
return item.userPaidText;
}
if (hasNumber(item.userPaidAmountMinor)) {
return formatUsdMinor(item.userPaidAmountMinor, item.currencyCode || "USD");
if (hasPositiveNumber(item.userPaidAmountMicro) && item.userPaidCurrencyCode) {
return formatMicroMoney(item.userPaidAmountMicro, item.userPaidCurrencyCode);
}
if (hasNumber(item.providerAmountMinor)) {
return formatUsdMinor(item.providerAmountMinor, item.currencyCode || "USD");
if (isGoogleBill(item) && !item.paidSyncedAtMs) {
return "未同步(点“查询谷歌实付”)";
}
if (hasNumber(item.userPaidAmountMicro)) {
return formatMicroMoney(item.userPaidAmountMicro, item.currencyCode || "USD");
if (hasPositiveNumber(item.userPaidAmountMinor)) {
return formatUsdMinor(item.userPaidAmountMinor, item.userPaidCurrencyCode || item.currencyCode || "USD");
}
return "-";
}
function taxDeductionText(item) {
const rate = formatPercent(item.taxRate);
const amount =
item.taxDeductionText ||
(hasNumber(item.taxDeductionMinor) ? formatUsdMinor(item.taxDeductionMinor, item.currencyCode || "USD") : "");
if (amount && rate) {
return `${amount} / ${rate}`;
const currency = item.userPaidCurrencyCode || item.currencyCode || "USD";
const fee = hasPositiveNumber(item.providerFeeMicro) ? item.providerFeeMicro : 0;
const tax = hasPositiveNumber(item.providerTaxMicro) ? item.providerTaxMicro : 0;
const deduction = fee + tax;
if (deduction > 0) {
const parts = [formatMicroMoney(deduction, currency)];
if (hasPositiveNumber(item.userPaidAmountMicro)) {
const percent = (deduction / item.userPaidAmountMicro) * 100;
parts.push(`${percent.toLocaleString("zh-CN", { maximumFractionDigits: 2 })}%`);
}
return (
<span className="finance-stack">
<span>{parts.join(" / ")}</span>
<small>{deductionBreakdown(fee, tax, currency)}</small>
</span>
);
}
return amount || rate || "-";
if (item.taxDeductionText) {
return item.taxDeductionText;
}
if (isGoogleBill(item) && !item.paidSyncedAtMs) {
return "未同步";
}
return "-";
}
function deductionBreakdown(fee, tax, currency) {
const parts = [];
if (fee > 0) {
parts.push(`手续费 ${formatMicroMoney(fee, currency)}`);
}
if (tax > 0) {
parts.push(`税费 ${formatMicroMoney(tax, currency)}`);
}
return parts.join(" + ");
}
function isGoogleBill(item) {
return item.rechargeType === "google_play_recharge" || item.providerCode === "google_play";
}
function hasNumber(value) {
@ -205,3 +306,7 @@ function hasNumber(value) {
}
return Number.isFinite(Number(value));
}
function hasPositiveNumber(value) {
return hasNumber(value) && Number(value) > 0;
}

View File

@ -138,7 +138,12 @@ export function normalizeRechargeBill(item = {}) {
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),
status: stringValue(item.status),
taxDeductionMinor: numberOrNull(
@ -167,6 +172,7 @@ export function normalizeRechargeBill(item = {}) {
item.providerAmountMinor ??
item.provider_amount_minor,
),
userPaidCurrencyCode: stringValue(item.userPaidCurrencyCode ?? item.user_paid_currency_code),
userPaidText: stringValue(
item.userPaidText ??
item.user_paid_text ??
@ -188,6 +194,61 @@ export function normalizeRechargeBillPage(data = {}) {
};
}
const EMPTY_RECHARGE_SUMMARY_BUCKET = { billCount: 0, coinAmount: 0, usdMinorAmount: 0 };
export const EMPTY_RECHARGE_SUMMARY = {
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 {
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 ["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 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 operationLabel(value, operations = FINANCE_OPERATIONS) {
return operations.find((item) => item.value === value)?.label || value || "-";
}
@ -199,6 +260,15 @@ export function rechargeTypeLabel(value) {
if (value === "google_play_recharge") {
return "谷歌充值";
}
if (value === "mifapay") {
return "三方充值 · MiFaPay";
}
if (value === "v5pay") {
return "三方充值 · V5Pay";
}
if (value === "usdt_trc20") {
return "三方充值 · USDT";
}
return value || "-";
}

View File

@ -173,7 +173,36 @@ a {
}
.finance-toolbar--recharge-details {
grid-template-columns: minmax(170px, 1fr) minmax(280px, 1.35fr) minmax(180px, 1fr) auto;
grid-template-columns: minmax(150px, 1fr) minmax(130px, 0.85fr) minmax(250px, 1.3fr) minmax(160px, 1fr) auto auto;
}
.finance-summary-grid {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: var(--space-4);
}
.finance-summary-card {
display: grid;
gap: 4px;
border: 1px solid var(--border);
border-radius: var(--radius-control);
background: var(--bg-card-strong);
padding: var(--space-4);
}
.finance-summary-card__label {
color: var(--text-secondary);
font-size: 13px;
}
.finance-summary-card__value {
font-size: 22px;
line-height: 1.2;
}
.finance-summary-card small {
color: var(--text-tertiary);
}
.finance-error {
@ -411,7 +440,8 @@ a {
.finance-form-grid,
.finance-evidence-grid,
.finance-toolbar {
.finance-toolbar,
.finance-summary-grid {
grid-template-columns: 1fr;
}

View File

@ -120,6 +120,7 @@ export const API_OPERATIONS = {
getInviteActivityRewardConfig: "getInviteActivityRewardConfig",
getLuckyGiftConfig: "getLuckyGiftConfig",
getLuckyGiftDrawSummary: "getLuckyGiftDrawSummary",
getRechargeBillSummary: "getRechargeBillSummary",
getRedPacket: "getRedPacket",
getRedPacketConfig: "getRedPacketConfig",
getRegion: "getRegion",
@ -193,6 +194,7 @@ export const API_OPERATIONS = {
listPrettyIds: "listPrettyIds",
listRechargeBills: "listRechargeBills",
listRechargeProducts: "listRechargeProducts",
listRechargeRegions: "listRechargeRegions",
listRedPackets: "listRedPackets",
listRegionBlocks: "listRegionBlocks",
listRegions: "listRegions",
@ -235,6 +237,7 @@ export const API_OPERATIONS = {
publishHostAgencyPolicy: "publishHostAgencyPolicy",
recyclePrettyId: "recyclePrettyId",
refresh: "refresh",
refreshGoogleRechargePaid: "refreshGoogleRechargePaid",
rejectFinanceApplication: "rejectFinanceApplication",
rejectFinanceWithdrawalApplication: "rejectFinanceWithdrawalApplication",
replaceCoinSellerSalaryRates: "replaceCoinSellerSalaryRates",
@ -1078,6 +1081,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
permission: "lucky-gift:view",
permissions: ["lucky-gift:view"]
},
getRechargeBillSummary: {
method: "GET",
operationId: API_OPERATIONS.getRechargeBillSummary,
path: "/v1/admin/payment/recharge-bills/summary",
permission: "payment-bill:view",
permissions: ["payment-bill:view"]
},
getRedPacket: {
method: "GET",
operationId: API_OPERATIONS.getRedPacket,
@ -1587,6 +1597,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
permission: "payment-product:view",
permissions: ["payment-product:view"]
},
listRechargeRegions: {
method: "GET",
operationId: API_OPERATIONS.listRechargeRegions,
path: "/v1/admin/payment/recharge-regions",
permission: "payment-bill:view",
permissions: ["payment-bill:view"]
},
listRedPackets: {
method: "GET",
operationId: API_OPERATIONS.listRedPackets,
@ -1871,6 +1888,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
operationId: API_OPERATIONS.refresh,
path: "/v1/auth/refresh"
},
refreshGoogleRechargePaid: {
method: "POST",
operationId: API_OPERATIONS.refreshGoogleRechargePaid,
path: "/v1/admin/payment/recharge-bills/google-paid/refresh",
permission: "payment-bill:view",
permissions: ["payment-bill:view"]
},
rejectFinanceApplication: {
method: "POST",
operationId: API_OPERATIONS.rejectFinanceApplication,

View File

@ -1876,6 +1876,54 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/payment/recharge-bills/summary": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getRechargeBillSummary"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/payment/recharge-bills/google-paid/refresh": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["refreshGoogleRechargePaid"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/payment/recharge-regions": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["listRechargeRegions"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/payment/recharge-products": {
parameters: {
query?: never;
@ -6735,6 +6783,55 @@ export interface operations {
200: components["responses"]["RechargeBillPageResponse"];
};
};
getRechargeBillSummary: {
parameters: {
query?: {
keyword?: string;
recharge_type?: string;
status?: string;
region_id?: number;
start_at_ms?: number;
end_at_ms?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: components["responses"]["EmptyResponse"];
};
};
refreshGoogleRechargePaid: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
transactionIds: string[];
};
};
};
responses: {
200: components["responses"]["EmptyResponse"];
};
};
listRechargeRegions: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: components["responses"]["EmptyResponse"];
};
};
listRechargeProducts: {
parameters: {
query?: never;