Concepts
Subscription Lifecycle
States and transitions
States
| Status | When |
|---|---|
pending_payment | Waiting for Stripe Checkout |
trialing | In trial period |
active | Paid and active |
past_due | Payment failed |
canceled | Ended |
paused | Temporarily paused |
incomplete | Setup incomplete |
Flow
pending_payment ──webhook──► active
└► trialing ──trial ends──► active (charged)
└► past_due (charge failed)
└► canceled (no payment method)
active ──payment fails──► past_due ──retries fail──► canceledCancellation
// Keep access until period ends
await billing.api.cancelSubscription({
customerId: user.id,
cancelAt: "period_end",
});
// status stays "active", cancelAt is set
// End immediately
await billing.api.cancelSubscription({
customerId: user.id,
cancelAt: "immediately",
});
// status becomes "canceled"Trials
Configure per price:
prices: [
{ amount: 2000, interval: "monthly", trialDays: 14 },
]Trial Flow
- Checkout: User subscribes to a plan with
trialDays. Stripe Checkout collects a payment method without charging (mode: "setup"). - Webhook: After checkout completes, subscription stays
"trialing"(not overwritten to"active"). - Trial period: User has full access.
subscription.status === "trialing"andsubscription.trialEndis set. - Trial ends: The
processRenewals()cron finds expired trials and runs theonTrialEndbehavior:- Has payment method: Charges the first billing period, sets
currentPeriodStart/currentPeriodEnd, transitions to"active". - Charge fails: Transitions to
"past_due". - No payment method: Transitions to
"canceled".
- Has payment method: Charges the first billing period, sets
Plan Change During Trial
Changing plans during a trial ends the trial immediately. The customer is charged the full price of the new plan and a new billing period starts. No proration is applied since nothing was charged during the trial.