feat(billing): embed Stripe Payment Element in onboarding (#21759)
## What & why Replaces the hosted Stripe Checkout redirect on the onboarding "Choose your plan" step (credit-card trial) with an inline Stripe **Payment Element**, so users never leave the app to enter card details. ## How it works - **Frontend:** a deferred `<Elements mode="setup">` renders the Payment Element, themed via the Appearance API. On Continue: `elements.submit()` → `checkoutSession` mutation creates the trialing subscription server-side and returns its pending SetupIntent `clientSecret` → `stripe.confirmSetup()` confirms the card (handling 3DS) → redirect to the existing `/plan-required/payment-success`. - **Backend:** new `BILLING_STRIPE_PUBLISHABLE_KEY` config var exposed via `/client-config`; the card path creates the subscription with `payment_behavior: default_incomplete` + a free trial (so Stripe attaches a `pending_setup_intent`) and returns its client secret. The hosted-Checkout code path is removed. - The **no-credit-card** trial path is unchanged. - Billing address collection is **disabled** in the Payment Element to reduce friction; `automatic_tax` is correspondingly disabled (tax needs an address — collect it later, e.g. at conversion / via the billing portal). ## Required before this works 1. Set `BILLING_STRIPE_PUBLISHABLE_KEY` (`pk_…`) on the server (infra change pending). 2. Run `nx run twenty-front:graphql:generate --configuration=metadata` against a server exposing the updated schema (see inline note on the hand-authored document). 3. Verify in Stripe test mode: happy path, 3DS (`4000 0025 0000 3155`), a decline. ## Verified typecheck (front + server), oxlint + oxfmt clean, `client-config.service.spec` passing. Not run here: the app end-to-end / Stripe test mode and `graphql:generate` (no server/DB in the dev container). I've left self-review comments inline flagging cleanup opportunities plus a couple of architectural/tech-debt items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TxCfinXq7abSrbF7aTw2cA --- _Generated by [Claude Code](https://claude.ai/code/session_01TxCfinXq7abSrbF7aTw2cA)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21759?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1610,6 +1610,11 @@ type BillingPlan {
|
||||
meteredProducts: [BillingMeteredProduct!]!
|
||||
}
|
||||
|
||||
type BillingPaymentIntent {
|
||||
clientSecret: String!
|
||||
paymentIntentType: String!
|
||||
}
|
||||
|
||||
type BillingSession {
|
||||
url: String
|
||||
}
|
||||
@@ -1879,6 +1884,7 @@ enum ModelFamily {
|
||||
type Billing {
|
||||
isBillingEnabled: Boolean!
|
||||
billingUrl: String
|
||||
stripePublishableKey: String
|
||||
trialPeriods: [BillingTrialPeriod!]!
|
||||
}
|
||||
|
||||
@@ -3286,6 +3292,7 @@ type Mutation {
|
||||
skipBookOnboardingStep: OnboardingStepSuccess!
|
||||
updateOneApplicationVariable(key: String!, value: String!, applicationId: UUID!): Boolean!
|
||||
checkoutSession(recurringInterval: SubscriptionInterval!, plan: BillingPlanKey! = PRO, requirePaymentMethod: Boolean! = true, successUrlPath: String): BillingSession!
|
||||
createSubscriptionPaymentIntent(recurringInterval: SubscriptionInterval!, plan: BillingPlanKey! = PRO, requirePaymentMethod: Boolean! = true, successUrlPath: String, idempotencyKey: String!): BillingPaymentIntent!
|
||||
switchSubscriptionInterval: BillingUpdate!
|
||||
switchBillingPlan: BillingUpdate!
|
||||
cancelSwitchBillingPlan: BillingUpdate!
|
||||
|
||||
Reference in New Issue
Block a user