Stripe Integration
Complete guide to the tiered subscription system with Stripe integration, webhook automation, billing management, and programmatic authorization.
RankThis implements a sophisticated 3-tier subscription system with Stripe, featuring monthly/yearly billing options, automated lifecycle management, and server-side authorization.
✅ What's Included
- • 3-tier pricing (Free, Pro, Ultra)
- • Monthly and yearly billing options
- • Secure checkout flow with Stripe
- • Webhook event handling
- • Subscription lifecycle automation
- • Billing email notifications
- • Payment failure handling
- • Automated subscription sync
🔧 Built-in Features
- • Plan upgrades/downgrades
- • Proration handling
- • Trial period support
- • Multiple payment methods
- • Server-side authorization
- • Admin dashboard integration
- • MRR and analytics tracking
- • Programmatic tier-based access
The system uses a programmatic, type-safe architecture with centralized configuration and server-side authorization.
Free Tier
- • No Stripe interaction
- • Database-only tracking
- • Basic feature access
- • Email support level
Pro Tier
- • $20/month - $200/year
- • Stripe subscription
- • Advanced features
- • Priority support
Ultra Tier
- • $50/month - $500/year
- • Enterprise features
- • Dedicated support
- • Team collaboration
Data Flow
User selects plan → Stripe checkout → Payment → Webhook → Database update → Authorization update → Email notification
Configure your environment variables for the tiered pricing system:
# Stripe API KeysSTRIPE_SECRET_KEY="sk_test_your-stripe-secret-key"STRIPE_WEBHOOK_SECRET="whsec_your-webhook-secret"NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_your-publishable-key"# Subscription Price IDs (create these in Stripe Dashboard)STRIPE_PRICE_PRO_MONTHLY="price_xxx" # Pro monthly planSTRIPE_PRICE_PRO_YEARLY="price_xxx" # Pro yearly planSTRIPE_PRICE_ULTRA_MONTHLY="price_xxx" # Ultra monthly planSTRIPE_PRICE_ULTRA_YEARLY="price_xxx" # Ultra yearly plan# SecurityCRON_SECRET="some-random-secret-string" # For sync jobs
💡 Pro Tip
In development, the Price IDs are optional to allow easier server startup. In production, all Price IDs are required for the subscription system to work.
Set up your products and prices in the Stripe Dashboard:
1. Create Products
In your Stripe Dashboard, create two products:
- • "Pro Plan" - For your Pro tier features
- • "Ultra Plan" - For your Ultra tier features
2. Create Prices
For each product, create both monthly and yearly prices:
Pro Plan Prices
- • Monthly: $20.00
- • Yearly: $200.00 (17% savings)
Ultra Plan Prices
- • Monthly: $50.00
- • Yearly: $500.00 (17% savings)
3. Copy Price IDs
After creating prices, copy the Price IDs (starting with price_) and add them to your environment variables.
Implementation
The pricing section displays plans and handles subscription creation:
1import { PricingSection } from "~/components/PricingSection";23// In your page component4export default async function HomePage() {5 const session = await auth();67 // Get user's current subscription8 let subscription = null;9 if (session?.user?.id) {10 subscription = await db.subscription.findUnique({11 where: { userId: session.user.id },12 });13 }1415 return (16 <div>17 {/* Other content */}1819 <PricingSection20 session={session}21 subscription={subscription}22 showHeader={true}23 />24 </div>25 );26}
The PricingSection automatically shows the correct UI based on user authentication and subscription status.
Users are redirected to Stripe Checkout for secure payment processing:
1// Checkout API route2import { auth } from "~/auth";3import { stripe } from "~/server/lib/stripe";45export async function POST(request: Request) {6 const session = await auth();7 if (!session?.user?.email) {8 return new Response("Unauthorized", { status: 401 });9 }1011 const { priceId } = await request.json();1213 try {14 const checkoutSession = await stripe.checkout.sessions.create({15 customer_email: session.user.email,16 line_items: [17 {18 price: priceId,19 quantity: 1,20 },21 ],22 mode: "subscription",23 success_url: `${process.env.NEXTAUTH_URL}/account?success=true`,24 cancel_url: `${process.env.NEXTAUTH_URL}/#pricing`,25 metadata: {26 userId: session.user.id,27 },28 });2930 return Response.json({ url: checkoutSession.url });31 } catch (error) {32 return new Response("Internal Server Error", { status: 500 });33 }34}
Webhook Integration
Configure webhooks in Stripe Dashboard to receive subscription events:
- 1. Go to Developers → Webhooks in Stripe Dashboard
- 2. Click "Add endpoint"
- 3. Add your webhook URL:
https://yourdomain.com/api/webhooks/stripe - 4. Select these events:
- •
customer.subscription.created - •
customer.subscription.updated - •
customer.subscription.deleted - •
invoice.payment_succeeded - •
invoice.payment_failed
- •
- 5. Copy the webhook signing secret to
STRIPE_WEBHOOK_SECRET
The webhook handler processes Stripe events and updates your database:
1import { stripe } from "~/server/lib/stripe";2import { db } from "~/server/db";34export async function POST(request: Request) {5 const body = await request.text();6 const signature = request.headers.get("stripe-signature")!;78 let event;9 try {10 event = stripe.webhooks.constructEvent(11 body,12 signature,13 process.env.STRIPE_WEBHOOK_SECRET!14 );15 } catch (error) {16 return new Response("Webhook signature verification failed", {17 status: 40018 });19 }2021 switch (event.type) {22 case "customer.subscription.created":23 case "customer.subscription.updated":24 await handleSubscriptionUpdate(event.data.object);25 break;2627 case "customer.subscription.deleted":28 await handleSubscriptionCancellation(event.data.object);29 break;3031 case "invoice.payment_succeeded":32 await handlePaymentSuccess(event.data.object);33 break;3435 case "invoice.payment_failed":36 await handlePaymentFailure(event.data.object);37 break;38 }3940 return new Response("Webhook processed", { status: 200 });41}
Subscription Management
Stripe Customer Portal provides self-service billing management:
1// Customer portal server action2import { stripe } from "~/server/lib/stripe";3import { auth } from "~/auth";45export async function createPortalSession() {6 const session = await auth();7 if (!session?.user?.id) {8 throw new Error("Unauthorized");9 }1011 // Get user's Stripe customer ID12 const subscription = await db.subscription.findUnique({13 where: { userId: session.user.id },14 });1516 if (!subscription?.stripeCustomerId) {17 throw new Error("No subscription found");18 }1920 const portalSession = await stripe.billingPortal.sessions.create({21 customer: subscription.stripeCustomerId,22 return_url: `${process.env.NEXTAUTH_URL}/account`,23 });2425 return portalSession.url;26}
The Customer Portal allows users to update payment methods, download invoices, and manage their subscription.
Handle plan upgrades and downgrades with proper proration:
1export async function changePlan(newPriceId: string) {2 const session = await auth();3 if (!session?.user?.id) {4 throw new Error("Unauthorized");5 }67 const subscription = await db.subscription.findUnique({8 where: { userId: session.user.id },9 });1011 if (!subscription?.stripeSubscriptionId) {12 throw new Error("No active subscription");13 }1415 // Update subscription in Stripe16 const updatedSubscription = await stripe.subscriptions.update(17 subscription.stripeSubscriptionId,18 {19 items: [{20 id: subscription.stripeSubscriptionItemId,21 price: newPriceId,22 }],23 proration_behavior: 'always_invoice', // Immediate proration24 }25 );2627 // Update database28 await db.subscription.update({29 where: { id: subscription.id },30 data: {31 stripePriceId: newPriceId,32 // Webhook will update other fields33 },34 });3536 return updatedSubscription;37}
Database Schema
The subscription model stores all necessary Stripe data:
1model Subscription {2 id String @id @default(cuid())3 userId String @unique4 user User @relation(fields: [userId], references: [id], onDelete: Cascade)56 // Stripe IDs7 stripeCustomerId String?8 stripeSubscriptionId String? @unique9 stripeSubscriptionItemId String?10 stripePriceId String?11 stripeCurrentPeriodEnd DateTime?1213 // Subscription details14 status String? // active, canceled, incomplete, etc.15 cancelAtPeriodEnd Boolean @default(false)16 cancelAt DateTime?17 canceledAt DateTime?18 trialStart DateTime?19 trialEnd DateTime?2021 // Metadata22 createdAt DateTime @default(now())23 updatedAt DateTime @updatedAt2425 @@map("subscriptions")26}2728model SubscriptionEvent {29 id String @id @default(cuid())30 subscriptionId String31 subscription Subscription @relation(fields: [subscriptionId], references: [id])3233 // Event details34 eventType String // created, updated, canceled, payment_succeeded, etc.35 stripeEventId String? @unique36 eventData Json? // Store full Stripe event data3738 createdAt DateTime @default(now())3940 @@map("subscription_events")41}
Test Cards
- •
4242424242424242- Success - •
4000000000000002- Declined - •
4000000000000341- Requires authentication
Local Testing
- • Use Stripe CLI for webhook forwarding
- • Test subscription flows end-to-end
- • Verify webhook event handling
# Forward webhooks to local developmentstripe listen --forward-to localhost:3000/api/webhooks/stripe
Security
- • Always verify webhook signatures
- • Use HTTPS in production
- • Validate user permissions
- • Log all payment events
User Experience
- • Provide clear pricing information
- • Handle payment failures gracefully
- • Send billing email notifications
- • Offer self-service portal access
Stripe Integration Complete!
Your subscription billing is ready. Next, explore database patterns and email automation.