Skip to Content
Supported subscriptionsPer-offer eligibility

Per-offer eligibility

Reference tables. One per offer. Each table lists the conditions a subscription must satisfy for the offer to surface, and the conditions that block it.

Eligibility is per-offer, not all-or-nothing. A single subscription can be eligible for cancel and discount but blocked from pause (because it is annual) and plan-switch (because no merchant-approved downgrade exists).

For the comprehensive list of subscription shapes that block automated cancel, see blocked subscription shapes.

Cancel

Cancel has the loosest constraints, but it is not unconditional. When automated cancel is blocked, the cancel CTA still works — it records a manual cancellation request instead of mutating Stripe.

Stripe primitive: subscriptions.update({ cancel_at_period_end: true })

Eligible when:

ConditionRequired value
Statusactive or trialing
Already cancelingcancel_at_period_end === false and cancel_at === null. If already canceling, the widget shows the existing date and does not mutate again.
Pause collectionpause_collection === null (foreign or manual collection pauses route to manual)
First-class paused statusstatus !== 'paused'
Scheduleschedule === null
Cadencecadence === null (Stripe v2 preview primitive — undocumented behavior with retention mutations)
Pending updatepending_update === null
Itemsitems.data.length === 1 AND items.has_more === false (paginated multi-item shapes are also blocked)
Status finalityNot in canceled, incomplete_expired, past_due, unpaid, incomplete

Routed to manual cancellation request when:

ReasonWhy
Subscription is delinquent or in setup (past_due, unpaid, incomplete)Period boundaries and dunning state make “scheduled for period end” ambiguous; we do not mutate dunning state automatically
First-class paused subscriptionStripe’s resume path may create a resumption invoice; cancel semantics on a true status='paused' sub are not part of the launch contract
Foreign or manual pause_collection is setCancel semantics on a paused-collection sub set by another tool are outside the launch contract
Pending update is queuedOut-of-band pending updates interleave unsafely with mutations; per-mutation interaction is unverifiable in Stripe docs
Multi-item or paginated multi-item subscriptionPer-item billing periods break a single-date cancellation copy; mixed-interval items have separate final-invoice semantics
Schedule-attached subscriptionA direct cancel_at_period_end may be overwritten by the next phase boundary or conflict with the schedule’s end_behavior. The safe path (subscriptionSchedules.release then cancel) is roadmap
Cadence-attached subscription (Stripe v2 preview)Cancel behavior on cadence-attached subs is undocumented
Already terminal (canceled, incomplete_expired)No mutation is valid; widget shows the truth
Unrecognized future Stripe shapeResolver fails closed when it cannot prove which other rule applies

Discount

Applies a freshly-created session-specific coupon (max_redemptions=1, redeem_by=now+1h) to the customer’s next eligible invoice.

Stripe primitive: coupons.create then subscriptions.update({ discounts: [{ coupon }] })

Eligible when:

ConditionRequired value
Statusactive or trialing. Trialing subs receive duration='once' coupons only
CadenceAny single-item cadence
ItemsSingle-item, single-seat (items.data.length === 1, quantity === 1)
Price shapeLicensed (recurring.usage_type === 'licensed'), per-unit (billing_scheme === 'per_unit'), integer unit_amount, single-currency (currency_options empty), no custom_unit_amount, no transform_quantity, no tiers_mode
Collection methodcharge_automatically
Automatic taxautomatic_tax.enabled === false
Payment methodCard, country !== 'IN'
Pending stateNo schedule, no pending_update, no pending_invoice_item_interval, no pending one-off invoice items, no cadence
Invoice stateNo unresolved invoices on the sub (every invoice is paid or void)
Existing discountNone on customer.discount, subscription.discounts, or any subscription.items[].discounts
CooldownDiscount cooldown not active for this customer
Coupon duration vs cadenceFor non-monthly subs, only duration='once' is allowed unless duration_in_months >= billing_interval_in_months. Coupon validity is time-based, not invoice-count-based
Trialing + repeatingTrialing subs may not receive duration='repeating' coupons (the coupon window can expire before the first paid invoice)

Ineligible when:

ReasonWhy
Subscription already has any active discountStacking is not supported. Customer-level, subscription-level, and item-level discount surfaces are all checked
Multi-item or multi-seat subscriptionDiscount copy assumes single-item, single-seat economics
Tiered pricing, custom amount, decimal-only price, transformed quantity, or multi-currency price”Show price” math is non-deterministic without invoice preview
Automatic tax enabledWidget tile shows pre-tax unit_amount; customer would be charged post-tax — copy lie
Async payment method or India cardAsync PMs (ACH, SEPA, BECS, Bacs, ACSS, customer balance, UPI, Klarna, PayPal, Link) keep status='active' after a failed payment, so active is not a reliable signal. India recurring cards have RBI-mandated 24h pre-debit notification + Stripe 26h delay
Unresolved invoices on the subDiscount applies to future invoices; it does not rewrite already-finalized draft or open invoices
pending_invoice_item_interval is set, or pending one-off invoice items exist”Next invoice” copy can be false because pending items can be invoiced separately
Schedule-attached, cadence-attached, or pending_updateMutations can be overwritten by the next phase or interleave unsafely
Trialing + repeating couponTime-based coupon can expire during the trial before the first paid invoice
Cooldown activeConfigured per-customer cooldown prevents discount fatigue

Source: coupons , subscriptions overview .

Pause

Pauses invoice collection for a fixed window. New invoices generated during the pause are voided immediately. The subscription itself stays status='active'.

This is not Stripe’s first-class status='paused' API. The two are separate primitives and do not share a resume path.

Stripe primitive: subscriptions.update({ pause_collection: { behavior: 'void', resumes_at } })

Eligible when:

ConditionRequired value
Statusactive
CadenceMonthly only (interval === 'month' AND interval_count === 1)
ItemsSingle-item, single-seat
Price shapeLicensed, per-unit, integer unit_amount, single-currency, no custom/decimal-only/tiered/transformed shape
Collection methodcharge_automatically
Automatic taxautomatic_tax.enabled === false
Payment methodCard, country !== 'IN'
Pending stateNo schedule, no pending_update, no pending_invoice_item_interval, no pending one-off invoice items, no cadence
Invoice stateNo unresolved invoices on the sub (every invoice is paid or void)
DiscountsNone on customer, subscription, or items
Cancel statecancel_at_period_end === false and cancel_at === null
Pause statepause_collection === null (already-paused subs use the resume path, not re-pause)
CooldownPause cooldown not active

Ineligible when:

ReasonWhy
Annual, quarterly, or any non-monthly cadencePausing across a renewal date forfeits revenue. Annual pause is monthly-only at launch and is on the roadmap
Status is not activePause requires status='active' — trialing, past_due, unpaid, and paused subs are all blocked
Pre-existing unresolved invoicespause_collection only voids invoices generated after the pause takes effect. Existing draft or open invoices continue to be retried unless voided separately
Multi-item or multi-seatPer-item or per-seat pause copy is not supported
Existing discount on any surfacePause does not extend coupon validity, so a discount can expire unused during the pause
Async PM or India cardSame async-PM and RBI timing concerns as discount
Schedule-attached, cadence-attached, or pending_updatePause behavior interleaves unsafely with phase transitions or queued updates
Already paused via pause_collectionUse resume; do not re-pause
First-class status='paused'Different primitive; not addressed by this offer

Source: pause-payment  — Stripe’s own copy is “the subscription’s status remains unchanged” and “invoices created before subscriptions are paused continue to be retried unless you void them.”

Plan-switch (downgrade)

Migrates the customer to a strictly cheaper monthly plan. The new price applies at the next invoice. Proration is forced to 'none' — no immediate charge or credit. No customer-balance side effects across other subscriptions.

Stripe primitive: subscriptions.update({ items: [{ id, price, quantity }], proration_behavior: 'none' })

Eligible when:

ConditionRequired value
Statusactive
CadenceMonthly only on both current and target prices
ItemsSingle-item, single-seat
Current price shapeLicensed, per-unit, integer unit_amount, single-currency, no custom/decimal/tiered/transformed shape
Target price shapeSame as current. Plus target.active === true
Currencytarget.currency === current.currency (lowercase compare)
Cadence matchtarget.recurring.interval === current.recurring.interval AND interval_count matches
Tax behaviortarget.tax_behavior === current.tax_behavior (immutable per price; switching inclusiveexclusive changes the customer’s actual paid amount even at the same headline price)
Strictly cheapertarget.unit_amount < current.unit_amount (integer compare, default currency)
Multi-currency on either sideBoth current.currency_options and target.currency_options must be empty/null
Merchant approvalExact pair current_price_id -> target_price_id exists in the merchant’s plan_switch.allowed_transitions configuration
Automatic taxautomatic_tax.enabled === false
Pending stateNo schedule, no pending_update, no pending_invoice_item_interval, no pending one-off invoice items, no cadence
DiscountsNone on any surface
Payment methodCard, country !== 'IN'
Cancel stateNot already canceling
Pause stateNot already paused
ProrationForced to 'none' regardless of merchant config (avoids cross-sub customer.balance subsidy)

Ineligible when:

ReasonWhy
Annual or non-monthly subscriptionPlan switch is monthly-only at launch. Roadmap
Upgrade attempt (target.unit_amount >= current.unit_amount)Plan-switch is downgrade-only
Cross-cadence switch (monthly ↔ annual)Stripe resets billing_cycle_anchor in classic mode and may bill immediately at the new interval
Currency mismatchStripe rejects mid-sub currency change
Tax-behavior mismatchThe same headline price can charge a different actual amount
Multi-currency price on current OR targetunit_amount can drift independently per currency; the comparison can be inverted in the settlement currency, silently moving the customer to a more expensive plan
Target price not on the merchant’s allowed_transitions listProduct names and metadata are not entitlement proof. Each downgrade path must be explicitly approved
Multi-item, multi-seat, or meteredQuantity reset risk on update; metered usage has separate final-usage semantics
Tiered, decimal-only, transformed, or non-per_unit price on either side”Strictly cheaper” is only deterministic for integer per-unit prices
Automatic tax enabledSame copy-lie risk as discount
Existing discount, schedule, pending update, cadence, pending invoice itemsSame interleave/preservation risks as discount

Source: billing-cycle , customer balance  — credit prorations land in customer.balance and auto-apply to the next finalized invoice on any sub for that customer, which is why proration is forced to 'none'.

Trial extension

Extends the customer’s trial by N days (configured per merchant). The next billing date shifts based on Stripe’s billing-mode rules.

Stripe primitive: subscriptions.update({ trial_end, proration_behavior: 'none' })

Eligible when:

ConditionRequired value
Statustrialing
Trial remainingtrial_end > now + 24h
New trial endnew_trial_end <= billing_cycle_anchor + 2y (Stripe’s 2-year hard cap). Per-offer config caps daysExtended at 1–30 days.
ItemsSingle-item, single-seat, licensed per-unit
DiscountsNone on any surface
Pending stateNo schedule, no pending_update, no pending_invoice_item_interval, no pending one-off invoice items, no cadence
Trial offer markerNo subscription.items[].current_trial.trial_offer (first-class Trial Offers are a separate model)
BudgetPer-merchant per-customer extension budget remaining (typically one extension per customer in live mode)
Cancel stateNot already canceling
Payment methodCard, country !== 'IN'
Automatic taxautomatic_tax.enabled === false

Ineligible when:

ReasonWhy
Trial has less than 24 hours remainingCustomer is past the decision window for the offer to be meaningful
Customer has already received an extensionCompounding extensions break per-customer budgets and stack into Stripe’s 2-year anchor cap
Existing discount on any surfaceCompounding incentives become hard to reason about; coupon validity is time-based and may expire mid-extension
Attached first-class Trial OfferStripe treats Trial Offers as a separate model from legacy trial_end extension
Pending invoice items, pending invoice-item interval, or pending_update”Free until” copy can be false
Schedule-attached or cadence-attachedPhase transitions can overwrite the new trial_end
Multi-item, multi-seat, or non-licensed priceSingle-item, single-seat is the supported retention shape

The next-invoice timing after extension depends on subscription.billing_mode.type. In classic mode, billing_cycle_anchor aligns to the new trial_end. In flexible mode, the anchor is unchanged. The widget makes no anchor-movement promise it cannot read back from Stripe — see billing-cycle .

Last updated on