fix(rcart): graceful server-side page for paid/expired/cancelled payments
When a payment request is in a terminal state (paid, confirmed, expired, cancelled, filled), the /pay/:id route now renders a static HTML page with a clear message instead of loading the full JS component. Prevents "corrupted content error" and shows a friendly "already paid" message. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f9dc06394c
commit
55067729b1
|
|
@ -2535,6 +2535,49 @@ routes.get("/request", (c) => {
|
|||
routes.get("/pay/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const paymentId = c.req.param("id");
|
||||
|
||||
// Check payment status server-side for graceful terminal-state messages
|
||||
const docId = paymentRequestDocId(space, paymentId);
|
||||
const doc = _syncServer?.getDoc<PaymentRequestDoc>(docId);
|
||||
if (doc) {
|
||||
const p = doc.payment;
|
||||
const terminalStates: Record<string, { title: string; msg: string; icon: string }> = {
|
||||
paid: { title: 'Payment Complete', msg: 'This payment request has already been paid.', icon: '✓' },
|
||||
confirmed: { title: 'Payment Confirmed', msg: 'This payment has been confirmed on-chain.', icon: '✓' },
|
||||
expired: { title: 'Payment Expired', msg: 'This payment request has expired and is no longer accepting payments.', icon: '⏲' },
|
||||
cancelled: { title: 'Payment Cancelled', msg: 'This payment request has been cancelled by the creator.', icon: '✗' },
|
||||
filled: { title: 'Payment Limit Reached', msg: 'This payment request has reached its maximum number of payments.', icon: '✓' },
|
||||
};
|
||||
const info = terminalStates[p.status];
|
||||
if (info) {
|
||||
const chainNames: Record<number, string> = { 8453: 'Base', 84532: 'Base Sepolia', 1: 'Ethereum' };
|
||||
const explorerBase: Record<number, string> = { 8453: 'https://basescan.org/tx/', 84532: 'https://sepolia.basescan.org/tx/', 1: 'https://etherscan.io/tx/' };
|
||||
const txLink = p.txHash && explorerBase[p.chainId]
|
||||
? `<a href="${explorerBase[p.chainId]}${p.txHash}" target="_blank" rel="noopener" style="color:#60a5fa;text-decoration:underline">${p.txHash.slice(0, 10)}...${p.txHash.slice(-8)}</a>`
|
||||
: '';
|
||||
return c.html(renderShell({
|
||||
title: `${info.title} | rCart`,
|
||||
moduleId: "rcart",
|
||||
spaceSlug: space,
|
||||
spaceVisibility: "public",
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: `
|
||||
<div style="max-width:480px;margin:60px auto;padding:32px;text-align:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:#e2e8f0">
|
||||
<div style="font-size:48px;margin-bottom:16px;color:${p.status === 'paid' || p.status === 'confirmed' || p.status === 'filled' ? '#4ade80' : p.status === 'expired' ? '#fbbf24' : '#f87171'}">${info.icon}</div>
|
||||
<h1 style="font-size:24px;font-weight:600;margin:0 0 12px">${info.title}</h1>
|
||||
<p style="color:#94a3b8;font-size:15px;line-height:1.6;margin:0 0 24px">${info.msg}</p>
|
||||
${p.amount && p.amount !== '0' ? `<div style="font-size:20px;font-weight:600;margin-bottom:8px">${p.amount} ${p.token}</div>` : ''}
|
||||
${p.fiatAmount ? `<div style="color:#94a3b8;font-size:14px;margin-bottom:16px">≈ $${p.fiatAmount} ${p.fiatCurrency || 'USD'}</div>` : ''}
|
||||
${chainNames[p.chainId] ? `<div style="color:#64748b;font-size:13px;margin-bottom:8px">Network: ${chainNames[p.chainId]}</div>` : ''}
|
||||
${txLink ? `<div style="font-size:13px;margin-bottom:8px">Tx: ${txLink}</div>` : ''}
|
||||
${p.paidAt ? `<div style="color:#64748b;font-size:13px">Paid: ${new Date(p.paidAt).toLocaleString()}</div>` : ''}
|
||||
</div>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rcart/cart.css">`,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return c.html(renderShell({
|
||||
title: `Payment | rCart`,
|
||||
moduleId: "rcart",
|
||||
|
|
|
|||
Loading…
Reference in New Issue