Leyla buys books in the shop and wants "Prowid Shop Plus": free delivery for 9.90 AZN a month. She will not press "Pay" every month: the money has to be charged by itself until she opts out. This is a subscription, and it brings three difficulties: regular charges, changing and canceling the plan, and cards that sometimes fail. Stripe Billing takes care of all this, and we only have to keep the access state in our own database.
Subscription objects
A Product is what we sell ("Prowid Shop Plus"), a Price is how much and how often (9.90 AZN, recurring.interval: 'month'), a Customer is the buyer. A Subscription is a contract between the buyer and the price: "charge 9.90 AZN every month". Every period Stripe issues an Invoice and pays it through a PaymentIntent (lesson 041). An analogy: a gym membership: there is one contract, but a bill arrives every month.
Stripe keeps its own objects, while your database keeps a copy of the state: who is subscribed and until when. The app reads your database, not Stripe.
The price and Checkout in subscription mode
The Product and the recurring Price are created once, in the Dashboard or with a script:
// scripts/create-plus.js (run once)
import { stripe } from '../src/stripe.js';
const product = await stripe.products.create({ name: 'Prowid Shop Plus' });
const price = await stripe.prices.create({
product: product.id,
currency: 'azn',
unit_amount: 990, // 9.90 AZN every month
recurring: { interval: 'month' },
});
console.log(price.id); // price_... -> PLUS_PRICE_ID in .envPut the price id into .env as PLUS_PRICE_ID. The buyer needs a Customer: we create it on the first subscription and remember stripeCustomerId in User. Then a Checkout Session with mode: 'subscription':
// src/billing.js
import { stripe } from './stripe.js';
import { prisma } from './db.js';
import { handleOnce } from './idempotent.js';
export async function startSubscription(req, res) {
const user = req.user; // from your auth middleware, never from the body
let customerId = user.stripeCustomerId;
if (!customerId) {
const customer = await stripe.customers.create(
{ email: user.email, metadata: { userId: String(user.id) } },
{ idempotencyKey: `user-${user.id}-customer` },
);
customerId = customer.id;
await prisma.user.update({
where: { id: user.id },
data: { stripeCustomerId: customerId },
});
}
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customerId,
line_items: [{ price: process.env.PLUS_PRICE_ID, quantity: 1 }],
success_url: `${process.env.APP_URL}/plus/thanks`,
cancel_url: `${process.env.APP_URL}/plus`,
});
res.json({ url: session.url });
}