Implement ops operational closure
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
PaymentRuntimePayload,
|
||||
PaymentIncident,
|
||||
PaymentRecord,
|
||||
RefundCase,
|
||||
} from "@/types/ops";
|
||||
|
||||
const PaymentIncidentPieChart = dynamic(
|
||||
@@ -60,6 +61,8 @@ function paymentReasonLabel(reason?: string) {
|
||||
if (key === "expired") return "订单已过期";
|
||||
if (key === "event_mismatch") return "支付事件不匹配";
|
||||
if (key === "direct_transfer_mismatch") return "直接转账不匹配";
|
||||
if (key === "refund_required") return "需要退款处理";
|
||||
if (key === "duplicate_payment") return "重复付款";
|
||||
if (key === "unknown") return "未知原因";
|
||||
return key || "未知原因";
|
||||
}
|
||||
@@ -96,21 +99,24 @@ export function PaymentsPageClient() {
|
||||
const [runtime, setRuntime] = useState<PaymentRuntimePayload | null>(null);
|
||||
const [incidents, setIncidents] = useState<PaymentIncident[]>([]);
|
||||
const [payments, setPayments] = useState<PaymentRecord[]>([]);
|
||||
const [refunds, setRefunds] = useState<RefundCase[]>([]);
|
||||
const [risk, setRisk] = useState<BillingRiskPayload | null>(null);
|
||||
const [resolving, setResolving] = useState<Set<number>>(new Set());
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [rt, inc, pay, riskPayload] = await Promise.all([
|
||||
const [rt, inc, pay, refundPayload, riskPayload] = await Promise.all([
|
||||
opsApi.paymentRuntime() as Promise<PaymentRuntimePayload>,
|
||||
opsApi.incidents(50),
|
||||
opsApi.listPayments(50),
|
||||
opsApi.refunds(50),
|
||||
opsApi.billingRisk(30, 80) as Promise<BillingRiskPayload>,
|
||||
]);
|
||||
setRuntime(rt);
|
||||
setIncidents((inc as unknown as { incidents?: PaymentIncident[] }).incidents ?? []);
|
||||
setPayments((pay as unknown as { payments?: PaymentRecord[] }).payments ?? []);
|
||||
setRefunds((refundPayload as unknown as { refunds?: RefundCase[] }).refunds ?? []);
|
||||
setRisk(riskPayload);
|
||||
} catch { /* */ }
|
||||
setLoading(false);
|
||||
@@ -328,6 +334,11 @@ export function PaymentsPageClient() {
|
||||
<div className="mt-0.5 max-w-xl truncate text-xs text-slate-500" title={inc.detail || inc.reason || ""}>
|
||||
{inc.detail || inc.reason || "—"}
|
||||
</div>
|
||||
{inc.refund_case_id ? (
|
||||
<div className="mt-1 text-[11px] font-semibold text-blue-700">
|
||||
退款工单 #{inc.refund_case_id} · {inc.refund_status || "open"}
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="py-2 pr-4 text-xs text-slate-500">
|
||||
<div className="font-mono" title={inc.user_id || ""}>{compactMono(inc.user_id)}</div>
|
||||
@@ -355,6 +366,54 @@ export function PaymentsPageClient() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader><CardTitle>退款工单 ({refunds.length})</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
{refunds.length === 0 ? (
|
||||
<span className="text-sm text-slate-500">暂无退款或售后工单</span>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10 text-left text-slate-400">
|
||||
<th className="py-2 pr-4 font-medium">ID</th>
|
||||
<th className="py-2 pr-4 font-medium">状态 / 原因</th>
|
||||
<th className="py-2 pr-4 font-medium">用户 / Intent</th>
|
||||
<th className="py-2 pr-4 font-medium">Tx Hash</th>
|
||||
<th className="py-2 pr-4 font-medium">处理人</th>
|
||||
<th className="py-2 pr-4 font-medium">更新时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{refunds.map((refund) => (
|
||||
<tr key={refund.id} className="border-b border-white/5">
|
||||
<td className="py-2 pr-4 font-mono text-xs text-slate-500">{refund.id}</td>
|
||||
<td className="py-2 pr-4">
|
||||
<div className="font-bold text-slate-900">{refund.status || "open"}</div>
|
||||
<div className="text-xs text-amber-600">{paymentReasonLabel(refund.reason)}</div>
|
||||
</td>
|
||||
<td className="py-2 pr-4 text-xs text-slate-500">
|
||||
<div className="font-mono" title={refund.user_id || ""}>{compactMono(refund.user_id)}</div>
|
||||
<div className="mt-0.5 font-mono text-blue-700" title={refund.intent_id || ""}>{compactMono(refund.intent_id, 12, 6)}</div>
|
||||
</td>
|
||||
<td className="py-2 pr-4 font-mono text-xs text-slate-500" title={refund.tx_hash || ""}>
|
||||
{compactMono(refund.tx_hash)}
|
||||
</td>
|
||||
<td className="py-2 pr-4 text-xs text-slate-500">
|
||||
{refund.handled_by || refund.created_by || "—"}
|
||||
</td>
|
||||
<td className="py-2 pr-4 whitespace-nowrap text-xs text-slate-500">
|
||||
{compactDate(refund.updated_at || refund.created_at)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader><CardTitle>成功支付记录 ({payments.length})</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
|
||||
Reference in New Issue
Block a user