Recurring revenue requires tracking customers, billing cycles, and payment methods—all of which Stripe handles through subscriptions. Rather than building custom logic for when to charge, how much to charge, and what happens when payments fail, Stripe's subscription system automates these workflows. If you're running a SaaS or membership business, understanding subscriptions is essential.
How to Set Up a Basic Subscription
A subscription in Stripe connects a customer, a product/price, and a payment method. Here's how the pieces fit together.
Create or retrieve a customer
Every subscription needs a customer. Use stripe.customers.create() to add a new customer to Stripe, or fetch an existing one with stripe.customers.retrieve(). Store the customer ID—you'll need it for the subscription.
const customer = await stripe.customers.create({
email: '[email protected]',
name: 'Jane Doe',
metadata: { user_id: '12345' }
});Attach a payment method to the customer
Before Stripe can charge the customer, you need to attach a payment method (credit card, bank account, etc.). Use stripe.paymentMethods.attach() to link the payment method to the customer.
await stripe.paymentMethods.attach(
'pm_card_visa',
{ customer: customer.id }
);
await stripe.customers.update(
customer.id,
{ invoice_settings: { default_payment_method: 'pm_card_visa' } }
);Create a subscription
Now create the subscription by specifying the customer, the price, and the billing cycle. Stripe will charge on the interval you set (monthly, annual, etc.). Use stripe.subscriptions.create() to start the subscription.
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{
price: 'price_1234567890',
}],
payment_behavior: 'error_if_incomplete'
});payment_behavior: 'error_if_incomplete' to prevent subscriptions from starting if the first payment fails. This avoids creating a subscription with a failed payment.Modifying Subscriptions
After a customer subscribes, you'll often need to change the quantity, upgrade to a different price, or adjust the billing amount. Stripe handles proration automatically.
Update the subscription price or quantity
Use stripe.subscriptions.update() to change what the customer is paying for. You can change the quantity, the price, or both. Stripe calculates the prorated amount based on the remaining billing period.
const updated = await stripe.subscriptions.update(
subscription.id,
{
items: [{
id: subscription.items.data[0].id,
price: 'price_upgraded_plan',
}],
proration_behavior: 'create_prorations'
}
);Handle proration and billing
When you update a subscription mid-cycle, Stripe calculates the difference between what the customer already paid and what they owe. Set proration_behavior: 'create_prorations' to bill the difference immediately, or use 'none' to skip the charge and wait until the next billing cycle.
await stripe.subscriptions.update(
subscription.id,
{
items: [{ id: item.id, quantity: 5 }],
proration_behavior: 'create_prorations',
billing_cycle_anchor: 'unchanged'
}
);payment_settings, the new terms apply at the next renewal, not immediately.Canceling and Ending Subscriptions
Subscriptions end in two ways: immediate cancellation (customer stops being charged) or at the end of the current billing period (grace period).
Cancel a subscription immediately
Use stripe.subscriptions.del() to stop a subscription right away. The customer won't be charged again, but they'll lose access when your app revokes it. This is the standard for when a customer cancels.
const canceled = await stripe.subscriptions.del(
subscription.id
);Set a cancellation date instead
To give customers a grace period (e.g., they lose access at the end of the month), use stripe.subscriptions.update() with cancel_at set to a future timestamp. The subscription cancels automatically at that time.
const gracePeriod = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60);
await stripe.subscriptions.update(
subscription.id,
{ cancel_at: gracePeriod }
);customer.subscription.deleted webhook to detect when subscriptions end, so you can sync your app and revoke access at the right time.Common Pitfalls
- Forgetting to set a default payment method before creating a subscription—Stripe will create it, but it won't charge until you attach one.
- Not handling proration correctly when upgrading mid-cycle—use
proration_behavior: 'create_prorations'to bill the difference immediately, or'none'to defer. - Relying on subscription status from the API instead of webhooks—your app can drift out of sync if you don't listen for
customer.subscription.updatedandcustomer.subscription.deletedevents. - Canceling a subscription without immediately revoking access in your app—the subscription stops, but the customer can still use the product until you remove access.
Wrapping Up
Subscription management in Stripe automates the complexity of recurring billing—you define the product, customer, and payment method, and Stripe handles the rest. Proration, retries, and dunning are built-in. If you want to track this automatically across tools, Product Analyst can help.