import { useCallback, useEffect, useRef, useState } from 'react'; import QRCode from 'qrcode'; import { Coins, RefreshCw, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { AppError } from '@/lib/error-model'; import { formatWorksTokenPointValue, isWorksTokenPointBalanceExhausted } from '@/lib/works-square-token-points'; import { createWorksRechargeOrder, fetchWorksBillingSummary, fetchWorksPointHistory, openWorksBillingAccount, fetchWorksRechargeOrder, fetchWorksRechargeOrders, type WorksBillingSummary, type WorksPointHistory, type WorksRechargeOrder, } from '@/lib/works-billing'; const labels: Record = { pending: '待支付', succeeded: '已到账', failed: '支付失败', canceled: '已关闭', manual_review: '待核查', credited: '已入账', reserved: '已预占', dispatched: '处理中', pending_review: '待核对', settled: '已结算', released: '已释放', refunded: '已退款', expired: '已释放', }; const points = (value: string | null) => formatWorksTokenPointValue(value) ?? '—'; const money = (cents: number) => `¥${(cents / 100).toFixed(2)}`; const time = (value: string) => new Date(value).toLocaleString('zh-CN'); const unresolved = (order: WorksRechargeOrder) => order.status === 'pending' || order.status === 'manual_review'; export function PointWallet({ active = true }: { active?: boolean }) { const [summary, setSummary] = useState(null); const [summaryError, setSummaryError] = useState(false); const [open, setOpen] = useState(false); const [tab, setTab] = useState<'recharge' | 'orders' | 'history'>('recharge'); const [offset, setOffset] = useState(0); const [orders, setOrders] = useState([]); const [pending, setPending] = useState(null); const [history, setHistory] = useState({ items: [], has_more: false }); const [loading, setLoading] = useState(false); const [loaded, setLoaded] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [checkout, setCheckout] = useState(null); const [qr, setQr] = useState(''); const [unknownProduct, setUnknownProduct] = useState(null); const intent = useRef<{ productId: string; requestId: string } | null>(null); const alive = useRef(true); const summaryRequest = useRef(0); const pageRequest = useRef(0); const purchaseBusy = useRef(false); useEffect(() => { alive.current = true; return () => { alive.current = false; summaryRequest.current += 1; pageRequest.current += 1; }; }, []); const refreshSummary = useCallback(async () => { const request = ++summaryRequest.current; try { const next = await fetchWorksBillingSummary(); if (!alive.current || summaryRequest.current !== request) return; setSummary(next); setSummaryError(false); } catch { if (!alive.current || summaryRequest.current !== request) return; setSummary(null); setSummaryError(true); } }, []); useEffect(() => { if (active) void refreshSummary(); const focus = () => { void refreshSummary(); }; const visibility = () => { if (document.visibilityState === 'visible') focus(); }; window.addEventListener('focus', focus); document.addEventListener('visibilitychange', visibility); return () => { window.removeEventListener('focus', focus); document.removeEventListener('visibilitychange', visibility); }; }, [active, refreshSummary]); const refreshPage = useCallback(async () => { const request = ++pageRequest.current; setLoading(true); setLoaded(false); try { const latest = await fetchWorksRechargeOrders(); const page = tab === 'orders' && offset > 0 ? await fetchWorksRechargeOrders(offset) : latest; const ledger = tab === 'history' ? await fetchWorksPointHistory(offset) : null; if (!alive.current || pageRequest.current !== request) return; setOrders(page.items); setPending(latest.items.find(unresolved) ?? null); if (ledger) setHistory(ledger); setLoaded(true); } catch { if (alive.current && pageRequest.current === request) setError('记录加载失败,请刷新后再充值。'); } finally { if (alive.current && pageRequest.current === request) setLoading(false); } }, [offset, tab]); useEffect(() => { if (open) { setError(''); void refreshSummary(); void refreshPage(); } return () => { pageRequest.current += 1; }; }, [open, refreshPage, refreshSummary]); useEffect(() => { let active = true; setQr(''); if (checkout?.qr_payload) { void QRCode.toDataURL(checkout.qr_payload, { width: 224, margin: 1 }).then((value) => { if (active) setQr(value); }).catch(() => { if (active) setError('二维码生成失败,请刷新此订单。'); }); } return () => { active = false; }; }, [checkout?.qr_payload]); const checkOrder = useCallback(async (id: string) => { const order = await fetchWorksRechargeOrder(id); if (!alive.current) return; setCheckout((current) => current?.id === id ? order : current); if (!unresolved(order)) { intent.current = null; setUnknownProduct(null); void refreshSummary(); void refreshPage(); } }, [refreshPage, refreshSummary]); const checkoutId = checkout?.id; const checkoutStatus = checkout?.status; useEffect(() => { if (!open || !checkoutId || (checkoutStatus !== 'pending' && checkoutStatus !== 'manual_review')) return; let active = true; let timer: ReturnType; const poll = async () => { if (document.visibilityState === 'visible') { try { await checkOrder(checkoutId); } catch { /* Keep the frozen order visible; never resubmit payment. */ } } if (active) timer = setTimeout(() => void poll(), 4000); }; timer = setTimeout(() => void poll(), 4000); return () => { active = false; clearTimeout(timer); }; }, [open, checkoutId, checkoutStatus, checkOrder]); async function purchase(productId: string) { if (purchaseBusy.current || !loaded || !summary?.can_recharge || !summary.payment_configured || pending) return; if (intent.current && intent.current.productId !== productId) return; purchaseBusy.current = true; setBusy(true); setError(''); intent.current ??= { productId, requestId: crypto.randomUUID() }; try { const order = await createWorksRechargeOrder(productId, intent.current.requestId); if (!alive.current) return; setCheckout(order); setPending(unresolved(order) ? order : null); intent.current = null; setUnknownProduct(null); void refreshSummary(); void refreshPage(); } catch (cause) { if (!alive.current) return; const definitive = cause instanceof AppError && cause.details?.commandOutcome === 'definitive_failure'; if (definitive) { intent.current = null; setUnknownProduct(null); } else setUnknownProduct(productId); setError(definitive ? '本次充值未提交成功,请刷新后重试。' : '付款结果尚未确认。请先检查订单;重试将沿用同一次请求,请勿重复付款。'); void refreshPage(); } finally { purchaseBusy.current = false; if (alive.current) setBusy(false); } } const openWebAccount = () => { void openWorksBillingAccount().catch(() => setError('网页账户页暂时无法打开。')); }; const own = summary?.own_points; const selected = summary?.points; const usingShared = typeof selected?.shared_available === 'boolean'; return <>
我的词元点数{own ? `${points(own.total_remaining)} 点` : summaryError ? '暂时不可用' : '加载中…'}
{own &&

永久有效 · 预占 {points(own.reserved)} 点

} {usingShared &&

AI 编程使用共享点数:{selected?.shared_available ? '可用' : '已用尽,请联系付款人'}

} {summary?.funding_error &&

{summary.funding_error}

} {isWorksTokenPointBalanceExhausted(own) && !usingShared &&

{summary?.can_recharge ? '点数已用尽,充值后可继续使用。' : '点数已用尽,请联系家长。'}

} {summaryError && }
词元点数
注册赠送与充值点数永久有效。新账号注册赠送 100 点,1 元充值 50 点。

我的可用点数

{own ? points(own.total_remaining) : '—'} 点

预占 {points(own?.reserved ?? null)} 点 · 充值到账本人钱包

{summary?.funding_error &&

{summary.funding_error}

} {usingShared &&

AI 编程使用共享点数({selected?.shared_available ? '可用' : '已用尽'})。下方余额、充值与记录均属于你自己的钱包;充值不会自动切换付款来源。

}
{(['recharge', 'orders', 'history'] as const).map((value) => )}
{summaryError &&

余额读取失败,请刷新后重试。

} {error &&

{error}

} {loading &&

正在读取记录…

} {tab === 'recharge' &&
{summary && !summary.can_recharge &&

仅家长身份可以充值,请联系家长。你仍可查看自己的余额和收支。

} {summary?.can_recharge && !summary.payment_configured &&

充值暂未开放,请稍后再试。

} {pending && checkout?.id !== pending.id &&

已有{labels[pending.status]}订单:{money(pending.amount_cents)} / {points(pending.point_amount)} 点。请先完成或联系运营核查。

} {(!checkout || !unresolved(checkout)) &&
{summary?.recharge_products.map((product) => )}
} {unknownProduct && !pending && } {checkout &&

{labels[checkout.status]} · {money(checkout.amount_cents)} · {points(checkout.point_amount)} 点

订单 {checkout.id}

{qr && checkout.status === 'pending' && 充值支付二维码} {checkout.status === 'pending' && !checkout.qr_payload &&

当前支付方式未提供扫码信息,请在网页账户页继续此订单。

} {checkout.status === 'pending' &&

付款后会自动核对到账状态,关闭窗口不会取消订单。

} {checkout.status === 'manual_review' &&

付款结果需要运营核查,请勿再次付款。

}
}
} {tab === 'orders' && !loading &&
{orders.length === 0 &&

暂无充值订单。

}{orders.map((order) =>
{points(order.point_amount)} 点 · {money(order.amount_cents)}{labels[order.status]}

{time(order.created_at)}

{unresolved(order) && }
)}
} {tab === 'history' && !loading &&
{history.items.length === 0 &&

暂无收支记录。

}{history.items.map((item) =>
{item.description}{item.kind === 'credit' ? '+' : item.points.startsWith('-') ? '−' : ''}{points(item.points.replace(/^-/, ''))} 点

{time(item.created_at)} · {labels[item.status] ?? '待核对'}{item.reserved_points !== '0.00' ? ` · 预占 ${points(item.reserved_points)} 点` : ''}

)}
} {tab !== 'recharge' &&
第 {offset / 20 + 1} 页
}
; }