6 min read

How to Set Up Subscription Management in Stripe

Recurring revenue stops being manual once you automate billing. Stripe subscriptions handle the mechanics—charging on a schedule, retrying failed payments, prorating mid-cycle changes, and tracking invoices. You build once, Stripe handles the rest.

Create Products and Pricing Plans

Start by defining what you're selling and how much it costs. Products are what you offer; prices attach amounts and billing frequency to them.

Create a product

In the Stripe Dashboard, go to Catalog > Products and click Add product. Enter a name ("Premium Plan"), optional description, and choose Service for software. Hit save. Stripe assigns a product ID you'll reference in code.

javascript
const stripe = require('stripe')('sk_live_...');

const product = await stripe.products.create({
  name: 'Premium Plan',
  description: 'Unlimited analytics and integrations',
  type: 'service',
});
Use this product ID for all pricing tiers of the same offering.

Add a monthly price

In Catalog > Prices, click Add price. Select the product, enter the amount in cents (9999 = $99.99), and set Recurring to Monthly. Stripe uses this price object to bill customers on a schedule.

javascript
const price = await stripe.prices.create({
  product: product.id,
  unit_amount: 9999,
  currency: 'usd',
  recurring: {
    interval: 'month',
    interval_count: 1,
  },
});
interval can be 'day', 'week', 'month', or 'year'. interval_count multiplies the interval (e.g., 3 months).

Create annual pricing for upsell

Add another price for the same product at an annual interval. Customers often prefer annual plans if you discount them 20%. In the Dashboard, add another Recurring price with Yearly selected.

javascript
const annualPrice = await stripe.prices.create({
  product: product.id,
  unit_amount: 99900, // $999/year = ~$83/month
  currency: 'usd',
  recurring: {
    interval: 'year',
    interval_count: 1,
  },
});
Tip: Keep prices as separate objects, not variants. This lets customers switch between monthly and annual without recreating their subscription.

Create Customers and Subscriptions

A customer record ties billing to a person. Subscriptions link customers to prices and set the billing schedule.

Create a customer record

In Customers, click Add customer or create via API. Store their email, name, and any internal IDs in metadata. You'll need the customer ID to create a subscription.

javascript
const customer = await stripe.customers.create({
  email: '[email protected]',
  name: 'Jane Doe',
  metadata: {
    userId: '12345', // Your internal user ID
    accountTier: 'pro',
  },
});
Metadata is key for syncing Stripe records back to your database.

Create a subscription

Link the customer to a price. Use payment_behavior: 'default_incomplete' to handle payment asynchronously—useful for web flows where you confirm payment on the frontend after the customer fills in their card. Expand latest_invoice.payment_intent to check payment status.

javascript
const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  items: [
    { price: price.id },
  ],
  payment_behavior: 'default_incomplete',
  expand: ['latest_invoice.payment_intent'],
});

const paymentIntent = subscription.latest_invoice.payment_intent;
if (paymentIntent?.status === 'requires_action') {
  // Return client_secret to frontend for payment confirmation
  return { clientSecret: paymentIntent.client_secret };
}
requires_action means 3D Secure or SCA verification is needed. Handle this on the frontend.

Confirm payment on the frontend

Use Stripe.js and confirmCardPayment() to prompt the customer for payment confirmation. Once confirmed, the subscription activates and recurring billing begins.

javascript
// Frontend code
const stripe = Stripe('pk_live_...');
const { clientSecret } = await createSubscription();

const { paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
  payment_method: {
    card: cardElement, // Stripe Element
    billing_details: { name: 'Jane Doe' },
  },
});

if (paymentIntent.status === 'succeeded') {
  console.log('Subscription active');
}
Watch out: payment_behavior: 'default_incomplete' means the subscription won't be active until payment succeeds. Don't grant access to the product yet.

Manage Subscriptions Over Time

Customers upgrade, downgrade, and cancel. Handle these changes without losing revenue or data.

Upgrade or downgrade a plan

Use the subscription update endpoint. Provide the subscription ID and new price. Stripe prorates by default—if upgrading mid-cycle, the customer is charged the difference immediately. If downgrading, they get a credit toward the next cycle.

javascript
const updated = await stripe.subscriptions.update(subscription.id, {
  items: [
    {
      id: subscription.items.data[0].id, // Get current item ID
      price: newPriceId,
    },
  ],
  proration_behavior: 'create_prorations', // Adjust charges now
});
Set proration_behavior to 'none' if you want to bill the new price only on the next cycle.

Check upcoming charges

Before a customer renews, see what they'll be charged. Use invoices.retrieveUpcoming() to peek at the next invoice without creating it. This is useful for showing "renewal date" on your UI.

javascript
const upcoming = await stripe.invoices.retrieveUpcoming({
  customer: customer.id,
});

const renewalDate = new Date(upcoming.next_payment_attempt * 1000);
const amount = (upcoming.amount_due / 100).toFixed(2);
console.log(`Next charge: $${amount} on ${renewalDate.toLocaleDateString()}`);

Handle failed payments

Set up webhooks for invoice.payment_failed and invoice.payment_succeeded. Stripe retries failed payments automatically (3x over 3 days by default), but you should email the customer and prompt them to update their card.

javascript
const express = require('express');
const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const event = JSON.parse(req.body);

  if (event.type === 'invoice.payment_failed') {
    const invoice = event.data.object;
    const customerId = invoice.customer;
    console.log(`Payment failed for ${customerId}. Amount: $${invoice.amount_due / 100}`);
    // Send email: "Your payment failed. Update your card: ..."
  }

  if (event.type === 'invoice.payment_succeeded') {
    const invoice = event.data.object;
    console.log(`Charged ${invoice.customer} $${invoice.amount_paid / 100}`);
    // Log revenue, update metrics
  }

  res.json({ received: true });
});

Cancel a subscription

In Customers > [Customer] > Subscriptions, click the subscription and select Cancel subscription. Toggle Refund if canceling mid-cycle to credit the customer for unused time. In code, use the delete endpoint.

javascript
const canceled = await stripe.subscriptions.del(subscription.id, {
  proration_behavior: 'create_prorations', // Issue refund for unused time
});

console.log(`Subscription ${canceled.id} canceled on ${new Date(canceled.canceled_at * 1000).toLocaleDateString()}`);
Tip: Always sync via webhooks, not polling. Webhooks deliver events in real-time when Stripe updates a subscription, invoice, or charge. Polling is slow and expensive.

Common Pitfalls

  • Creating subscriptions with payment_behavior: 'default_incomplete' but not handling requires_action status—the subscription sits incomplete and the customer never gets charged.
  • Forgetting to expand latest_invoice.payment_intent when creating subscriptions—you won't see payment status and can't return the client secret to the frontend.
  • Not setting up webhooks for billing events—you'll miss failed payments, cancellations, and invoice updates. Polling the API is unreliable.
  • Ignoring proration_behavior during updates—Stripe defaults to creating prorations, which charges immediately, but some businesses need to bill on the next cycle instead.

Wrapping Up

You now have a production-grade subscription system: products with tiered pricing, customers you charge reliably, and tools to handle upgrades, cancellations, and failed payments without manual work. Recurring revenue scales as your customer base grows. If you want to track MRR, churn, and cohort retention across Stripe and your other tools, Product Analyst can help you connect and analyze the data.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free