feat(rcart): persist payment request ID in URL for reloadability

After generating a payment request, push ?id=<paymentId> into the URL.
On page load, if ?id= is present, fetch the payment from the API and
display the QR/share view directly. Reset clears the URL param.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-11 20:14:10 -07:00
parent 5d019566f3
commit 71b2acd47b
1 changed files with 64 additions and 2 deletions

View File

@ -30,6 +30,7 @@ class FolkPaymentRequest extends HTMLElement {
private enabledMethods = { card: true, wallet: true, encryptid: true };
// Result state
private loading = false;
private generating = false;
private generatedPayment: any = null;
private qrDataUrl = '';
@ -50,7 +51,14 @@ class FolkPaymentRequest extends HTMLElement {
connectedCallback() {
this.space = this.getAttribute('space') || 'default';
this.checkExistingSession();
this.render();
// Restore payment from URL if present (e.g. ?id=abc-123)
const urlId = new URLSearchParams(window.location.search).get('id');
if (urlId) {
this.loadExistingPayment(urlId);
} else {
this.render();
}
}
private getApiBase(): string {
@ -134,6 +142,48 @@ class FolkPaymentRequest extends HTMLElement {
this.render();
}
// ── Load existing payment from URL ──
private async loadExistingPayment(paymentId: string) {
this.loading = true;
this.render();
try {
const res = await fetch(`${this.getApiBase()}/api/payments/${paymentId}`);
if (!res.ok) throw new Error('Payment not found');
const data = await res.json();
this.generatedPayment = data;
this.description = data.description || '';
this.amount = data.amount || '';
this.amountEditable = data.amountEditable || false;
this.token = data.token || 'USDC';
this.chainId = data.chainId || 8453;
this.paymentType = data.paymentType || 'single';
this.maxPayments = data.maxPayments || 0;
this.enabledMethods = data.enabledMethods || { card: true, wallet: true, encryptid: true };
this.authenticated = true; // skip auth since we're viewing
// Build URLs
const host = window.location.origin;
this.payUrl = `${host}/${this.space}/rcart/pay/${paymentId}`;
this.qrSvgUrl = `${host}/${this.space}/rcart/api/payments/${paymentId}/qr`;
// Generate client-side QR
try {
const QRCode = await import('qrcode');
this.qrDataUrl = await QRCode.toDataURL(this.payUrl, {
margin: 2, width: 280,
color: { dark: '#1e1b4b', light: '#ffffff' },
});
} catch { /* QR generation optional */ }
} catch (e) {
this.authError = e instanceof Error ? e.message : String(e);
}
this.loading = false;
this.render();
}
// ── Generate payment request ──
private async generatePayment() {
@ -188,6 +238,11 @@ class FolkPaymentRequest extends HTMLElement {
color: { dark: '#1e1b4b', light: '#ffffff' },
});
} catch { /* QR generation optional */ }
// Update URL so page can be reloaded
const newUrl = new URL(window.location.href);
newUrl.searchParams.set('id', this.generatedPayment.id);
history.pushState(null, '', newUrl.toString());
} catch (e) {
this.authError = e instanceof Error ? e.message : String(e);
}
@ -206,6 +261,12 @@ class FolkPaymentRequest extends HTMLElement {
this.paymentType = 'single';
this.maxPayments = 0;
this.enabledMethods = { card: true, wallet: true, encryptid: true };
// Clear URL param
const newUrl = new URL(window.location.href);
newUrl.searchParams.delete('id');
history.pushState(null, '', newUrl.toString());
this.render();
}
@ -218,7 +279,8 @@ class FolkPaymentRequest extends HTMLElement {
<h1 class="page-title">Request Payment</h1>
<p class="page-subtitle">Generate a QR code anyone can scan to pay you</p>
${this.generatedPayment ? this.renderResult() :
${this.loading ? '<p style="text-align:center;color:var(--rs-text-secondary)">Loading payment...</p>' :
this.generatedPayment ? this.renderResult() :
!this.authenticated ? this.renderAuthStep() :
this.renderForm()}
</div>`;