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.