Skip to content

Confirm create-org and upgrade-plan payments on-session#3028

Open
lohanidamodar wants to merge 3 commits intomainfrom
feat/live-payment-create-upgrade
Open

Confirm create-org and upgrade-plan payments on-session#3028
lohanidamodar wants to merge 3 commits intomainfrom
feat/live-payment-create-upgrade

Conversation

@lohanidamodar
Copy link
Copy Markdown
Member

Summary

  • Updates the create-organization and change-plan submit flows to confirm PaymentIntents on-session via stripe.confirmPayment({redirect: 'if_required'}), treating PaymentAuthentication as the expected default path for paid plans rather than a 3DS edge case.
  • Adds a ConfirmPaymentOutcome return shape to the confirmPayment helper in lib/stores/stripe.ts so callers can branch cleanly on succeeded / processing / requires_action / error. Other call sites (account/payments, billing/+page.svelte, BAA enable, retry-invoice) keep the existing redirect-default behavior.
  • Adds a paymentProcessing.svelte header alert + checkForUpgradingStatus() which registers it at importance 5 (below readonly / budget / mark-for-deletion / enterpriseTrial / paymentAuthRequired) when team status is upgrading, so the user is not blocked while Stripe is still settling the first charge.
  • Relaxes the Indian card-holder warning copy in 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 / draft and 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 errors
  • bun run check — no new errors in modified files
  • bun run test:unit — 235 passed
  • Manual: create new org with US, EU-3DS and IN mandate cards; verify success / processing / error UX
  • Manual: upgrade plan; verify "Payment is processing" alert appears while Stripe settles and clears once the webhook flips the team to active
  • Manual: failed card during create-org; verify error surfaces and the form can be resubmitted

🤖 Generated with Claude Code

…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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR updates the create-organization and change-plan submit flows to confirm PaymentIntents on-session via stripe.confirmPayment({redirect: 'if_required'}), removing the 24h RBI pre-debit notification window for Indian cards. It also adds a paymentProcessing header alert and checkForUpgradingStatus() so users are not blocked while Stripe settles the first charge.

  • stripe.ts: New ConfirmPaymentOutcome type and redirectIfRequired branch in confirmPayment let callers branch cleanly on succeeded / processing / requires_action / error; existing redirect-default callers are unchanged.
  • create-organization / change-plan: Payment outcome is now fully branched — error triggers draft cleanup via validatePayment, requires_action returns early for the redirect flow, and succeeded/processing call validate() which correctly handles the upgrading-status info toast.
  • BAA flows (BAA.svelte, BAAEnableModal.svelte): Migrated to the same on-session pattern with confirmAddonPayment called after a successful payment outcome.

Confidence Score: 5/5

Safe 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

Filename Overview
src/lib/stores/stripe.ts Adds ConfirmPaymentOutcome type and on-session confirmPayment branch (redirectIfRequired). Logic is sound for the happy path; edge case where paymentIntent.status falls outside the three expected values returns error without showing a notification toast.
src/routes/(console)/create-organization/+page.svelte PaymentAuthentication and validate() paths correctly handle upgrading status; the non-PaymentAuthentication bottom block (direct Organization return from create) still shows unconditional success without a teamStatusUpgrading guard — already flagged in prior review.
src/routes/(console)/organization-[organization]/change-plan/+page.svelte validate() and the PaymentAuthentication branch correctly show the upgrading-status info toast; the direct upgrade() path without PaymentAuthentication still emits unconditional success — already flagged in prior review.
src/lib/components/billing/alerts/paymentProcessing.svelte New header alert component; correctly reads organization status and hides on billing-excluded routes. No issues found.
src/lib/stores/billing.ts Adds teamStatusUpgrading constant, checkForUpgradingStatus helper, and imports PaymentProcessing component. Importance 5 correctly slots the alert below existing higher-priority banners.
src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte Adds on-session confirmPayment with full outcome branching, sets error display on failure, and closes modal on success. confirmAddonPayment errors are caught by the outer try-catch. Clean implementation.
src/routes/(console)/organization-[organization]/settings/BAA.svelte handleReEnable updated with same on-session outcome pattern; errors are covered by the outer try-catch and finally block. No issues found.

Reviews (3): Last reviewed commit: "fix(billing): surface real confirmPaymen..." | Re-trigger Greptile

lohanidamodar and others added 2 commits May 7, 2026 04:03
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>
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.

1 participant