Stripe Integration
The subscription system integrates deeply with Stripe for secure payment processing, subscription management, and billing automation. This guide covers the complete Stripe integration setup and usage.
Overview
The Stripe integration handles:
- Payment processing via Stripe Checkout
- Subscription lifecycle management through webhooks
- Customer portal for self-service billing
- Invoice and payment tracking
- Plan synchronization between your app and Stripe
Stripe Configuration
Environment Variables
Configure your Stripe integration with these environment variables:
# Stripe API Keys
STRIPE_SECRET_KEY=sk_test_... # Your Stripe secret key
STRIPE_PUBLISHABLE_KEY=pk_test_... # Your Stripe publishable key
# Webhook Configuration
STRIPE_WEBHOOK_SECRET=whsec_... # Webhook endpoint secret
# Plan Price IDs (created in Stripe Dashboard)
STRIPE_STARTER_PRICE_ID=price_starter_monthly
STRIPE_STARTER_ANNUAL_PRICE_ID=price_starter_annual
STRIPE_PRO_PRICE_ID=price_pro_monthly
STRIPE_PRO_ANNUAL_PRICE_ID=price_pro_annual
STRIPE_ENTERPRISE_PRICE_ID=price_enterprise_monthly
STRIPE_ENTERPRISE_ANNUAL_PRICE_ID=price_enterprise_annual
Stripe Dashboard Setup
- Create Products in your Stripe Dashboard for each subscription plan
- Create Prices for monthly and annual billing cycles
- Configure Webhooks to point to your application endpoint
- Set up Customer Portal for self-service billing
Webhook Handling
Webhook Endpoint
The system processes Stripe webhooks at:
POST /api/webhooks/stripe
Supported Events
The webhook handler processes these Stripe events:
Checkout Completed
// Event: checkout.session.completed
// Triggered when a customer completes checkout
Subscription Created
// Event: customer.subscription.created
// Creates new subscription in database
Subscription Updated
// Event: customer.subscription.updated
// Updates subscription (plan changes, seat changes, etc.)
Subscription Deleted
// Event: customer.subscription.deleted
// Marks subscription as canceled
Payment Events
// Event: invoice.payment_succeeded
// Updates subscription status to active
// Event: invoice.payment_failed
// Updates subscription status to past_due
Webhook Processing Flow
- Verify webhook signature using Stripe webhook secret
- Parse event data and determine event type
- Process event based on type (create, update, delete subscription)
- Update database with new subscription state
- Return success response to Stripe
Webhook Security
The webhook handler includes security measures:
// Signature verification
const signature = getHeader(event, 'stripe-signature')
const stripeEvent = stripe.webhooks.constructEvent(
body,
signature,
webhookSecret
)
Error Handling
Webhook processing includes comprehensive error handling:
- Signature verification failures return 400 status
- Missing webhook secret returns 500 status
- Processing errors are logged and return 500 status
- Idempotent processing prevents duplicate operations
Checkout Integration
Creating Checkout Sessions
Create Stripe checkout sessions for subscription purchases:
// API: POST /api/subscriptions/checkout
const checkoutSession = await createCheckoutSession({
planId: 'plan_123',
billingInterval: 'monthly',
organizationId: 'org_456',
successUrl: 'https://yourapp.com/success',
cancelUrl: 'https://yourapp.com/cancel',
seats: 10
}, userInfo)
Checkout Session Configuration
The checkout session includes:
{
mode: 'subscription',
customer: stripeCustomerId,
line_items: [{
price: stripePriceId,
quantity: seats || 1
}],
metadata: {
organizationId,
planId,
userId,
seats: seats?.toString()
},
subscription_data: {
metadata: {
organizationId,
planId,
userId,
seats: seats?.toString()
},
trial_period_days: plan.trialDays || undefined
},
success_url: successUrl,
cancel_url: cancelUrl
}
Frontend Integration
// Create checkout session
const response = await fetch('/api/subscriptions/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
planId: 'plan_pro_123',
billingInterval: 'monthly',
organizationId: currentOrganization.id
})
})
const { data } = await response.json()
// Redirect to Stripe Checkout
window.location.href = data.url
Customer Portal
Billing Portal Access
Provide customers with self-service billing management:
// API: POST /api/subscriptions/billing-portal
const portalSession = await stripe.billingPortal.sessions.create({
customer: stripeCustomerId,
return_url: returnUrl
})
Portal Features
The Stripe Customer Portal allows customers to:
- Update payment methods
- Download invoices
- View billing history
- Update billing information
- Cancel subscriptions
- Reactivate canceled subscriptions
Portal Configuration
Configure the portal in your Stripe Dashboard:
- Enable features you want customers to access
- Set business information (name, logo, support details)
- Configure cancellation flow
- Set up email notifications
Subscription Management
Plan Changes
Handle subscription plan changes through Stripe:
// Upgrade/downgrade subscription
await stripe.subscriptions.update(stripeSubscriptionId, {
items: [{
id: subscriptionItemId,
price: newStripePriceId
}],
proration_behavior: 'create_prorations'
})
Seat-Based Billing
For seat-based plans, update quantity:
// Update seat count
await stripe.subscriptions.update(stripeSubscriptionId, {
items: [{
id: subscriptionItemId,
quantity: newSeatCount
}],
proration_behavior: 'create_prorations'
})
Cancellation
Cancel subscriptions with different strategies:
// Cancel at period end
await stripe.subscriptions.update(stripeSubscriptionId, {
cancel_at_period_end: true
})
// Cancel immediately
await stripe.subscriptions.cancel(stripeSubscriptionId)
Data Synchronization
Subscription Status Mapping
Stripe subscription statuses map to application statuses:
Stripe Status | App Status | Description |
---|---|---|
incomplete | incomplete | Payment not processed |
incomplete_expired | incomplete_expired | Payment failed and expired |
trialing | trialing | In trial period |
active | active | Active and paid |
past_due | past_due | Payment failed but active |
canceled | canceled | Subscription canceled |
unpaid | unpaid | Payment failed, suspended |
Metadata Synchronization
Important metadata is stored in Stripe subscriptions:
metadata: {
organizationId: 'org_123',
planId: 'plan_456',
userId: 'user_789',
seats: '10'
}
Billing Interval Detection
The system determines billing intervals from Stripe data:
async function determineBillingInterval(subscription: any): Promise<'monthly' | 'annual' | null> {
const priceId = subscription.items.data[0].price.id
const plans = await getAllSubscriptionPlans()
for (const plan of plans) {
if (plan.stripePriceIdMonthly === priceId) return 'monthly'
if (plan.stripePriceIdAnnual === priceId) return 'annual'
}
// Fallback to Stripe price interval
const interval = subscription.items.data[0].price.recurring?.interval
return interval === 'month' ? 'monthly' : interval === 'year' ? 'annual' : null
}
Error Handling
Common Stripe Errors
Handle common Stripe API errors:
try {
await stripe.subscriptions.create(subscriptionData)
} catch (error) {
if (error.type === 'StripeCardError') {
// Card was declined
} else if (error.type === 'StripeInvalidRequestError') {
// Invalid parameters
} else if (error.type === 'StripeAPIError') {
// Stripe API error
} else if (error.type === 'StripeConnectionError') {
// Network error
}
}
Webhook Error Recovery
Implement webhook error recovery:
- Log all webhook events for debugging
- Implement idempotent processing to handle retries
- Use Stripe event IDs to prevent duplicate processing
- Monitor webhook delivery in Stripe Dashboard
Testing
Test Mode Configuration
Use Stripe test mode for development:
# Test API Keys
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
Test Cards
Use Stripe test cards for testing:
// Successful payment
'4242424242424242'
// Declined payment
'4000000000000002'
// Requires authentication
'4000002500003155'
Webhook Testing
Test webhooks locally using Stripe CLI:
# Install Stripe CLI
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Trigger test events
stripe trigger customer.subscription.created
Monitoring and Analytics
Stripe Dashboard
Monitor your subscription business through Stripe Dashboard:
- Revenue metrics and MRR tracking
- Customer lifecycle analytics
- Churn and retention reports
- Failed payment monitoring
Application Metrics
Track subscription metrics in your application:
// Example metrics to track
const metrics = {
activeSubscriptions: await getActiveSubscriptionCount(),
trialConversions: await getTrialConversionRate(),
churnRate: await getChurnRate(),
averageRevenuePerUser: await getARPU()
}
Security Best Practices
API Key Management
- Never expose secret keys in client-side code
- Use environment variables for API keys
- Rotate keys regularly for security
- Use restricted API keys when possible
Webhook Security
- Always verify webhook signatures
- Use HTTPS endpoints for webhooks
- Implement rate limiting on webhook endpoints
- Log and monitor webhook activity
Customer Data
- Follow PCI compliance guidelines
- Never store card data in your database
- Use Stripe's secure vaults for payment methods
- Implement proper access controls
Troubleshooting
Common Issues
- Webhook signature verification fails
- Check webhook secret configuration
- Verify endpoint URL in Stripe Dashboard
- Subscription not created after payment
- Check webhook event processing
- Verify metadata is correctly set
- Plan upgrades not working
- Ensure Stripe price IDs are correct
- Check proration settings
- Customer portal not accessible
- Verify customer has valid Stripe customer ID
- Check portal configuration in Stripe
Debug Tools
- Stripe Dashboard event logs
- Application logs for webhook processing
- Stripe CLI for local testing
- Browser developer tools for checkout issues
This comprehensive Stripe integration provides a robust foundation for subscription billing in your SaaS application.