Stripe Subscription States
This is a quick note on Subscription states on Stripe. Subscriptions are objects which track products with recurring payments. Stripe docs on Subscriptions are very comprehensive, but for some reason they don’t include a state diagram that shows the transitions between different states of a subscription. They do have one for Invoices, so maybe this post will inspire them to add one.
As of May 2024, the API has 8 values for Subscription.status
:
incomplete
: This is the initial state of a subscription. It means that the subscription has been created but the first payment has not been made yet.incomplete_expired
: The first payment was not made within 23 hours of creating the subscription.trialing
: The subscription is in a trial period.active
: The subscription is active and the customer is being billed according to the subscription’s billing schedule.past_due
: The subscription has unpaid invoices.unpaid
: The subscription has been canceled due to non-payment.canceled
: The subscription has been canceled by the customer or due to non-payment.paused
: The subscription is paused and will not renew.
At any given time, a Customer
’s subscription can be in one of these states. The following diagram shows the transitions between these states.
stateDiagram
classDef alive fill:#28a745,color:white,font-weight:bold,stroke-width:2px
classDef dead fill:#dc3545,color:white,font-weight:bold,stroke-width:2px
classDef suspended fill:#ffc107,color:#343a40,font-weight:bold,stroke-width:2px
active:::alive
trialing:::alive
incomplete:::suspended
past_due:::suspended
unpaid:::suspended
paused:::suspended
canceled:::dead
incomplete_expired:::dead
[*] --> incomplete: Create Subscription
trialing --> active: Trial ended, first<br>payment succeeded
incomplete --> trialing: Started trial
incomplete --> incomplete_expired: Payment not made<br>within 23 hours
incomplete --> active: Payment<br>succeeded
active --> past_due: Automatic payment<br>failed
trialing --> past_due: Trial ended<br>payment failed
past_due --> unpaid: Retry limit<br>reached
past_due --> canceled: Retry limit reached<br>or subscription canceled
past_due --> active: Payment<br>succeeded
trialing --> paused: Trial ended without<br>default payment method
paused --> active: First payment<br>made
active --> unpaid: Automatic payment disabled,<br>manual intervention required
unpaid --> active: Payment<br>succeeded
unpaid --> canceled: Subscription<br>canceled
active --> canceled: Subscription<br>canceled
paused --> canceled: Subscription<br>canceled
trialing --> canceled: Subscription<br>canceled
incomplete_expired --> [*]
canceled --> [*]
Stripe doesn’t comment on these states further and leaves their interpretation to the developer. This is probably because each company might interpret these states differently. For example, a user skipping a payment and becoming past_due
might not warrant disabling a service for some companies, while others might want to disable services immediately. Stripe’s API is built to be agnostic of these decisions.
Regardless of how you interpret these 8 states, you will most likely end up generalizing them into 3 categories: ALIVE
, SUSPENDED
, and DEAD
. The colors in the diagram above represent these categories:
ALIVE
: The subscription is active and payments are being made. States:active
,trialing
.SUSPENDED
: The subscription is not active but can be reactivated. States:incomplete
,past_due
,unpaid
,paused
.DEAD
: The subscription is not active and cannot be reactivated. Such subscriptions are effectively deleted. States:canceled
,incomplete_expired
.
While DEAD
states are unambiguous, your company might differ in what is considered ALIVE
and SUSPENDED
. For example, you might consider past_due
as ALIVE
if you don’t want to disable services immediately after a payment failure.
If you collapse the 8 states into these categories, you get the following diagram:
stateDiagram
direction TB;
classDef alive fill:#28a745,color:white,font-weight:bold,stroke-width:2px
classDef dead fill:#dc3545,color:white,font-weight:bold,stroke-width:2px
classDef suspended fill:#ffc107,color:#343a40,font-weight:bold,stroke-width:2px
ALIVE:::alive
SUSPENDED:::suspended
DEAD:::dead
state ALIVE {
active
trialing
trialing-->active
}
state DEAD {
canceled
incomplete_expired
}
state SUSPENDED {
incomplete
past_due
unpaid
paused
past_due-->unpaid
}
[*] --> SUSPENDED: Create<br>Subscription
SUSPENDED --> ALIVE: Payment succeeded<br>or trial started
ALIVE --> SUSPENDED: Payment<br>failed
SUSPENDED --> DEAD: Subscription canceled<br>or checkout expired
ALIVE --> DEAD: Subscription<br>canceled
DEAD --> [*]
The distinction is important, because Stripe doesn’t make it crystal clear what kind of subscriptions can come back from the dead and end up charging the customers multiple times. If you are not limiting the number of subscriptions per customer, this is something you should be aware of. Practically, this means that you block the customer from creating a new subscription if they already have an ALIVE
or SUSPENDED
subscription. DEAD
subscriptions can be ignored.