feat(rcart): add payment context, staging banner, and Transak amount pre-fill
- Payment page now shows recipient info and available payment methods - Testnet chains show staging environment disclaimer - Transak widget receives effective amount for editable-amount payments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
12f25edaf3
commit
d6be2f2039
|
|
@ -140,10 +140,11 @@ class FolkPaymentPage extends HTMLElement {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const effectiveAmount = this.getEffectiveAmount();
|
||||||
const res = await fetch(`${this.getApiBase()}/api/payments/${this.paymentId}/transak-session`, {
|
const res = await fetch(`${this.getApiBase()}/api/payments/${this.paymentId}/transak-session`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email: this.cardEmail }),
|
body: JSON.stringify({ email: this.cardEmail, ...(effectiveAmount ? { amount: effectiveAmount } : {}) }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) throw new Error(data.error || 'Failed to create session');
|
if (!res.ok) throw new Error(data.error || 'Failed to create session');
|
||||||
|
|
@ -391,16 +392,38 @@ class FolkPaymentPage extends HTMLElement {
|
||||||
|
|
||||||
const chainNames: Record<number, string> = { 8453: 'Base', 84532: 'Base Sepolia', 1: 'Ethereum' };
|
const chainNames: Record<number, string> = { 8453: 'Base', 84532: 'Base Sepolia', 1: 'Ethereum' };
|
||||||
const chainName = chainNames[p.chainId] || `Chain ${p.chainId}`;
|
const chainName = chainNames[p.chainId] || `Chain ${p.chainId}`;
|
||||||
|
const isTestnet = p.chainId === 84532;
|
||||||
|
|
||||||
const showAmountInput = p.amountEditable && p.status === 'pending';
|
const showAmountInput = p.amountEditable && p.status === 'pending';
|
||||||
const displayAmount = (!p.amount || p.amount === '0') && p.amountEditable ? 'Any amount' : `${p.amount} ${p.token}`;
|
const displayAmount = (!p.amount || p.amount === '0') && p.amountEditable ? 'Any amount' : `${p.amount} ${p.token}`;
|
||||||
|
|
||||||
|
// Derive a short recipient label from creatorDid
|
||||||
|
const recipientLabel = p.creatorDid
|
||||||
|
? (p.creatorDid.startsWith('did:') ? p.creatorDid.split(':').pop()?.slice(0, 12) + '...' : p.creatorDid.slice(0, 16) + '...')
|
||||||
|
: p.recipientAddress.slice(0, 8) + '...' + p.recipientAddress.slice(-6);
|
||||||
|
|
||||||
|
// Build available methods description
|
||||||
|
const methods = p.enabledMethods || { card: true, wallet: true, encryptid: true };
|
||||||
|
const methodLabels: string[] = [];
|
||||||
|
if (methods.card) methodLabels.push('Credit Card (via Transak)');
|
||||||
|
if (methods.wallet) methodLabels.push('External Wallet (MetaMask, etc.)');
|
||||||
|
if (methods.encryptid) methodLabels.push('EncryptID Passkey');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
${isTestnet ? `<div class="staging-banner">
|
||||||
|
This is a staging environment for testing credit card to stablecoin and asset-backed CRDT token transactions. Your credit card will not be charged.
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 class="title">Payment Request</h1>
|
<h1 class="title">Payment Request</h1>
|
||||||
<div class="status-badge status-${p.status}">${p.status}</div>
|
<div class="status-badge status-${p.status}">${p.status}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="recipient-info">
|
||||||
|
You are sending a payment to <strong>${this.esc(recipientLabel)}</strong>${p.description ? ` for <strong>${this.esc(p.description)}</strong>` : ''}.
|
||||||
|
${methodLabels.length > 0 ? `You can pay by: ${methodLabels.join(', ')}.` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="amount-display">
|
<div class="amount-display">
|
||||||
${showAmountInput ? `
|
${showAmountInput ? `
|
||||||
<div class="editable-amount">
|
<div class="editable-amount">
|
||||||
|
|
@ -417,7 +440,7 @@ class FolkPaymentPage extends HTMLElement {
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="description">${this.esc(p.description)}</div>
|
${p.description ? `<div class="description">${this.esc(p.description)}</div>` : ''}
|
||||||
|
|
||||||
${p.paymentType === 'subscription' ? '<div class="type-badge">Subscription</div>' : ''}
|
${p.paymentType === 'subscription' ? '<div class="type-badge">Subscription</div>' : ''}
|
||||||
${p.paymentType === 'payer_choice' && p.status === 'pending' ? `
|
${p.paymentType === 'payer_choice' && p.status === 'pending' ? `
|
||||||
|
|
@ -633,6 +656,10 @@ class FolkPaymentPage extends HTMLElement {
|
||||||
|
|
||||||
.payment-page { }
|
.payment-page { }
|
||||||
|
|
||||||
|
.staging-banner { background: rgba(251,191,36,0.12); border: 1px solid rgba(251,191,36,0.3); color: #fbbf24; border-radius: 8px; padding: 0.75rem 1rem; font-size: 0.8125rem; line-height: 1.5; margin-bottom: 1rem; text-align: center; }
|
||||||
|
.recipient-info { color: var(--rs-text-secondary); font-size: 0.875rem; line-height: 1.6; margin-bottom: 1.25rem; padding: 0.75rem 1rem; background: var(--rs-bg-surface); border-radius: 8px; border: 1px solid var(--rs-border); }
|
||||||
|
.recipient-info strong { color: var(--rs-text-primary); }
|
||||||
|
|
||||||
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
||||||
.title { color: var(--rs-text-primary); font-size: 1.25rem; font-weight: 700; margin: 0; }
|
.title { color: var(--rs-text-primary); font-size: 1.25rem; font-weight: 700; margin: 0; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1389,7 +1389,7 @@ routes.post("/api/payments/:id/transak-session", async (c) => {
|
||||||
if (p.status !== 'pending') return c.json({ error: "Payment is no longer pending" }, 400);
|
if (p.status !== 'pending') return c.json({ error: "Payment is no longer pending" }, 400);
|
||||||
if (p.enabledMethods && !p.enabledMethods.card) return c.json({ error: "Card payments are not enabled for this request" }, 400);
|
if (p.enabledMethods && !p.enabledMethods.card) return c.json({ error: "Card payments are not enabled for this request" }, 400);
|
||||||
|
|
||||||
const { email } = await c.req.json();
|
const { email, amount: overrideAmount } = await c.req.json();
|
||||||
if (!email) return c.json({ error: "Required: email" }, 400);
|
if (!email) return c.json({ error: "Required: email" }, 400);
|
||||||
|
|
||||||
const transakApiKey = getTransakApiKey();
|
const transakApiKey = getTransakApiKey();
|
||||||
|
|
@ -1399,6 +1399,9 @@ routes.post("/api/payments/:id/transak-session", async (c) => {
|
||||||
|
|
||||||
const host = new URL(c.req.url).hostname;
|
const host = new URL(c.req.url).hostname;
|
||||||
|
|
||||||
|
// Use override amount for editable-amount payments, otherwise use preset amount
|
||||||
|
const effectiveAmount = (p.amountEditable && overrideAmount) ? String(overrideAmount) : p.amount;
|
||||||
|
|
||||||
const widgetParams: Record<string, string> = {
|
const widgetParams: Record<string, string> = {
|
||||||
apiKey: transakApiKey,
|
apiKey: transakApiKey,
|
||||||
referrerDomain: extractRootDomain(host),
|
referrerDomain: extractRootDomain(host),
|
||||||
|
|
@ -1407,7 +1410,7 @@ routes.post("/api/payments/:id/transak-session", async (c) => {
|
||||||
defaultCryptoCurrency: p.token,
|
defaultCryptoCurrency: p.token,
|
||||||
walletAddress: p.recipientAddress,
|
walletAddress: p.recipientAddress,
|
||||||
disableWalletAddressForm: 'true',
|
disableWalletAddressForm: 'true',
|
||||||
cryptoAmount: p.amount,
|
cryptoAmount: effectiveAmount,
|
||||||
partnerOrderId: `pay-${paymentId}`,
|
partnerOrderId: `pay-${paymentId}`,
|
||||||
email,
|
email,
|
||||||
themeColor: '6366f1',
|
themeColor: '6366f1',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue