paymentsmedium3-4 hours
Stripe Checkout Integration
Implement Stripe Checkout for one-time payments with webhooks and order fulfillment
stripepaymentscheckoute-commerce
Overview
Integrate Stripe Checkout for secure payment processing. This blueprint covers creating checkout sessions, handling webhooks for payment confirmation, and fulfilling orders.
Prerequisites
- •Stripe account (test mode for development)
- •Product/price created in Stripe Dashboard
- •Database for storing orders
Steps
1. Install Stripe SDK
npm install stripe @stripe/stripe-js
2. Environment Variables
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
3. Stripe Server Client
// src/lib/stripe.ts
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20',
})
4. Create Checkout Session API
// src/app/api/checkout/route.ts
import { stripe } from '@/lib/stripe'
import { auth } from '@/lib/auth'
export async function POST(req: Request) {
const session = await auth()
if (!session?.user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const { priceId, quantity = 1 } = await req.json()
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'payment',
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity,
},
],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/cancel`,
customer_email: session.user.email!,
metadata: {
userId: session.user.id,
},
})
return Response.json({ url: checkoutSession.url })
}
5. Checkout Button Component
// src/components/CheckoutButton.tsx
"use client"
import { useState } from 'react'
interface Props {
priceId: string
children: React.ReactNode
}
export function CheckoutButton({ priceId, children }: Props) {
const [loading, setLoading] = useState(false)
const handleCheckout = async () => {
setLoading(true)
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
})
const { url } = await res.json()
window.location.href = url
} catch (error) {
console.error('Checkout error:', error)
} finally {
setLoading(false)
}
}
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Loading...' : children}
</button>
)
}
6. Webhook Handler
// src/app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe'
import { headers } from 'next/headers'
import { db } from '@/lib/db'
export async function POST(req: Request) {
const body = await req.text()
const signature = headers().get('stripe-signature')!
let event
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
console.error('Webhook signature verification failed')
return Response.json({ error: 'Invalid signature' }, { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object
await fulfillOrder(session)
break
}
case 'payment_intent.payment_failed': {
const paymentIntent = event.data.object
console.log('Payment failed:', paymentIntent.id)
break
}
}
return Response.json({ received: true })
}
async function fulfillOrder(session: any) {
const userId = session.metadata.userId
await db.order.create({
data: {
userId,
stripeSessionId: session.id,
amount: session.amount_total,
status: 'completed',
},
})
// Add your fulfillment logic here:
// - Send confirmation email
// - Grant access to product
// - Update inventory
}
7. Success Page
// src/app/checkout/success/page.tsx
import { stripe } from '@/lib/stripe'
export default async function SuccessPage({
searchParams,
}: {
searchParams: { session_id: string }
}) {
const session = await stripe.checkout.sessions.retrieve(
searchParams.session_id
)
return (
<div className="text-center py-20">
<h1 className="text-3xl font-bold text-green-600">
Payment Successful!
</h1>
<p className="mt-4">
Thank you for your purchase. Order ID: {session.id}
</p>
</div>
)
}
Testing with Stripe CLI
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login and forward webhooks
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Test card numbers
# Success: 4242 4242 4242 4242
# Decline: 4000 0000 0000 0002
Variations
Subscription Payments
Change mode to subscription:
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'subscription',
// ... rest same
})
With Quantity Selector
Pass quantity from frontend and validate:
const quantity = Math.min(Math.max(parseInt(body.quantity) || 1, 1), 99)
Testing Checklist
- • Checkout session creates successfully
- • Redirect to Stripe works
- • Successful payment redirects to success page
- • Webhook receives and processes events
- • Order created in database
- • Cancellation redirects correctly
Want to contribute?
This blueprint is open source. Found an issue or want to improve it? Edit on GitHub