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
- Go to stripe.com and create an account
- Complete your account setup (start in test mode)
- Go to Developers → API Keys in your dashboard
- Copy your keys:
- Publishable Key (starts with
pk_test_) - Secret Key (starts with
sk_test_)
- Publishable Key (starts with
2. Create a Product and Price
- Go to Products in your Stripe dashboard
- Click “Add Product”
- Set up your product (e.g., “Pro Plan”)
- Create a recurring price (e.g., $49/month)
- 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:30004. 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_here5. How It Works in the Template
The template includes complete Stripe integration:
- Client Setup:
lib/stripe-client.tsfor frontend operations - Server Setup:
lib/stripe.tsfor 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 StripePayment 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 requiredSubscription 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 statusGET /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
-
Start Development Server:
npm run dev -
Start Stripe Webhook Forwarding:
stripe listen --forward-to localhost:3000/api/stripe/webhook -
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
-
Verify Database Records:
npm run db:studioCheck 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.urlPricing 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 creationcustomer.subscription.created- Subscription record creationcustomer.subscription.updated- Status and billing changescustomer.subscription.deleted- Cancellation handlinginvoice.payment_succeeded- Successful payment recordinginvoice.payment_failed- Failed payment handling
Production Setup
1. Switch to Live Mode
- In Stripe dashboard, toggle to “Live mode”
- Get your live API keys
- Update environment variables with live keys
2. Production Webhooks
- Go to Developers → Webhooks in Stripe dashboard
- Click “Add endpoint”
- Set URL:
https://yourdomain.com/api/stripe/webhook - Select events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
- 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.comTroubleshooting
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_URLis 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:generateafter 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