## Summary
When users click 'Subscribe Now' during a trial period without a payment
method configured, they previously saw an unhelpful error message: "No
payment method found. Please update your billing details."
This PR improves the UX by redirecting users directly to Stripe's
billing portal payment method update flow, where they can add their
payment information. After adding a payment method, users are redirected
back to the billing settings page and can click 'Subscribe Now' again to
successfully activate their subscription.
## Changes
### Backend
- **stripe-billing-portal.service.ts**: Added
`createBillingPortalSessionForPaymentMethodUpdate` method that creates a
Stripe billing portal session with `flow_data.type:
'payment_method_update'`
- **billing-portal.workspace-service.ts**: Added
`computeBillingPortalSessionURLForPaymentMethodUpdate` method to build
the portal URL
- **billing-end-trial-period.output.ts**: Added optional
`billingPortalUrl` field to the GraphQL output DTO
- **billing-subscription.service.ts**: Modified `endTrialPeriod` to
return `stripeCustomerId` when no payment method exists
- **billing.resolver.ts**: Updated resolver to orchestrate billing
portal URL generation when no payment method
### Frontend
- **useEndSubscriptionTrialPeriod.ts**: Redirect to billing portal URL
instead of showing error snackbar
- **endSubscriptionTrialPeriod.ts**: Added `billingPortalUrl` to
mutation response fields
## User Flow
1. User clicks "Subscribe Now" during trial
2. Backend checks for payment method
3. If no payment method → redirect to Stripe billing portal payment
method update page
4. User adds payment method in Stripe
5. User is redirected back to `/settings/billing`
6. User clicks "Subscribe Now" again to activate subscription
## Summary
This PR implements credit rollover functionality for billing, allowing
unused credits from one billing period to carry over to the next (capped
at the current period's subscription tier cap).
## Changes
### New Services
- **StripeCreditGrantService**: Interacts with Stripe's Billing Credits
API to create credit grants, retrieve customer credit balances, and void
grants
- **BillingCreditRolloverService**: Contains the rollover logic -
calculates unused credits and creates new grants for the next period
- **BillingWebhookCreditGrantService**: Handles
`billing.credit_grant.created` and `billing.credit_grant.updated`
webhooks to update billing alerts
### Modified Services
- **StripeBillingAlertService**: Updated to include credit balance when
calculating usage threshold alerts
- **BillingUsageService**: Returns rollover credits to the frontend for
display
- **BillingSubscriptionService**: Queries credit balance when creating
billing alerts
- **BillingWebhookInvoiceService**: Triggers rollover processing on
`invoice.finalized` webhook
### Frontend
- Updated `SettingsBillingCreditsSection` to display base credits,
rollover credits, and total available
- Updated GraphQL query to fetch new `rolloverCredits` and
`totalGrantedCredits` fields
### Stripe SDK Upgrade
- Upgraded from v17.3.1 to v19.3.1 to get proper types for
billing.credit_grant events
- Fixed breaking changes: invoice.subscription path, subscription period
fields location, removed properties
## How it works
1. When `invoice.finalized` webhook is received for
`subscription_cycle`, the system:
- Calculates usage from the previous period
- Determines unused credits (tier cap - usage)
- Caps rollover at current tier cap
- Creates a Stripe credit grant with expiration at end of new period
2. When credit grants are created/updated/voided:
- Billing alerts are recreated with the updated credit balance
3. The UI displays:
- Base credits (from subscription tier)
- Rollover credits (from previous periods)
- Total available credits
## Edge Cases Handled
- Credit grant voided: `billing.credit_grant.updated` webhook triggers
alert update
- Credit grant expired: Stripe's `creditBalanceSummary` API excludes
expired grants
- No unused credits: Rollover service skips grant creation
- Customer ID as object: Controller extracts `.id` from expanded
customer
## TODO:
- [x] display "yearly" or "monthly" wording everywhere it's needed
- [ ] Add button with "downgrade" or "upgrade" to save the change of
credits price + modal to validate
- [x] Add renewal date
- [ ] Implement
https://docs.stripe.com/billing/subscriptions/subscription-schedules for
`switchFromYearlyToMonthly` and decrease number of credits
In this PR :
- set billing thresholds after subscription creation (not possible
during billing checkout)
- add specific free trial workflow credit quantities + set them in
subscription item + check them when receiving stripe alert event
closes : https://github.com/twentyhq/core-team-issues/issues/682
**TLDR:**
Deprecate getProductPrices in the frontEnd and replace it with
BillingBaseProductPrices.
**In order to test:**
- Have the environment variable IS_BILLING_ENABLED set to true and add
the other required environment variables for Billing to work
- Do a database reset (to ensure that the new feature flag is properly
added and that the billing tables are created)
- Run the command: npx nx run twenty-server:command
billing:sync-plans-data (if you don't do that the products and prices
will not be present in the database)
- Run the server , the frontend, the worker, and the stripe listen
command (stripe listen --forward-to
http://localhost:3000/billing/webhooks)
- Buy a subscription for acme workspace the choose your plan should be
using the new front end endpoint
- move front `onboardingStatus` computing to server side
- add logic to `useSetNextOnboardingStatus`
- update some missing redirections in
`usePageChangeEffectNavigateLocation`
- separate subscriptionStatus from onboardingStatus