Stripe Customer Portal: Let Users Manage Their Own Subscriptions
Building subscription management UI yourself is a week of work. Stripe's Customer Portal gives you upgrade, downgrade, cancellation, and payment method updates — hosted by Stripe, no code required.
What the Portal Gives You
- Update payment method
- View billing history and download invoices
- Upgrade or downgrade plan
- Cancel subscription (with optional cancellation flow)
- Pause subscription (if enabled)
Setup
Configure the portal in Stripe Dashboard → Settings → Customer Portal. Set which plans are available for switching, cancellation behavior, etc.
Server: Create Portal Session
app.post('/api/billing/portal', requireAuth, async (req, res) => {
const user = await db.users.findUnique({
where: { id: req.userId },
select: { stripeCustomerId: true },
});
if (!user?.stripeCustomerId) {
return res.status(400).json({ error: 'No billing account found' });
}
const session = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: `${process.env.APP_URL}/settings/billing`,
});
res.json({ url: session.url });
});
Client: Redirect to Portal
function BillingSettings() {
const [loading, setLoading] = useState(false);
async function openBillingPortal() {
setLoading(true);
try {
const { url } = await fetch('/api/billing/portal', {
method: 'POST',
}).then(r => r.json());
window.location.href = url;
} finally {
setLoading(false);
}
}
return (
<button onClick={openBillingPortal} disabled={loading}>
{loading ? 'Loading...' : 'Manage Billing'}
</button>
);
}
Handling Portal Events via Webhooks
// Customer cancelled through portal
case 'customer.subscription.deleted':
await db.users.update({
where: { stripeCustomerId: event.data.object.customer as string },
data: { subscriptionStatus: 'cancelled', plan: 'free' },
});
break;
// Customer changed plan
case 'customer.subscription.updated':
const sub = event.data.object;
await db.users.update({
where: { stripeCustomerId: sub.customer as string },
data: {
plan: getPlanFromPriceId(sub.items.data[0].price.id),
subscriptionStatus: sub.status,
},
});
break;
Cancellation Flow Customization
In the Stripe Dashboard, you can:
- Require cancellation reason (valuable feedback)
- Offer pause instead of cancel
- Offer discount before cancellation
- Set a cancellation period (cancel at period end vs immediately)
The Customer Portal, webhook handlers, subscription lifecycle, and billing UI are fully implemented in the AI SaaS Starter Kit — zero billing UI to build yourself.
Top comments (0)