Confirm create-org and upgrade-plan payments on-session#3028
Confirm create-org and upgrade-plan payments on-session#3028lohanidamodar wants to merge 3 commits intomainfrom
Conversation
…grade-plan Switch the create-organization and change-plan submit flows to confirm PaymentIntents on-session via stripe.confirmPayment with redirect: 'if_required'. The frontend now treats PaymentAuthentication responses as the expected default path for paid plans (not just the 3DS edge case) and surfaces succeeded / processing / requires_action outcomes to callers via a new ConfirmPaymentOutcome return type. Other call sites (account/payments, billing, BAA, retry-invoice) remain on the previous redirect-based flow. Add a "Payment is processing" header alert that renders when the team status is 'upgrading' (Stripe still settling). The alert sits below readonly / budget / mark-for-deletion alerts (importance 5), so it only shows when nothing more critical is happening, and lets the user keep using the org while the charge clears. Update the Indian RBI card-holder warning copy to reflect that the first charge is now authenticated on-session and the $150 mandate applies to future renewals — this avoids the 24h "processing" limbo seen with off-session first charges under the RBI mandate.
Greptile SummaryThis PR updates the
Confidence Score: 5/5Safe to merge; the on-session payment flow is well-structured and all callers handle the outcome branches correctly. The core change is implemented cleanly with all outcome branches handled. The edge case in stripe.ts (unexpected Stripe status codes not in the handled set) lacks a user-visible notification but is not reachable through normal Stripe responses. The bottom notification blocks in create-organization/+page.svelte and change-plan/+page.svelte (non-PaymentAuthentication paid-plan path) were flagged in a prior review and remain unchanged in this diff. Important Files Changed
Reviews (3): Last reviewed commit: "fix(billing): surface real confirmPaymen..." | Re-trigger Greptile |
Switch the BAA addon enable flow (both the re-enable button on the
settings card and the BAAEnableModal submit) to confirm the addon
PaymentIntent on-session via stripe.confirmPayment with redirect:
'if_required'. After a succeeded or processing outcome, finalize the
addon by calling organizations.confirmAddonPayment directly and
invalidate ADDONS + ORGANIZATION dependencies inline — no more URL
round-trip via ?type=confirm-addon&addonId=.
A processing outcome surfaces an info notification ("BAA addon payment
is processing — we'll activate it shortly.") since the addon stays
pending while Stripe settles, and the existing "Payment pending" badge
on the BAA card already covers the visual state.
The onMount redirect-handler in BAA.svelte that consumes the
?type=confirm-addon query string is preserved as a fallback for the
rare case where Stripe still elects to redirect (e.g. some 3DS
challenges that can't be inlined) — the route is still passed to
confirmPayment so Stripe has a return URL when needed.
… failure
Two issues surfaced during user testing of the live-payment flow on
create-organization and change-plan.
1. confirmPayment() in src/lib/stores/stripe.ts always called
resolve('/(console)/organization-[organization]/billing', {organization: orgId})
eagerly at the top of the try block. The create-organization callsite
passes only `route` (no orgId, since the team doesn't yet exist), so
resolve() threw on the missing required `organization` param, the
outer catch fired, and the user saw the generic
"There was an error processing your payment..." message instead of
the actual Stripe error. The URL is now built lazily — resolve() is
only invoked when no `route` is supplied. The outer catch now
surfaces the underlying error message (Stripe / SDK error) and falls
back to the generic copy only when no message is available.
2. When the frontend Stripe confirmation failed, the backend was never
notified, leaving draft teams (status='draft') and partial upgrades
stranded. handleTeamCreateUpgradeFailure already deletes drafts and
rolls back upgrades — but only when the backend recognises failure.
The create-organization and change-plan error branches now make a
best-effort call to organizations.validatePayment after a failed
confirmation. validatePayment inspects the actual Stripe intent and
routes to handleTeamCreateUpgradeFailure when it isn't
succeeded/processing. validatePayment is expected to throw
BILLING_PAYMENT_FAILED in this path; the throw is swallowed because
the user has already seen the underlying Stripe error and the
backend cleanup is the desired side-effect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
create-organizationandchange-plansubmit flows to confirm PaymentIntents on-session viastripe.confirmPayment({redirect: 'if_required'}), treatingPaymentAuthenticationas the expected default path for paid plans rather than a 3DS edge case.ConfirmPaymentOutcomereturn shape to theconfirmPaymenthelper inlib/stores/stripe.tsso callers can branch cleanly onsucceeded/processing/requires_action/error. Other call sites (account/payments,billing/+page.svelte, BAA enable, retry-invoice) keep the existing redirect-default behavior.paymentProcessing.svelteheader alert +checkForUpgradingStatus()which registers it at importance 5 (below readonly / budget / mark-for-deletion / enterpriseTrial / paymentAuthRequired) when team status isupgrading, so the user is not blocked while Stripe is still settling the first charge.selectPaymentMethod.svelte: the $150 mandate context now refers to future renewals rather than the no-longer-applicable first-charge 24h delay.Why
For Indian cards bound by an RBI mandate, the first charge had to wait through a 24h pre-debit notification window when initiated off-session. The team document sat in
upgrading/draftand the user was effectively blocked. Authenticating the first charge on-session removes the notification window. Renewals continue to use the existing off-session mandate flow.Test plan
bun run lint— no new errorsbun run check— no new errors in modified filesbun run test:unit— 235 passedactive🤖 Generated with Claude Code