Skip to content

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

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

Confirm create-org and upgrade-plan payments on-session#3028
lohanidamodar wants to merge 4 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 shifts the first-charge Stripe confirmation for create-organization and change-plan from an off-session redirect to an on-session confirmPayment({ redirect: 'if_required' }) call, removing the 24-hour RBI pre-debit notification window for Indian cards. It also adds the paymentProcessing header alert and checkForUpgradingStatus to let users keep working while Stripe settles the charge.

  • ConfirmPaymentOutcome type + confirmPayment refactor (stripe.ts): adds a redirectIfRequired branch that returns a discriminated union (succeeded / processing / requires_action / error) so callers can branch cleanly; existing callers without redirectIfRequired: true keep the old redirect-default behavior unchanged.
  • Create-org and change-plan pages: consume the new outcome, clean up draft orgs on payment failure via validatePayment, and show an info toast (instead of a success toast) when org.status === upgrading — correctly applied in the validate() helpers, though the direct non-PaymentAuthentication fallback paths in create() and upgrade() still show the unconditional success message (flagged in prior review).
  • BAA flows (BAA.svelte, BAAEnableModal.svelte): mirror the same pattern for addon payment confirmation, calling confirmAddonPayment only after a confirmed or processing on-session payment, with proper error and processing notification variants.

Confidence Score: 5/5

Safe to merge; the on-session payment flow is correctly structured with no data loss or auth regressions.

The core payment path — on-session confirmation, outcome branching, draft-org cleanup on failure, and upgrading-status alerting — is implemented consistently across all four affected flows. The only new finding is that an unexpected Stripe payment status silently returns an error outcome without showing a notification, leaving the user without feedback in that edge case.

The non-PaymentAuthentication fallback blocks in create-organization/+page.svelte (bottom of create()) and change-plan/+page.svelte (bottom of upgrade()) still unconditionally show success messages when org.status may be upgrading.

Important Files Changed

Filename Overview
src/lib/stores/stripe.ts Adds ConfirmPaymentOutcome type and redirectIfRequired branch to confirmPayment; the unexpected-status fallback returns an error outcome without notifying the user.
src/routes/(console)/create-organization/+page.svelte Adds on-session payment confirmation flow and upgrading-status notification in validate(); the fallback non-PaymentAuthentication path in create() still always shows the success notification without checking org.status === upgrading.
src/routes/(console)/organization-[organization]/change-plan/+page.svelte Adds on-session confirmation and upgrading-status check in validate(); the non-PaymentAuthentication path at the bottom of upgrade() still unconditionally shows "Your organization has been upgraded" without checking for upgrading status.
src/lib/components/billing/alerts/paymentProcessing.svelte New header alert component that reactively checks $organization.status === teamStatusUpgrading; correctly self-hides when status transitions to active.
src/lib/stores/billing.ts Exports teamStatusUpgrading constant and checkForUpgradingStatus helper that registers the payment-processing alert at importance 5; follows the same pattern as other alert check functions.
src/routes/(console)/organization-[organization]/settings/BAA.svelte Updates handleReEnable to use on-session confirmation with outcome branching; confirmAddonPayment is called only on success, with processing vs succeeded notification correctly differentiated.
src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte Applies the same on-session confirmation pattern to handleSubmit; error, requires_action, and success/processing paths all handled correctly; outer try/catch covers confirmAddonPayment failures.
src/routes/(console)/+layout.svelte Registers checkForUpgradingStatus in the org-check pipeline; the alert component's reactive check handles subsequent status changes correctly.

Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | 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>
@lohanidamodar lohanidamodar requested a review from ChiragAgg5k May 10, 2026 07:59
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