Billing Quickstart Fresh
Build a prebuilt subscription page using Stripe Checkout and Stripe Billing.
Overview
The recommended pattern:
- Customer clicks checkout on your pricing page
- Redirected to Stripe-hosted Checkout
- Stripe creates the customer, subscription, and handles payment
- Customer redirected to your success page
- Webhook fires — you provision access
Step 1: Create products and prices
Create your subscription product in Dashboard → Product catalog → Create product. Set the pricing model (flat rate, per-seat, etc.) and billing interval.
Or via API:
bash
curl https://api.stripe.com/v1/products \
-u "sk_test_YOUR_KEY:" \
-d name="Pro Plan"
curl https://api.stripe.com/v1/prices \
-u "sk_test_YOUR_KEY:" \
-d product=prod_123 \
-d unit_amount=2000 \
-d currency=usd \
-d "recurring[interval]=month" \
-d lookup_key=pro_monthlyStep 2: Create a Checkout Session
javascript
const stripe = require('stripe')('sk_test_YOUR_KEY');
const express = require('express');
const app = express();
app.post('/create-checkout-session', async (req, res) => {
const prices = await stripe.prices.list({
lookup_keys: [req.body.lookup_key],
expand: ['data.product'],
});
const session = await stripe.checkout.sessions.create({
billing_address_collection: 'auto',
line_items: [{ price: prices.data[0].id, quantity: 1 }],
mode: 'subscription',
success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${YOUR_DOMAIN}/cancel`,
});
res.redirect(303, session.url);
});Step 3: Handle webhook events
Register a webhook endpoint in Dashboard → Workbench → Webhooks.
javascript
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const endpointSecret = 'whsec_YOUR_SECRET';
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
return res.sendStatus(400);
}
switch (event.type) {
case 'customer.subscription.created':
// Provision access for new subscriber
break;
case 'customer.subscription.updated':
// Handle plan changes
break;
case 'customer.subscription.deleted':
// Revoke access
break;
case 'customer.subscription.trial_will_end':
// Send reminder, collect payment method if missing
break;
case 'entitlements.active_entitlement_summary.updated':
// Update feature access based on new entitlements
break;
}
res.send();
});Step 4: Customer portal
Let customers manage their subscriptions without contacting support.
javascript
app.post('/create-portal-session', async (req, res) => {
const checkoutSession = await stripe.checkout.sessions.retrieve(req.body.session_id);
const returnUrl = YOUR_DOMAIN;
const portalSession = await stripe.billingPortal.sessions.create({
customer: checkoutSession.customer,
return_url: returnUrl,
});
res.redirect(303, portalSession.url);
});Test cards
| Scenario | Card number |
|---|---|
| Payment succeeds | 4242424242424242 |
| Requires 3DS | 4000002500003155 |
| Payment declined | 4000000000009995 |
Optional features
Add a free trial
javascript
const session = await stripe.checkout.sessions.create({
// ...
subscription_data: { trial_period_days: 14 },
});If you start a trial without collecting a payment method, set trial_settings[end_behavior][missing_payment_method] to pause or cancel.
Apply a coupon
javascript
const session = await stripe.checkout.sessions.create({
// ...
discounts: [{ coupon: 'COUPON_ID' }],
});Set billing cycle anchor
javascript
const session = await stripe.checkout.sessions.create({
// ...
subscription_data: { billing_cycle_anchor: 1672531200 },
});Automatic tax
javascript
const session = await stripe.checkout.sessions.create({
// ...
automatic_tax: { enabled: true },
});