-
Notifications
You must be signed in to change notification settings - Fork 732
Description
Stripe's flexible billing mode provides more accurate and predictable billing behavior for prorations, usage-based pricing, flexible invoicing, and trial settings. It also unlocks new capabilities like mixed interval subscriptions that aren't available in classic mode. Stripe recommends all new integrations use flexible billing mode, and as of API version 2025-09-30.clover, it's the default for all new subscriptions created through the API.
Cashier doesn't currently support it. This issue documents what's needed, the Stripe API requirements, and the known incompatibilities with the current codebase.
What flexible billing enables
- Usage-based billing: charge customers based on actual consumption via Stripe meters, with usage billed at the price in effect when reported (not the most recent price)
- Hybrid subscriptions: combine a fixed base price with metered usage on a single subscription
- Mixed interval subscriptions: bill for multiple recurring prices with different intervals (e.g. monthly and yearly) on one subscription
- Accurate credit prorations: calculated based on the original debited amount, accurately accounting for historical taxes, discounts, and billing cycles
- Proportional discount application: discounts applied proportionally to each subscription item during proration instead of distributed evenly
- Configurable proration on cancellation: disable prorations for truncated first billing periods when using
cancel_at - Consolidated invoicing: a single invoice on renewal, eliminating separate invoices for removed metered items
- No zero-amount line items: metered items added to a subscription don't generate zero-amount line items
- Consistent billing cycle anchor: no implicit resets during operations like setting
cancel_ator switching to a non-zero price - One-way migration: migrate existing classic subscriptions to flexible via the
/v1/subscriptions/:subscription/migrateendpoint, with timestamp tracking viabilling_mode_details.updated_at
What Cashier needs
Core billing mode support
- Set
billing_mode[type]when creating subscriptions via SubscriptionBuilder, CheckoutBuilder, and subscription schedules - A global default via
Cashier::defaultBillingMode('flexible')and per-subscription override viawithBillingMode('flexible') - Migration of existing subscriptions to flexible mode via Stripe's
/migrateendpoint (migrateToFlexibleBillingMode()) - A way to check whether a subscription uses flexible billing (
usesFlexibleBilling()) - A local
billing_modecolumn on the subscriptions table to avoid an API call on every check
Parameter guards for flexible mode
Flexible billing changes which parameters Stripe accepts. Cashier needs to conditionally omit or adjust incompatible parameters when a subscription is in flexible mode:
clear_usagemust not be sent when removing or swapping metered pricesbilling_modeis a top-level param on subscription create, but lives insidesubscription_dataon checkout sessions and quotesbilling_modecannot be sent on subscription update; migration uses the dedicated/migrateendpoint- Subscription schedules on
2025-09-30.cloverusedurationinstead of the removediterationsparameter
Currently incompatible Stripe features
Per Stripe's documentation, the following features return a 400 error when used with flexible billing mode:
- Prebilling (Preview)
- Legacy usage-based billing (non-meter based)
- Legacy 3P tax integrations using
pay_immediately=false(e.g. Avalara) - The legacy
max_occurrencesparameter - Subscription-specific
retry_settings(Preview)
Note: billing thresholds were initially incompatible but Stripe added support as of API version 2025-07-30.basil.
Broader features (subsequent work)
- Subscription schedules: multi-phase lifecycle management with flexible billing mode support and
duration-based phases - Quotes: draft, finalize, accept/cancel lifecycle with billing mode in
subscription_data - Billing credits: credit balance management building on existing
creditBalance()/debitBalance() - Usage thresholds: database-backed usage monitoring with overage calculations
- Webhook handlers:
subscription_schedule.*andquote.*events
Stripe API version requirements
| API Version | What it introduces | stripe-php version |
|---|---|---|
2025-06-30.basil |
billing_mode parameter, /migrate endpoint, flexible billing opt-in |
v17.4.0 |
2025-07-30.basil |
Billing thresholds compatible with flexible mode, mixed interval subscriptions, duration parameter for schedule phases |
v17.5.0 |
2025-09-30.clover |
Flexible becomes the default for new subscriptions, removes iterations on subscription schedules |
v18.0.0 |
Stripe recommends new integrations use 2025-09-30.clover or later. Existing integrations on older API versions default to classic mode and are unaffected.
Meter behavior note
Flexible billing requires usage products to use meters. Meters are customer-level, not subscription-level. If a customer has multiple subscriptions with the same metered price, reported usage aggregates across all subscriptions. This is a fundamental behavioral change from classic metered billing that Cashier's documentation should call out.
Known incompatibilities in current Cashier
| Issue | Description | Status |
|---|---|---|
| #1819 | clear_usage = true sent when removing/swapping metered prices on flexible subscriptions causes Stripe to reject the request |
Fixed in #1820 |
| #1772 (comment) | @j3j5 independently identified the same clear_usage incompatibility |
Documented |
Prior work
- [17.x] Flexible Billing Support for Laravel Cashier #1772 by @Diddyy: comprehensive draft PR (Aug 2025). Reviewed by @crynobone who pushed several WIP commits. Also reviewed by @yoeriboven, with additional input from @j3j5. Currently open as a draft with 23 commits.
- Add flexible billing mode support #1830 by @JoshSalway: clean implementation building on [17.x] Flexible Billing Support for Laravel Cashier #1772, incorporating all review feedback from the original PR. 177 unit tests, 47 feature tests against real Stripe API, documentation, and a live interactive demo running 14 scenarios. Closed without technical review.
- Avoids Stripe error removing metered price from flex sub #1820 by @Arkitecht: targeted fix for the
clear_usageincompatibility (merged).
Design decisions from the review process
These emerged from @crynobone's review on #1772 and subsequent discussion:
Cashier::defaultBillingMode('flexible')in a service provider rather than a config file valuecashier_quotestable name (prefixed) to avoid conflicts with existingquotestables in appsmigrateToFlexibleBillingMode()method naming instead of genericmigrateBillingMode()assertFlexibleBillingSupport()for the validation method name (per @yoeriboven)'classic'|'flexible'type hint on billing mode parameters
References
- Stripe: Flexible billing mode overview
- Stripe: Compare classic and flexible billing mode
- Stripe: Mixed interval subscriptions
- Stripe changelog: billing_mode introduced (2025-06-30.basil)
- Stripe changelog: /migrate endpoint (2025-05-28)
- Stripe changelog: billing thresholds support (2025-07-30.basil)
- Stripe changelog: flexible becomes default (2025-09-30.clover)
- PR [17.x] Flexible Billing Support for Laravel Cashier #1772 (draft, open)
- PR Add flexible billing mode support #1830 (closed)
- Issue Error removing price on flexible subscription #1819 / PR Avoids Stripe error removing metered price from flex sub #1820 (
clear_usagefix, merged)