Skip to Content
DocsPaymentStripe Payments

Stripe Payments

Stripe provides comprehensive payment processing with subscription management. This module is already integrated in the template.

Overview

Stripe offers advanced payment processing with subscription management, international support, and extensive webhook system for real-time updates.

Key Features:

  • Subscription billing
  • One-time payments
  • International payment methods
  • Tax handling and invoicing
  • Customer portal
  • Comprehensive webhooks

Getting Started

1. Get Your Stripe API Keys

  1. Go to stripe.com  and create an account
  2. Complete your account setup (start in test mode)
  3. Go to Developers → API Keys in your dashboard
  4. Copy your keys:
    • Publishable Key (starts with pk_test_)
    • Secret Key (starts with sk_test_)

2. Create a Product and Price

  1. Go to Products in your Stripe dashboard
  2. Click “Add Product”
  3. Set up your product (e.g., “Pro Plan”)
  4. Create a recurring price (e.g., $49/month)
  5. Copy the Price ID (starts with price_) - NOT the Product ID

3. Add Environment Variables

Add these to your .env.local file:

STRIPE_SECRET_KEY=sk_test_your_secret_key_here NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here STRIPE_PRICE_ID=price_your_price_id_here NEXT_PUBLIC_APP_URL=http://localhost:3000

4. Set Up Webhooks

For Development (using Stripe CLI):

# Install Stripe CLI # Visit: https://stripe.com/docs/stripe-cli # Login to Stripe stripe login # Forward webhooks to your local server stripe listen --forward-to localhost:3000/api/stripe/webhook # Copy the webhook secret from CLI output (starts with whsec_)

Add the webhook secret to .env.local:

STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here

5. How It Works in the Template

The template includes complete Stripe integration:

  • Client Setup: lib/stripe-client.ts for frontend operations
  • Server Setup: lib/stripe.ts for backend operations
  • Checkout API: Creates secure checkout sessions
  • Webhook Handler: Processes payment events and updates database
  • Database Integration: Automatic user and subscription management

6. Template Structure

lib/ ├── stripe.ts # Server-side Stripe configuration ├── stripe-client.ts # Client-side Stripe loading app/api/stripe/ ├── checkout/ # Create checkout sessions └── webhook/ # Handle Stripe webhooks components/ui/ └── pricing-cards.tsx # Integrated pricing with Stripe

Payment Flow

The template handles the complete payment flow:

1. User Clicks Subscribe

When users click a subscription button:

// This is already implemented in pricing-cards.tsx const handleSubscribe = async () => { const response = await fetch('/api/stripe/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId: 'price_your_price_id' }) }) const { sessionId } = await response.json() // Redirect to Stripe checkout const stripe = await getStripe() await stripe?.redirectToCheckout({ sessionId }) }

2. Checkout Session Creation

The template creates secure checkout sessions:

// app/api/stripe/checkout/route.ts (already implemented) export async function POST(request: NextRequest) { const { userId } = await auth() // Clerk authentication // Get user from database const user = await prisma.user.findUnique({ where: { clerkId: userId } }) // Check for existing subscription const existingSubscription = await prisma.subscription.findFirst({ where: { userId: user.id, status: 'ACTIVE' } }) if (existingSubscription) { return NextResponse.json({ error: 'User already has active subscription' }) } // Create Stripe customer if needed const customer = await stripe.customers.create({ email: user.email, name: `${user.firstName} ${user.lastName}`, metadata: { userId: user.id, clerkId: user.clerkId } }) // Create checkout session const session = await stripe.checkout.sessions.create({ customer: customer.id, mode: 'subscription', line_items: [{ price: priceId, quantity: 1 }], success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`, cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/?canceled=true`, metadata: { userId: user.id, clerkId: user.clerkId } }) return NextResponse.json({ sessionId: session.id }) }

3. Webhook Processing

The template automatically handles webhooks:

// app/api/stripe/webhook/route.ts (already implemented) export async function POST(request: Request) { const signature = headers().get('stripe-signature')! const body = await request.text() // Verify webhook signature const event = stripe.webhooks.constructEvent(body, signature, webhookSecret) switch (event.type) { case 'checkout.session.completed': // Create subscription record in database await handleCheckoutCompleted(event.data.object) break case 'customer.subscription.updated': // Update subscription status await handleSubscriptionUpdated(event.data.object) break case 'invoice.payment_succeeded': // Record successful payment await handlePaymentSucceeded(event.data.object) break } }

Database Integration

The template automatically manages:

User Records

// Users are automatically synced from Clerk authentication // No additional setup required

Subscription Records

// Subscriptions are created/updated via webhooks model Subscription { id String @id @default(cuid()) userId String stripeCustomerId String? stripeSubscriptionId String? @unique stripePriceId String? status SubscriptionStatus @default(INACTIVE) currentPeriodStart DateTime? currentPeriodEnd DateTime? cancelAtPeriodEnd Boolean @default(false) }

Payment Records

// Payments are tracked automatically model Payment { id String @id @default(cuid()) userId String stripePaymentIntentId String? @unique stripeSessionId String? @unique amount Int // Amount in cents currency String @default("usd") status PaymentStatus @default(PENDING) description String? }

API Endpoints

The template includes these payment APIs:

Checkout API (/api/stripe/checkout)

  • POST - Create checkout session
  • Validates user authentication
  • Checks for existing subscriptions
  • Creates Stripe customer if needed
  • Returns checkout session ID

Webhook API (/api/stripe/webhook)

  • POST - Process Stripe webhooks
  • Verifies webhook signatures
  • Updates database based on events
  • Handles all subscription lifecycle events

User APIs

  • GET /api/user/subscription - Get subscription status
  • GET /api/user/payments - Get payment history

Testing Payments

Test Cards

Use these test card numbers:

  • Success: 4242 4242 4242 4242
  • Declined: 4000 0000 0000 0002
  • Requires 3D Secure: 4000 0025 0000 3155
  • Insufficient Funds: 4000 0000 0000 9995

Use any future expiry date, any 3-digit CVC, and any ZIP code.

Testing Workflow

  1. Start Development Server:

    npm run dev
  2. Start Stripe Webhook Forwarding:

    stripe listen --forward-to localhost:3000/api/stripe/webhook
  3. Test Payment Flow:

    • Sign up/Sign in to your app
    • Go to pricing page
    • Click “Subscribe” button
    • Use test card: 4242 4242 4242 4242
    • Complete checkout
    • Verify success redirect to dashboard
  4. Verify Database Records:

    npm run db:studio

    Check that subscription and payment records were created.

Subscription Management

Get Subscription Status

// This is already implemented in the template const subscription = await prisma.subscription.findFirst({ where: { userId: user.id, status: 'ACTIVE' } })

Cancel Subscription

// Implementation available in template const cancelSubscription = async (subscriptionId: string) => { await stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }) }

Customer Portal

Stripe provides a hosted customer portal:

// Create portal session const portalSession = await stripe.billingPortal.sessions.create({ customer: customerId, return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard` }) // Redirect user to portal window.location.href = portalSession.url

Pricing Integration

The template includes pricing cards with integrated Stripe checkout:

// components/ui/pricing-cards.tsx (already implemented) const plans = [ { name: 'Starter', price: '$0', priceId: null, features: ['Basic features', '5 projects'] }, { name: 'Pro', price: '$49/month', priceId: process.env.STRIPE_PRICE_ID, features: ['Everything in Starter', 'Unlimited projects'] } ] // Checkout button automatically integrated <CheckoutButton priceId={plan.priceId} disabled={!isSignedIn} > Subscribe to {plan.name} </CheckoutButton>

Webhook Events Handled

The template processes these webhook events:

  • checkout.session.completed - Initial subscription creation
  • customer.subscription.created - Subscription record creation
  • customer.subscription.updated - Status and billing changes
  • customer.subscription.deleted - Cancellation handling
  • invoice.payment_succeeded - Successful payment recording
  • invoice.payment_failed - Failed payment handling

Production Setup

1. Switch to Live Mode

  1. In Stripe dashboard, toggle to “Live mode”
  2. Get your live API keys
  3. Update environment variables with live keys

2. Production Webhooks

  1. Go to Developers → Webhooks in Stripe dashboard
  2. Click “Add endpoint”
  3. Set URL: https://yourdomain.com/api/stripe/webhook
  4. Select events:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  5. Copy webhook signing secret to production environment

3. Environment Variables

# Production environment STRIPE_SECRET_KEY=sk_live_your_live_secret_key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_live_publishable_key STRIPE_WEBHOOK_SECRET=whsec_your_production_webhook_secret STRIPE_PRICE_ID=price_your_live_price_id NEXT_PUBLIC_APP_URL=https://yourdomain.com

Troubleshooting

Checkout not working:

  • Verify API keys are correctly set in environment variables
  • Check that price ID exists in your Stripe account
  • Ensure user is signed in before attempting checkout
  • Verify NEXT_PUBLIC_APP_URL is set correctly

Webhook verification failing:

  • Check webhook secret matches CLI output exactly
  • Ensure both dev server and Stripe CLI are running
  • Verify webhook endpoint is accessible
  • Check for any middleware blocking webhook requests

Subscription not updating:

  • Verify webhook events are being received
  • Check webhook signature verification is working
  • Look at webhook logs in Stripe dashboard
  • Ensure database operations in webhook handler are succeeding

Database issues:

  • Run npm run db:generate after schema changes
  • Check database connection and credentials
  • Verify Prisma client is properly imported
  • Look at server logs for database errors

User already has subscription error:

  • This prevents duplicate subscriptions (working as intended)
  • Cancel existing subscription first, or implement upgrade flow
  • Check subscription status in database vs Stripe dashboard

Payment not completing:

  • Use valid test card numbers
  • Check for JavaScript errors in browser console
  • Verify Stripe publishable key is loaded correctly
  • Ensure checkout session is created successfully
Last updated on