Skip to content

feat: add crypto payment options to dashboard Stripe modal (CPL-274)#314

Open
GTC6244 wants to merge 2 commits intonextfrom
feature/cpl-274-add-crypto-payment-options-in-the-stripe-modal-in-the
Open

feat: add crypto payment options to dashboard Stripe modal (CPL-274)#314
GTC6244 wants to merge 2 commits intonextfrom
feature/cpl-274-add-crypto-payment-options-in-the-stripe-modal-in-the

Conversation

@GTC6244
Copy link
Copy Markdown
Contributor

@GTC6244 GTC6244 commented Apr 24, 2026

Summary

Switches the Add Funds modal in the dashboard from Stripe's Card Element to the Payment Element, which auto-renders whichever methods are enabled on the Stripe account (card + crypto: USDC, USDP, ETH, SOL). Backend billing endpoints already support crypto — this is a frontend-only change.

Linear: CPL-274

What changed

  • lit-static/dapps/dashboard/index.html — Billing modal split into a two-step flow: amount selector + Continue (step 1), then Payment Element mount point + Back/Pay (step 2). stripe-card-element renamed to stripe-payment-element.
  • lit-static/dapps/dashboard/billing.js — Rewrote the payment flow. Continue creates the PaymentIntent and mounts elements({ clientSecret }).create('payment'). Pay calls stripe.confirmPayment({ elements, confirmParams: { return_url }, redirect: 'if_required' }) — card stays inline, crypto redirects to the wallet flow. Exports new handleBillingReturn() that detects ?payment_intent=…&redirect_status=… on page load, calls the backend confirm_payment endpoint, and strips the query params.
  • lit-static/dapps/dashboard/app.js — Imports and invokes handleBillingReturn() from the setOnAuthReady callback so the crypto-redirect return is processed once the API key is loaded.

Why

The existing modal used elements.create('card') + confirmCardPayment(), which is card-only and doesn't surface Stripe's crypto options. The Payment Element approach is a minimal frontend refactor that unlocks card + crypto in one UI — no backend changes needed.

Pre-Landing Review

No blocking issues found.

  • Security/data safety: return_url is built from window.location.origin + window.location.pathname (no user input). Status display uses textContent (no XSS surface). No new credentials/tokens exposed.
  • Error handling: handleContinue, handlePay, and handleBillingReturn all wrap network calls in try/catch. confirmPayment-backend failure preserves the prior "card charged, credit pending" fallback. URL params stripped before confirm_payment call so reloads don't retrigger.
  • Edge cases checked: double-click on Continue/Pay (buttons disabled in-flight, re-enabled in finally); amount change mid-flow (Back fully unmounts the element, next Continue creates a fresh intent); repeated handleBillingReturn calls (URL cleaned first, subsequent fires no-op); Stripe account without crypto enabled (Payment Element gracefully shows card only).

Test plan

Requires a dev backend with Stripe test keys and a Stripe account that has crypto payment methods enabled.

  • Open dashboard → Add Funds → amount step shows with dropdown + Continue
  • Pick $5 → Continue → Payment Element renders with card (and crypto tabs if enabled)
  • Back button returns to amount step (unmounts element; next Continue creates a fresh PaymentIntent)
  • Card path: submit test card 4242… → stays inline → balance updates → modal closes
  • Crypto path: select crypto → connect wallet → approve → redirects out → returns to dashboard root → auth ready fires → handleBillingReturn calls confirm_payment → overview shows "Credits added" → balance refreshes
  • Confirm that an unauthenticated user hitting ?payment_intent=… silently strips the params without processing (no crash, no retry loop)

Notes

  • No unit tests exist for the vanilla-JS dashboard module — node --check passes on both JS files.
  • Crypto docs at docs/management/crypto.mdx already describe the intended user flow and reference the Payment Element approach this PR implements.

🤖 Generated with Claude Code

Switch the Add Funds modal from Stripe Card Element to Payment Element,
which auto-renders any payment methods enabled on the Stripe account
(card, USDC, USDP, ETH, SOL). Backend billing endpoints already support
crypto — this is frontend-only.

Flow: user picks amount → Continue creates a PaymentIntent and mounts
the Payment Element with its client_secret → Pay calls
stripe.confirmPayment with redirect: 'if_required'. Card stays inline;
crypto redirects to the wallet flow and returns to the dashboard with
?payment_intent=...&redirect_status=succeeded, which handleBillingReturn
detects on page load to call confirm_payment and refresh the balance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the dashboard “Add Funds” modal to use Stripe’s Payment Element so enabled payment methods (including crypto) can be used in a single flow, and adds return-handling for redirect-based methods.

Changes:

  • Replaced the Card Element UI with a two-step modal (amount → payment) and a Payment Element mount point.
  • Reworked the billing flow to create a PaymentIntent on “Continue” and confirm via stripe.confirmPayment(..., redirect: 'if_required') on “Pay”.
  • Added handleBillingReturn() and wired it into the auth-ready callback to confirm redirect-based payments on return.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
lit-static/dapps/dashboard/index.html Updates modal markup for a two-step flow and Payment Element container/buttons.
lit-static/dapps/dashboard/billing.js Implements Payment Element flow, confirm logic, and redirect-return processing.
lit-static/dapps/dashboard/app.js Calls handleBillingReturn() after auth is ready so redirect returns are processed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +261 to +262
setStatus('Payment processed — credit pending. Reference: ' + intentId, 'info');
closeBillingModal();
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the confirmPayment failure fallback, setStatus('Payment processed — credit pending…') is immediately cleared because closeBillingModal() calls setStatus(''). This means the user never sees the pending/receipt message. Consider either (a) showing this message via the top-level status banner before closing, or (b) not clearing status when closing (or only clearing when opening).

Suggested change
setStatus('Payment processed — credit pending. Reference: ' + intentId, 'info');
closeBillingModal();
closeBillingModal();
setStatus('Payment processed — credit pending. Reference: ' + intentId, 'info');

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +302
if (status !== 'succeeded') {
showTopLevelStatus('Payment ' + status + '. Reference: ' + intentId, 'error');
return;
}

try {
const client = await getClient();
await client.confirmPayment(apiKey, intentId);
showTopLevelStatus('Credits added to your account.', 'success');
await loadBillingBalance();
} catch (e) {
logError('handleBillingReturn', e, { intentId });
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redirect_status can be values like processing, failed, or canceled (not just succeeded). Treating every non-succeeded status as an error will mislabel processing payments and prevents running the backend confirmPayment path that could transition credits to pending/success. Handle processing as an info/pending state (and optionally still call confirmPayment), and only mark failed/canceled as error.

Suggested change
if (status !== 'succeeded') {
showTopLevelStatus('Payment ' + status + '. Reference: ' + intentId, 'error');
return;
}
try {
const client = await getClient();
await client.confirmPayment(apiKey, intentId);
showTopLevelStatus('Credits added to your account.', 'success');
await loadBillingBalance();
} catch (e) {
logError('handleBillingReturn', e, { intentId });
if (status === 'failed' || status === 'canceled') {
showTopLevelStatus('Payment ' + status + '. Reference: ' + intentId, 'error');
return;
}
if (status === 'processing') {
showTopLevelStatus('Payment processing. Reference: ' + intentId, 'info');
}
try {
const client = await getClient();
await client.confirmPayment(apiKey, intentId);
showTopLevelStatus('Credits added to your account.', 'success');
await loadBillingBalance();
} catch (e) {
logError('handleBillingReturn', e, { intentId, status });

Copilot uses AI. Check for mistakes.
<div class="modal-body">
<p style="margin-bottom:1rem;">Add credits to your account (minimum $5.00). Credits are used for API calls ($0.01 for management, $0.01 per second for Lit Action execution with a 1-second minimum).</p>
<div class="form-group">
<p style="margin-bottom:1rem;">Add credits to your account (minimum $5.00). Pay with card or crypto (USDC, ETH, SOL). Credits are used for API calls ($0.01 for management, $0.01 per second for Lit Action execution with a 1-second minimum).</p>
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modal copy enumerates specific crypto assets ("USDC, ETH, SOL"), but the PR description/code comments mention additional assets (e.g., USDP) and Stripe-enabled methods can change over time. To avoid stale/misleading UI text, consider making this phrasing generic (e.g., "Pay with card or crypto") or ensure the list matches the actual enabled methods.

Suggested change
<p style="margin-bottom:1rem;">Add credits to your account (minimum $5.00). Pay with card or crypto (USDC, ETH, SOL). Credits are used for API calls ($0.01 for management, $0.01 per second for Lit Action execution with a 1-second minimum).</p>
<p style="margin-bottom:1rem;">Add credits to your account (minimum $5.00). Pay with card or crypto. Credits are used for API calls ($0.01 for management, $0.01 per second for Lit Action execution with a 1-second minimum).</p>

Copilot uses AI. Check for mistakes.
…rypto-payment-options-in-the-stripe-modal-in-the

# Conflicts:
#	lit-static/dapps/dashboard/app.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants