feat: migrate hosted checkout to embedded form
GRW-79
Summary
- Migrate hosted Stripe Checkout to the Embedded form (
ui_mode: "form") on a dedicated Billing setup page, with a themed payment form, order summary, and promotion-code entry. - Add subscription-aware Overview, Billing setup, and Payment method navigation. Checkout entry points share the setup route; Free/Base signup and partner discounts remain supported.
- Add organization-scoped session-status lookup, redirect recovery, session replacement, and delayed-payment webhook handling. Preserve historical session parameters and idempotency during migration.
- Update local Stripe setup to preserve configured key pairs, validate publishable-key readiness, and save configuration atomically. Cover checkout lifecycle behavior through real React Query flows and Stripe-boundary tests.
Deploy the server and dashboard together: createStripeCheckout now returns a client secret and publishable key. Configure STRIPE_PUBLISHABLE_KEY from the same Stripe account as STRIPE_API_KEY, and subscribe the webhook endpoint to checkout.session.async_payment_succeeded and checkout.session.async_payment_failed alongside its existing events.
Motivation
Collect payment details inside the dashboard and give billing setup a dedicated page. Keep existing promotion and subscription behavior while recovering canceled redirects and earlier hosted sessions through the same checkout lifecycle.
Gutternote review steps
- Enable
gram-payg-self-serve-billingfor a sandbox organization with an active trial or an unsubscribed Free/Base account, and sign in as an org admin. Configure the preview's matching Stripe secret/publishable keys and webhook delivery. The seeded demo does not enable this flag itself. - Open Billing (
/<org>/billing) and select Billing setup, or click Add payment method from the trial card. Expect the dedicated setup page (/<org>/billing/setup) with an order summary and Stripe form. Check light/dark themes, a narrow viewport, and promotion-code entry; an accepted partner promotion should be pre-applied. - Complete checkout with Stripe sandbox payment details. Expect Confirming billing setup, followed by Payment method (
/<org>/billing/payment-method) after backend subscription confirmation. A delayed payment remains in the processing state until confirmed. - Try a redirect-based sandbox payment method and cancel its authorization. Returning to billing should resume an open checkout; an expired session should be replaced. Check retry behavior when checkout initialization or status lookup fails.
- For an already subscribed organization, select Payment method from Billing. Spot-check checkout entry points on the trial-ended gate and lockout panel, plus restricted access for a non-admin.
Linear Issue: GRW-79


Summary by cubic
Migrates hosted Stripe Checkout to the embedded form so pay-as-you-go setup starts inside the dashboard. The checkout endpoint now returns a client secret and publishable key that mount Stripe's form on a dedicated Billing setup page instead of redirecting to hosted Checkout.
- Adds Billing setup and Payment method pages with Overview, Billing setup, and Payment method navigation, an order summary, promotion-code entry, and light/dark theming driven by the dashboard theme; a unified billing state picks between the setup and payment-method views.
- Sets
STRIPE_PUBLISHABLE_KEYpaired withSTRIPE_API_KEY;mise run stripe:setupsaves the CLI publishable key only when it also uses the CLI secret, never next to an env-providedSTRIPE_API_KEY. - Expires live hosted Checkout sessions before creating an embedded session, since Stripe rejects an idempotency key whose parameters change; new sessions accept a
themequery parameter while existing sessions keep their original appearance. - Adds an organization-scoped session-status lookup and redirect recovery that replaces expired sessions; the dialog clears when the active organization changes, and the lockout and trial-ended gate open billing routes for the active organization.
- Webhook handling now covers
checkout.session.async_payment_succeededandcheckout.session.async_payment_failedalongside the existing events. - Pins
@stripe/react-stripe-js7.0.0 and@stripe/stripe-js10.1.0 for the embedded form while preserving legacy versions for@polar-sh/checkoutviapackageExtensions.
Migration
- Deploy the server and dashboard together:
createStripeCheckoutnow returns a client secret and publishable key. - Configure
STRIPE_PUBLISHABLE_KEYfrom the same Stripe account asSTRIPE_API_KEY, and subscribe the webhook to the two async-payment checkout events.
Screenshots
Billing setup
Payment method
Platform MCP assessment
Decision: intentionally omit new or changed Platform MCP tools for this checkout migration. The outcome is an organization administrator completing billing setup or updating a payment method for the explicitly selected active organization, not a project-scoped management operation. The existing Platform MCP tool catalogue and registration have no billing/subscription checkout tool to update; this does not change existing tool contracts, server instructions, or shipped workflows.
- Browser and sensitive-data boundary: embedded Stripe checkout is a browser-bound payment-entry and confirmation flow. The checkout client secret belongs only at the authenticated dashboard/Stripe boundary; payment details and checkout credentials must not be exposed in agent inputs, results, or transcripts. This assessment does not propose agents collecting payment details or driving checkout through a new endpoint-shaped tool.
- Target and permission boundary: the dashboard requires organization read/admin access. Backend checkout status requires both permissions for the active organization and matches the returned session's organization and Stripe customer to that organization's billing metadata; a supplied session ID or return URL is not authority to access another tenant or evidence of completed payment. Organization switching must discard the prior checkout state.
- External-client audience: OAuth organization membership alone does not substitute for per-operation organization-admin authorization. Keep this interactive payment workflow in the authenticated dashboard rather than exposing checkout client secrets through external MCP. A future bounded, non-sensitive billing inspection/navigation outcome would require its own design and authorization assessment; it is not being added here.
- Managed-assistant audience: exact-project assistant identity is not an organization administrator's payment authority. Do not expand its scope into organization-wide billing or admit payment entry/checkout credentials to assistant tools.
- Evidence and verification boundary:
BillingSetup.tsxcontains the scope gates and organization-keyed checkout lifecycle;stripe_checkout_status.goenforces organization/customer ownership, with admin/read and foreign-session cases instripe_checkout_status_test.go.platformmcp/descriptor.godistinguishes external OAuth from project-assistant audiences. The browser review steps above cover payment entry, organization-admin access, redirect recovery, and confirmation. This records the intended design and inspected code/tests, not a claim that new browser checks were run or that the locally rebased changes have been pushed.
🚀 Preview Environment (PR #7459)
Preview URL: https://pr-7459.dev.getgram.ai
| Component | Status | Details | Updated (UTC) |
|---|---|---|---|
| ✅ Database | Ready | Created and validated | 2026-10-29 00:19:27. |
| ✅ Images | Available | Container images ready | 2026-10-29 00:20:08. |
Gram Preview Bot