In this section, we'll delve into the powerful world of Stripe subscriptions, enabling you to build robust recurring billing models directly within your Next.js application. This involves creating subscription plans, associating them with customers, and managing their lifecycle. We'll cover the essential steps to get your subscription system up and running.
The core of Stripe subscriptions lies in two main entities: Products and Prices. A Product represents the service or good you're selling (e.g., 'Pro Plan', 'Basic Access'), and a Price defines the cost and billing interval for that product (e.g., '100/year'). You'll typically set these up in your Stripe dashboard, but Stripe's API allows for programmatic creation as well.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function createProductAndPrice() {
try {
// Create a Product
const product = await stripe.products.create({
name: 'Premium Subscription',
description: 'Unlock all features for a premium experience.',
});
// Create a Price for the Product
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2000, // $20.00 in cents
currency: 'usd',
recurring: {
interval: 'month',
},
});
console.log('Product created:', product);
console.log('Price created:', price);
return { product, price };
} catch (error) {
console.error('Error creating product and price:', error);
throw error;
}
}Once you have your products and prices defined, the next crucial step is to create a Stripe Customer. A customer object in Stripe represents your user, and it's where their subscription information will be attached. It's good practice to create a customer when a user first signs up or as part of the checkout flow.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function createStripeCustomer(email, name) {
try {
const customer = await stripe.customers.create({
email: email,
name: name,
// You can add other metadata here, like your internal user ID
metadata: {
userId: 'user_123',
},
});
console.log('Stripe Customer created:', customer);
return customer;
} catch (error) {
console.error('Error creating Stripe customer:', error);
throw error;
}
}With a customer in hand, you can now create a subscription. This involves linking the Stripe Customer to a specific Price. This is often initiated from your frontend when a user chooses a plan and proceeds to checkout. The stripe.subscriptions.create method is your gateway to this.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function createSubscription(customerId, priceId) {
try {
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [
{
price: priceId,
},
],
// Optional: Add trial period, metadata, etc.
// trial_period_days: 7,
// metadata: { order_id: '6735' },
});
console.log('Subscription created:', subscription);
return subscription;
} catch (error) {
console.error('Error creating subscription:', error);
throw error;
}
}When a user clicks to subscribe, the typical flow involves creating a Stripe Customer (if they don't exist) and then creating the subscription. You'll need to pass the customer ID and the relevant price ID to your backend API endpoint that handles this logic.
graph TD
A[User Clicks Subscribe] --> B{Backend: Create/Get Customer}
B -- New Customer --> C[Stripe: Create Customer]
B -- Existing Customer --> D[Stripe: Get Customer]
C --> E[Backend: Store Customer ID]
D --> E
E --> F[Backend: Create Subscription]
F -- Success --> G[Frontend: Confirmation]
F -- Failure --> H[Frontend: Error Message]
F --> I[Stripe: Create Subscription]
For managing subscriptions, Stripe provides webhooks. These are essential for keeping your application's state in sync with Stripe's. Key events include customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted. You'll set up an API endpoint in your Next.js app to receive these events and perform actions like updating user roles or sending notifications.
import { buffer } from 'micro';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export default async (req, res) => {
if (req.method === 'POST') {
let event;
try {
const sig = req.headers['stripe-signature'];
const buf = await buffer(req);
event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
console.error(`Webhook error: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'customer.subscription.created':
const subscriptionCreated = event.data.object;
console.log(`Subscription created for customer: ${subscriptionCreated.customer}`);
// Update your database: grant access, set user role, etc.
break;
case 'customer.subscription.updated':
const subscriptionUpdated = event.data.object;
console.log(`Subscription updated for customer: ${subscriptionUpdated.customer}`);
// Handle updates (e.g., plan changes, cancellations)
break;
case 'customer.subscription.deleted':
const subscriptionDeleted = event.data.object;
console.log(`Subscription deleted for customer: ${subscriptionDeleted.customer}`);
// Update your database: revoke access, etc.
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.json({ received: true });
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}To enable users to manage their subscriptions (e.g., cancel, update payment method), Stripe provides Billing Customer Portal. This is a secure, pre-built UI that you can link to. You'll generate a portal session from your backend and redirect your user to it.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function createBillingPortalSession(customerStripeId) {
try {
const session = await stripe.billingPortal.sessions.create({
customer: customerStripeId,
return_url: 'https://your-app.com/account',
});
console.log('Billing portal session created:', session.url);
return session.url;
} catch (error) {
console.error('Error creating billing portal session:', error);
throw error;
}
}