153 lines
7.9 KiB
JavaScript
153 lines
7.9 KiB
JavaScript
import Button from "@mui/material/Button";
|
||
import { EMPTY_RECHARGE_OVERVIEW, EMPTY_RECHARGE_SUMMARY, formatAmount, formatUsdMinor } from "../format.js";
|
||
|
||
// 渠道对账页:按结算月核对各渠道的流水与扣费。谷歌口径来自 Orders API 同步的实付明细;
|
||
// 三方与币商没有渠道侧扣费数据时如实标注,不做拍脑袋估算。
|
||
export function FinanceReconciliation({ loading, month, onMonthChange, overview, summary }) {
|
||
const data = summary || EMPTY_RECHARGE_SUMMARY;
|
||
const paid = (overview || EMPTY_RECHARGE_OVERVIEW).googlePaid;
|
||
const totalUsd = Number(data.total?.usdMinorAmount) || 0;
|
||
const feeUsd = paid.estFeeUsdMinor;
|
||
const taxUsd = paid.estTaxUsdMinor;
|
||
const netUsd = totalUsd - feeUsd - taxUsd;
|
||
|
||
return (
|
||
<div className="finance-overview">
|
||
<div className="finance-recon-toolbar">
|
||
<Button size="small" variant="outlined" onClick={() => onMonthChange(-1)}>
|
||
‹ 上月
|
||
</Button>
|
||
<span className="finance-recon-toolbar__month">{month.label} 结算期</span>
|
||
<Button disabled={month.isCurrent} size="small" variant="outlined" onClick={() => onMonthChange(1)}>
|
||
下月 ›
|
||
</Button>
|
||
<span className="finance-hint" style={{ marginLeft: "auto" }}>
|
||
对账口径:平台流水 × 谷歌 Orders API 实付 · 中国时区
|
||
</span>
|
||
</div>
|
||
|
||
<section className="finance-card">
|
||
<div className="finance-card__head">
|
||
<span className="finance-card__title">
|
||
结算瀑布<small>全渠道 · USD{paid.unsyncedCount > 0 ? " · 谷歌扣费为已同步部分估算" : ""}</small>
|
||
</span>
|
||
</div>
|
||
<div className="finance-card__body">
|
||
{loading ? (
|
||
<p className="finance-hint">统计中…</p>
|
||
) : (
|
||
<div className="finance-wfall">
|
||
<WaterfallRow amount={totalUsd} color="var(--fin-ink)" label="充值流水" max={totalUsd} />
|
||
<WaterfallRow amount={-feeUsd} color="#E19B3C" label="渠道手续费(估)" max={totalUsd} />
|
||
<WaterfallRow amount={-taxUsd} color="#C86A6A" label="代缴税费(估)" max={totalUsd} />
|
||
<WaterfallRow amount={netUsd} color="var(--fin-pos)" label="净入账(估)" max={totalUsd} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
|
||
<div className="finance-recon-grid">
|
||
<section className="finance-card">
|
||
<div className="finance-card__head">
|
||
<span className="finance-badge finance-badge--google">谷歌</span>
|
||
{paid.unsyncedCount > 0 ? (
|
||
<span className="finance-pill finance-pill--warn">
|
||
<i />
|
||
{formatAmount(paid.unsyncedCount)} 笔待同步
|
||
</span>
|
||
) : (
|
||
<span className="finance-pill finance-pill--ok">
|
||
<i />
|
||
实付已全量同步
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div className="finance-card__body">
|
||
<ReconLine
|
||
label="平台流水"
|
||
value={formatUsdMinor(data.googlePlay?.usdMinorAmount || 0, "USD")}
|
||
/>
|
||
<ReconLine
|
||
label={`已同步实付覆盖(${formatAmount(paid.syncedCount)}/${formatAmount(paid.googleBillCount)} 笔)`}
|
||
value={formatUsdMinor(paid.coveredUsdMinor, "USD")}
|
||
/>
|
||
<ReconLine
|
||
label="谷歌服务费(估)"
|
||
negative
|
||
value={`− ${formatUsdMinor(paid.estFeeUsdMinor, "USD")}`}
|
||
/>
|
||
<ReconLine
|
||
label="代缴税费(估)"
|
||
negative
|
||
value={`− ${formatUsdMinor(paid.estTaxUsdMinor, "USD")}`}
|
||
/>
|
||
<ReconLine label="预计净入账" total value={formatUsdMinor(paid.estNetUsdMinor, "USD")} />
|
||
<p className="finance-hint" style={{ marginTop: 10 }}>
|
||
与 Play Console 财务报告按月核对;未同步订单可在流水页批量补数
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="finance-card">
|
||
<div className="finance-card__head">
|
||
<span className="finance-badge finance-badge--thirdparty">三方</span>
|
||
</div>
|
||
<div className="finance-card__body">
|
||
<ReconLine
|
||
label="三方流水"
|
||
value={formatUsdMinor(data.thirdParty?.usdMinorAmount || 0, "USD")}
|
||
/>
|
||
<ReconLine label="笔数" value={`${formatAmount(data.thirdParty?.billCount || 0)} 笔`} />
|
||
<ReconLine label="渠道扣费" value="V5Pay 逐笔回传 / MiFaPay 未提供" />
|
||
<p className="finance-hint" style={{ marginTop: 10 }}>
|
||
V5Pay 的 fee/tax 已逐笔展示在流水明细;MiFaPay 仅提供毛额,需用渠道后台月账单人工核对
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="finance-card">
|
||
<div className="finance-card__head">
|
||
<span className="finance-badge finance-badge--coinseller">币商</span>
|
||
</div>
|
||
<div className="finance-card__body">
|
||
<ReconLine
|
||
label="净进货(进货 − 冲回)"
|
||
value={formatUsdMinor(data.coinSeller?.usdMinorAmount || 0, "USD")}
|
||
/>
|
||
<ReconLine label="记录数" value={`${formatAmount(data.coinSeller?.billCount || 0)} 笔`} />
|
||
<ReconLine label="金币入库" value={`${formatAmount(data.coinSeller?.coinAmount || 0)} 金币`} />
|
||
<p className="finance-hint" style={{ marginTop: 10 }}>
|
||
打款凭证与 TRC20 交易号逐笔登记在流水明细,可按凭证号搜索核对
|
||
</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function WaterfallRow({ amount, color, label, max }) {
|
||
const width = max > 0 ? (Math.abs(amount) / max) * 100 : 0;
|
||
const negative = amount < 0;
|
||
return (
|
||
<div className="finance-wfall__row">
|
||
<span className="finance-wfall__label">{label}</span>
|
||
<span className="finance-wfall__track">
|
||
<i style={{ background: color, width: `${Math.min(100, width)}%` }} />
|
||
</span>
|
||
<span className={negative ? "finance-wfall__val num finance-amount--negative" : "finance-wfall__val num"}>
|
||
{negative ? `− ${formatUsdMinor(-amount, "USD")}` : formatUsdMinor(amount, "USD")}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function ReconLine({ label, negative, total, value }) {
|
||
return (
|
||
<div className={["finance-recon-line", total ? "finance-recon-line--total" : ""].filter(Boolean).join(" ")}>
|
||
<span>{label}</span>
|
||
<b className={negative ? "num finance-amount--negative" : "num"}>{value}</b>
|
||
</div>
|
||
);
|
||
}
|