"use client"; import { List } from "@refinedev/antd"; import { useGetIdentity } from "@refinedev/core"; import { Alert, App, Button, Card, Col, Collapse, DatePicker, Form, Input, Row, Select, Space, Statistic, Switch, Table, Tag, Typography, type TableColumnsType, } from "antd"; import type { Dayjs } from "dayjs"; import { useCallback, useEffect, useState } from "react"; import type { AdminIdentity } from "@/lib/admin/providers"; const { Text } = Typography; type Order = { orderNo: string; userEmail: string | null; packageName: string | null; moneyCents: number; credits: number; status: string; epayTradeNo: string | null; createdAt: string; paidAt: string | null; }; type PaymentStats = { totalOrders: number; paidOrders: number; pendingOrders: number; failedExpiredOrders: number; paidAmountCents: number; grantedCredits: number; }; type PaymentFilters = { status?: string; dates?: [Dayjs, Dayjs] }; type EpaySettings = { gatewayUrl: string; pid: string; notifyUrl: string; returnUrl: string; siteName: string; chatEnabled: boolean; keyConfigured: boolean; complete: boolean; source: "database" | "environment" | "unconfigured"; }; type EpaySettingsForm = Pick & { newKey?: string }; const initialStats: PaymentStats = { totalOrders: 0, paidOrders: 0, pendingOrders: 0, failedExpiredOrders: 0, paidAmountCents: 0, grantedCredits: 0, }; const statusLabels: Record = { pending: "待支付", paid: "已支付", failed: "失败", expired: "已过期" }; const statusColors: Record = { pending: "gold", paid: "green", failed: "red", expired: "default" }; const dateFormatter = new Intl.DateTimeFormat("zh-CN", { dateStyle: "medium", timeStyle: "short", timeZone: "Asia/Shanghai" }); const pageSize = 20; function formatDate(value: string | null) { return value ? dateFormatter.format(new Date(value)) : "—"; } function formatMoney(cents: number) { return `¥${(cents / 100).toFixed(2)}`; } async function responsePayload(response: Response) { const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload.error || "请求失败"); return payload; } export default function PaymentManagement() { const { message } = App.useApp(); const { data: identity } = useGetIdentity(); const [filterForm] = Form.useForm(); const [epayForm] = Form.useForm(); const [orders, setOrders] = useState([]); const [stats, setStats] = useState(initialStats); const [paymentLoading, setPaymentLoading] = useState(true); const [paymentError, setPaymentError] = useState(""); const [filters, setFilters] = useState({}); const [offset, setOffset] = useState(0); const [total, setTotal] = useState(0); const [epaySettings, setEpaySettings] = useState(null); const [epayLoading, setEpayLoading] = useState(true); const [epaySaving, setEpaySaving] = useState(false); const [epayTesting, setEpayTesting] = useState(false); const [epayError, setEpayError] = useState(""); const canAdjustBilling = Boolean(identity?.permissions.includes("billing.adjustments.write")); const loadPayments = useCallback(async () => { setPaymentLoading(true); setPaymentError(""); const params = new URLSearchParams({ limit: String(pageSize), offset: String(offset) }); if (filters.status) params.set("status", filters.status); if (filters.dates?.[0]) params.set("from", filters.dates[0].startOf("day").toISOString()); if (filters.dates?.[1]) params.set("to", filters.dates[1].endOf("day").toISOString()); try { const payload = await responsePayload(await fetch(`/api/admin/payments?${params}`, { cache: "no-store" })); setOrders(payload.orders); setStats(payload.stats); setTotal(payload.pagination.total); } catch (error) { setPaymentError(error instanceof Error ? error.message : "读取支付记录失败"); } finally { setPaymentLoading(false); } }, [filters, offset]); const loadEpaySettings = useCallback(async () => { setEpayLoading(true); setEpayError(""); try { const payload: EpaySettings = await responsePayload(await fetch("/api/admin/epay-settings", { cache: "no-store" })); setEpaySettings(payload); epayForm.setFieldsValue({ gatewayUrl: payload.gatewayUrl, pid: payload.pid, notifyUrl: payload.notifyUrl, returnUrl: payload.returnUrl, siteName: payload.siteName, chatEnabled: payload.chatEnabled, newKey: "", }); } catch (error) { setEpayError(error instanceof Error ? error.message : "读取易支付配置失败"); } finally { setEpayLoading(false); } }, [epayForm]); useEffect(() => { const timer = window.setTimeout(() => void loadPayments(), 0); return () => window.clearTimeout(timer); }, [loadPayments]); useEffect(() => { const timer = window.setTimeout(() => void loadEpaySettings(), 0); return () => window.clearTimeout(timer); }, [loadEpaySettings]); async function testEpayAvailability() { setEpayTesting(true); try { const payload = await responsePayload(await fetch("/api/admin/epay-settings/test", { method: "POST" })); if (payload.available) message.success(`${payload.message}(${payload.status},${payload.latencyMs}ms)`); else message.error(payload.message || "当前已保存的易支付配置暂不可用"); } catch (error) { message.error(error instanceof Error ? error.message : "当前已保存的易支付配置暂不可用"); } finally { setEpayTesting(false); } } async function saveEpaySettings(values: EpaySettingsForm) { setEpaySaving(true); try { await responsePayload(await fetch("/api/admin/epay-settings", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...values, newKey: values.newKey || undefined }), })); epayForm.setFieldValue("newKey", ""); message.success("Z-Pay(易支付)配置已保存"); await loadEpaySettings(); } catch (error) { message.error(error instanceof Error ? error.message : "保存易支付配置失败"); } finally { setEpaySaving(false); } } const orderColumns: TableColumnsType = [ { title: "订单号", dataIndex: "orderNo", render: (value) => {value} }, { title: "用户邮箱", dataIndex: "userEmail", render: (value) => value || "—" }, { title: "套餐", dataIndex: "packageName", render: (value) => value || "—" }, { title: "金额", dataIndex: "moneyCents", align: "right", render: formatMoney }, { title: "点数", dataIndex: "credits", align: "right" }, { title: "状态", dataIndex: "status", render: (value) => {statusLabels[value] || value} }, { title: "易支付交易号", dataIndex: "epayTradeNo", render: (value) => value || "—" }, { title: "创建时间", dataIndex: "createdAt", render: formatDate }, { title: "支付时间", dataIndex: "paidAt", render: formatDate }, ]; return ( : null} > 配置兼容标准 Z-Pay / 易支付协议的支付网关、商户凭据、回调地址与对话页开关。 {epaySettings && 来源:{{ database: "数据库", environment: "环境变量", unconfigured: "未配置" }[epaySettings.source]} · {epaySettings.complete ? "配置完整" : "配置不完整"} · {epaySettings.keyConfigured ? "密钥已配置" : "密钥未配置"} · {epaySettings.chatEnabled ? "对话支付开放" : "对话支付关闭"}} {epayError && void loadEpaySettings()}>重试} />} form={epayForm} layout="vertical" onFinish={saveEpaySettings} requiredMark="optional"> ), }]} /> 共 {total} 条平台订单}>
{ setOffset(0); setFilters(values); }}>