Complete Guide: Setting Up Stripe with Next.js 15 App Router and TypeScript

Table Of Content
Setting Up Stripe with Next.js 15 App Router and TypeScript
In this comprehensive guide, we'll walk through the process of integrating Stripe payments into a Next.js 15 application using the App Router and TypeScript.
Prerequisites
Before we begin, make sure you have:
- Node.js installed (version 18.17 or later)
- A Stripe account (sign up at stripe.com)
- Next.js 15 project with TypeScript
- Stripe CLI installed (optional, for webhook testing)
Installation
First, install the required dependencies:
npm install stripe @stripe/stripe-js
Environment Setup
Create or update your .env.local
file:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
STRIPE_SECRET_KEY=sk_test_your_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
Type Definitions
Create a types file types/stripe.ts
:
export interface Price {
id: string;
product_id: string;
active: boolean;
currency: string;
unit_amount: number;
}
export interface Product {
id: string;
name: string;
description: string;
active: boolean;
prices?: Price[];
}
Stripe Configuration
Create a utility file lib/stripe.ts
:
import Stripe from 'stripe';
if (!process.env.STRIPE_SECRET_KEY) {
throw new Error('Missing Stripe secret key');
}
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2023-10-16', // Use the latest API version
typescript: true,
});
Server Actions Setup
Create a server action file app/actions/stripe.ts
:
'use server'
import { stripe } from '@/lib/stripe';
import { headers } from 'next/headers';
export async function createCheckoutSession(price_id: string) {
const headersList = headers();
const origin = headersList.get('origin');
try {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [
{
price: price_id,
quantity: 1,
},
],
success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/canceled`,
});
return { sessionId: session.id };
} catch (error) {
console.error('Error creating checkout session:', error);
throw error;
}
}
Client-Side Implementation
Create a checkout button component components/CheckoutButton.tsx
:
'use client';
import { loadStripe } from '@stripe/stripe-js';
import { useState } from 'react';
import { createCheckoutSession } from '@/app/actions/stripe';
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
interface CheckoutButtonProps {
priceId: string;
}
export default function CheckoutButton({ priceId }: CheckoutButtonProps) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
try {
setLoading(true);
const { sessionId } = await createCheckoutSession(priceId);
const stripe = await stripePromise;
if (!stripe) throw new Error('Stripe failed to initialize');
const { error } = await stripe.redirectToCheckout({
sessionId,
});
if (error) {
throw error;
}
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleCheckout}
disabled={loading}
className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50"
>
{loading ? 'Loading...' : 'Buy Now'}
</button>
);
}
Webhook Handler
Create a webhook route handler app/api/webhook/route.ts
:
import { headers } from 'next/headers';
import { stripe } from '@/lib/stripe';
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get('stripe-signature')!;
try {
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
// Handle successful payment
console.log('Payment successful:', session);
break;
// Add other webhook events as needed
}
return new Response('Webhook received', { status: 200 });
} catch (error) {
console.error('Webhook error:', error);
return new Response(
'Webhook error: ' + (error as Error).message,
{ status: 400 }
);
}
}
export const runtime = 'edge';
Usage Example
Here's how to implement a product page with Stripe checkout:
// app/products/[id]/page.tsx
import { stripe } from '@/lib/stripe';
import CheckoutButton from '@/components/CheckoutButton';
interface ProductPageProps {
params: {
id: string;
};
}
export default async function ProductPage({ params }: ProductPageProps) {
const product = await stripe.products.retrieve(params.id, {
expand: ['default_price'],
});
const price = product.default_price as Stripe.Price;
return (
<div className="p-4">
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>
Price: {new Intl.NumberFormat('en-US', {
style: 'currency',
currency: price.currency,
}).format((price.unit_amount || 0) / 100)}
</p>
<CheckoutButton priceId={price.id} />
</div>
);
}
Testing Webhooks Locally
To test webhooks during development:
- Install the Stripe CLI
- Run the following command:
stripe listen --forward-to localhost:3000/api/webhook
Best Practices and Security Considerations
-
Environment Variables
- Never expose your Stripe secret key
- Use different keys for development and production
-
Error Handling
- Implement proper error handling for all Stripe operations
- Log errors appropriately
-
TypeScript Types
- Use Stripe's built-in TypeScript types
- Create custom types for your specific implementation
-
Webhook Security
- Always verify webhook signatures
- Use environment variables for webhook secrets
-
Payment Flow
- Implement proper success and error pages
- Handle edge cases and failed payments
Conclusion
You now have a fully functional Stripe integration in your Next.js 15 application with TypeScript support. This setup provides:
- Type-safe Stripe operations
- Secure payment processing
- Webhook handling
- Server-side and client-side integration
Remember to:
- Test thoroughly in development
- Use Stripe's test mode before going live
- Monitor your Stripe dashboard for transactions
- Keep your dependencies updated
For more advanced features like subscriptions, recurring payments, or customer management, refer to the Stripe documentation.
This implementation provides a solid foundation for handling payments in your Next.js application. You can build upon this setup to add more complex payment flows and features as needed.