Leyla покупает в магазине книги и хочет «Prowid Shop Plus»: бесплатная доставка за 9.90 AZN в месяц. Нажимать «Оплатить» каждый месяц она не станет: деньги должны списываться сами, пока она не откажется. Это подписка, и у неё три сложности: регулярные списания, смена и отмена тарифа и карты, которые иногда не проходят. Всё это берёт на себя Stripe Billing, а нам остаётся хранить у себя состояние доступа.
Объекты подписки
Product — что продаём («Prowid Shop Plus»), Price — сколько и как часто (9.90 AZN, recurring.interval: 'month'), Customer — покупатель. Subscription — договор между покупателем и ценой: «списывать 9.90 AZN каждый месяц». Каждый период Stripe выпускает Invoice (счёт) и оплачивает его через PaymentIntent (урок 041). Аналогия: абонемент в спортзал: договор один, а счёт приходит каждый месяц.
Stripe хранит свои объекты, а ваша база — копию состояния: кто подписан и до какого числа. Приложение читает свою базу, а не Stripe.
Цена и Checkout в режиме подписки
Product и рекуррентный Price создают один раз, в Dashboard или скриптом:
// 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 .envId цены положите в .env как PLUS_PRICE_ID. Покупателю нужен Customer: создаём его при первой подписке и запоминаем stripeCustomerId в User. Затем Checkout Session с 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 });
}