Stripe Integration

Complete guide to the tiered subscription system with Stripe integration, webhook automation, billing management, and programmatic authorization.

Tiered Subscription Integration

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
🏗️ Subscription Architecture

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

🔧 Environment Setup

Configure your environment variables for the tiered pricing system:

# Stripe API Keys
STRIPE_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 plan
STRIPE_PRICE_PRO_YEARLY="price_xxx" # Pro yearly plan
STRIPE_PRICE_ULTRA_MONTHLY="price_xxx" # Ultra monthly plan
STRIPE_PRICE_ULTRA_YEARLY="price_xxx" # Ultra yearly plan
# Security
CRON_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.

💰 Creating Stripe Products & Prices

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

Pricing Component

The pricing section displays plans and handles subscription creation:

Using PricingSection
1import { PricingSection } from "~/components/PricingSection";
2
3// In your page component
4export default async function HomePage() {
5 const session = await auth();
6
7 // Get user's current subscription
8 let subscription = null;
9 if (session?.user?.id) {
10 subscription = await db.subscription.findUnique({
11 where: { userId: session.user.id },
12 });
13 }
14
15 return (
16 <div>
17 {/* Other content */}
18
19 <PricingSection
20 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.

Checkout Flow

Users are redirected to Stripe Checkout for secure payment processing:

src/app/api/checkout/route.ts
1// Checkout API route
2import { auth } from "~/auth";
3import { stripe } from "~/server/lib/stripe";
4
5export async function POST(request: Request) {
6 const session = await auth();
7 if (!session?.user?.email) {
8 return new Response("Unauthorized", { status: 401 });
9 }
10
11 const { priceId } = await request.json();
12
13 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 });
29
30 return Response.json({ url: checkoutSession.url });
31 } catch (error) {
32 return new Response("Internal Server Error", { status: 500 });
33 }
34}

Webhook Integration

Webhook Setup

Configure webhooks in Stripe Dashboard to receive subscription events:

  1. 1. Go to Developers → Webhooks in Stripe Dashboard
  2. 2. Click "Add endpoint"
  3. 3. Add your webhook URL: https://yourdomain.com/api/webhooks/stripe
  4. 4. Select these events:
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  5. 5. Copy the webhook signing secret to STRIPE_WEBHOOK_SECRET
Webhook Handler

The webhook handler processes Stripe events and updates your database:

src/app/api/webhooks/stripe/route.ts
1import { stripe } from "~/server/lib/stripe";
2import { db } from "~/server/db";
3
4export async function POST(request: Request) {
5 const body = await request.text();
6 const signature = request.headers.get("stripe-signature")!;
7
8 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: 400
18 });
19 }
20
21 switch (event.type) {
22 case "customer.subscription.created":
23 case "customer.subscription.updated":
24 await handleSubscriptionUpdate(event.data.object);
25 break;
26
27 case "customer.subscription.deleted":
28 await handleSubscriptionCancellation(event.data.object);
29 break;
30
31 case "invoice.payment_succeeded":
32 await handlePaymentSuccess(event.data.object);
33 break;
34
35 case "invoice.payment_failed":
36 await handlePaymentFailure(event.data.object);
37 break;
38 }
39
40 return new Response("Webhook processed", { status: 200 });
41}

Subscription Management

Customer Portal

Stripe Customer Portal provides self-service billing management:

Customer portal action
1// Customer portal server action
2import { stripe } from "~/server/lib/stripe";
3import { auth } from "~/auth";
4
5export async function createPortalSession() {
6 const session = await auth();
7 if (!session?.user?.id) {
8 throw new Error("Unauthorized");
9 }
10
11 // Get user's Stripe customer ID
12 const subscription = await db.subscription.findUnique({
13 where: { userId: session.user.id },
14 });
15
16 if (!subscription?.stripeCustomerId) {
17 throw new Error("No subscription found");
18 }
19
20 const portalSession = await stripe.billingPortal.sessions.create({
21 customer: subscription.stripeCustomerId,
22 return_url: `${process.env.NEXTAUTH_URL}/account`,
23 });
24
25 return portalSession.url;
26}

The Customer Portal allows users to update payment methods, download invoices, and manage their subscription.

Plan Changes

Handle plan upgrades and downgrades with proper proration:

Plan change handling
1export async function changePlan(newPriceId: string) {
2 const session = await auth();
3 if (!session?.user?.id) {
4 throw new Error("Unauthorized");
5 }
6
7 const subscription = await db.subscription.findUnique({
8 where: { userId: session.user.id },
9 });
10
11 if (!subscription?.stripeSubscriptionId) {
12 throw new Error("No active subscription");
13 }
14
15 // Update subscription in Stripe
16 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 proration
24 }
25 );
26
27 // Update database
28 await db.subscription.update({
29 where: { id: subscription.id },
30 data: {
31 stripePriceId: newPriceId,
32 // Webhook will update other fields
33 },
34 });
35
36 return updatedSubscription;
37}

Database Schema

Subscription Model

The subscription model stores all necessary Stripe data:

prisma/schema.prisma
1model Subscription {
2 id String @id @default(cuid())
3 userId String @unique
4 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5
6 // Stripe IDs
7 stripeCustomerId String?
8 stripeSubscriptionId String? @unique
9 stripeSubscriptionItemId String?
10 stripePriceId String?
11 stripeCurrentPeriodEnd DateTime?
12
13 // Subscription details
14 status String? // active, canceled, incomplete, etc.
15 cancelAtPeriodEnd Boolean @default(false)
16 cancelAt DateTime?
17 canceledAt DateTime?
18 trialStart DateTime?
19 trialEnd DateTime?
20
21 // Metadata
22 createdAt DateTime @default(now())
23 updatedAt DateTime @updatedAt
24
25 @@map("subscriptions")
26}
27
28model SubscriptionEvent {
29 id String @id @default(cuid())
30 subscriptionId String
31 subscription Subscription @relation(fields: [subscriptionId], references: [id])
32
33 // Event details
34 eventType String // created, updated, canceled, payment_succeeded, etc.
35 stripeEventId String? @unique
36 eventData Json? // Store full Stripe event data
37
38 createdAt DateTime @default(now())
39
40 @@map("subscription_events")
41}
🧪 Testing Your Integration

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 development
stripe listen --forward-to localhost:3000/api/webhooks/stripe
💡 Stripe Best Practices

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.