Stripe for SaaS: Complete Integration Guide for Subscriptions
Why Stripe
Stripe dominates SaaS payments because it handles everything:
- Card processing in 100+ countries
- Subscription management built-in
- Invoicing and tax calculation
- Dunning (failed payment recovery)
- Excellent developer experience
- Trusted by most SaaS companies
Alternatives (Paddle, Lemon Squeezy) are merchant-of-record, handling tax compliance but with higher fees. Use them if tax compliance is a major concern.
Core Concepts
Products & Prices
Product: Your plan (e.g., "Pro Plan")
Price: The amount and interval (e.g., "$20/month")
One product can have multiple prices (monthly, yearly, different currencies).
Customers
Stripe Customer = your user/organization. Link Stripe customer ID to your database.
Subscriptions
Ongoing billing relationship. Has a status (active, past_due, canceled) and current period.
Checkout Sessions
Hosted payment page. Redirect users to Stripe to complete payment.
Webhooks
Events Stripe sends to your server (payment succeeded, subscription canceled, etc.).
Implementation Steps
1. Create Products in Stripe Dashboard
Go to Products → Add Product. Create your plans with prices.
Example setup:
- Starter: $20/month, $200/year
- Pro: $50/month, $500/year
- Enterprise: Custom pricing
2. Create Checkout Session (Backend)
// API route: /api/checkout
const session = await stripe.checkout.sessions.create({
customer: stripeCustomerId, // or customer_email
mode: 'subscription',
line_items: [{
price: 'price_xxx', // Your Stripe price ID
quantity: 1,
}],
success_url: 'https://yourapp.com/billing?success=true',
cancel_url: 'https://yourapp.com/pricing',
});
return { url: session.url };3. Redirect to Checkout (Frontend)
const response = await fetch('/api/checkout', { method: 'POST' });
const { url } = await response.json();
window.location.href = url;4. Handle Webhooks
// API route: /api/webhooks/stripe
const event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET
);
switch (event.type) {
case 'checkout.session.completed':
// Create subscription in your database
break;
case 'customer.subscription.updated':
// Update subscription status
break;
case 'customer.subscription.deleted':
// Handle cancellation
break;
case 'invoice.payment_failed':
// Notify user, restrict access
break;
}
Database Schema
CREATE TABLE subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id), stripe_customer_id TEXT NOT NULL, stripe_subscription_id TEXT UNIQUE, status TEXT NOT NULL, -- 'active', 'past_due', 'canceled' plan TEXT NOT NULL, -- 'starter', 'pro' current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, cancel_at_period_end BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() );
Always store Stripe IDs. You'll need them for API calls and debugging.
Customer Portal
Stripe Customer Portal handles subscription management UI for free:
- Update payment method
- View invoices
- Upgrade/downgrade plans
- Cancel subscription
// API route: /api/billing-portal
const session = await stripe.billingPortal.sessions.create({
customer: stripeCustomerId,
return_url: 'https://yourapp.com/settings',
});
return { url: session.url };Configure the portal in Stripe Dashboard → Settings → Customer Portal.
Common Patterns
Free Trial
const session = await stripe.checkout.sessions.create({
// ...
subscription_data: {
trial_period_days: 14,
},
});Feature Gating
// Check plan before allowing feature
function canUseFeature(subscription, feature) {
const planFeatures = {
starter: ['basic_feature'],
pro: ['basic_feature', 'advanced_feature', 'api_access'],
};
return planFeatures[subscription.plan]?.includes(feature);
}Usage-Based Billing
Report usage to Stripe for metered billing:
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
quantity: apiCallsThisMonth,
timestamp: Math.floor(Date.now() / 1000),
action: 'set',
});
Testing Checklist
- Use Stripe test mode and test card numbers
- Test successful payment flow end-to-end
- Test failed payment (use declining test card)
- Test webhook handling (use Stripe CLI)
- Test subscription upgrade/downgrade
- Test cancellation flow
- Test Customer Portal access
- Verify database updates after each action
Stripe CLI for local webhook testing:
stripe listen --forward-to localhost:3000/api/webhooks/stripe