February 16, 2025 • Next.js, SaaS, Tech, TypeScript
In this tutorial, you will learn how to set up Paddle for payments in your Next.js SaaS application. Paddle is a popular payment gateway that allows you to accept payments from customers all over the world.
Don't have time to read the tutorial? Checkout the Github Repository Paddle Nextjs BoilerPlate Code.
The first step is to create a Paddle sandbox account. This will allow you to test your payment integration without using real money. You can sign up for a sandbox account on the Paddle website.
Go to Paddle Authentication to get your API key and vendor ID. Your vendor ID will be your seller ID.
Scroll down to generate a client-side token by following the instructions on the Paddle dashboard. This token will be used to authenticate your requests to the Paddle API.
Next, you need to set up your products on the Paddle dashboard. You can create different products with different prices and features. Make sure to note down the product IDs as you will need them later.
Now that you have set up your Paddle account and products, it's time to integrate Paddle into your Next.js application. You can use the Paddle SDK to handle the payment process.
First, Create a Next.js app from following command:
npx create-next-app@latest
Install ShadCN (UI component library):
npm install shadcn
import React from 'react'
import { Checkout } from '../../components/PaddleCheckout'
function CheckoutPage() {
const user = {
email: "test@test.com",
id: '1',
};
return (
<div>
<div id="paddle-checkout"></div>
<Checkout user={user} />
</div>
)
}
export default CheckoutPage;
'use client';
import { initializePaddle, Paddle, Environments } from '@paddle/paddle-js';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
export function Checkout({ user }: CheckoutProps) {
const router = useRouter();
const searchParams = useSearchParams();
const [paddle, setPaddle] = useState<Paddle>();
useEffect(() => {
initializePaddle({
environment: process.env.NEXT_PUBLIC_PADDLE_ENVIRONMENT as Environments, // comment this for production
token: process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN as string,
checkout: {
settings: {
allowLogout: false,
},
},
eventCallback(event) {
switch (event.name) {
case 'checkout.completed':
updateDatabase(event.data);
break;
case 'checkout.closed':
router.push('/');
break;
case 'checkout.loaded':
break;
case 'checkout.error':
console.error('Checkout error:', event.data);
break;
}
},
}).then((paddleInstance) => {
if (paddleInstance) {
setPaddle(paddleInstance);
}
});
}, []);
return <div id="paddle-checkout"></div>;
}
Update the database with the payment information when the checkout is completed.
function updateDatabase(data: any) {
// Update the database with the payment information
console.log('Payment completed:', data);
}
import Link from 'next/link';
export function PricingCard({
title,
monthlyPrice,
features,
buttonText,
isPro,
monthlyPriceId,
}: PricingCardProps) {
const getCheckoutLink = () => {
if (buttonText === 'Get Started') {
return '/login';
}
return `/checkout?priceId=${monthlyPriceId}`;
};
return (
<Card className={isPro ? 'border-indigo-500' : ''}>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>
<span className="text-3xl font-bold">${monthlyPrice}</span> / month
</CardDescription>
</CardHeader>
<CardContent>
<ul>
{features.map((feature, index) => (
<li key={index}>{feature.text}</li>
))}
</ul>
</CardContent>
<CardFooter>
<Link href={getCheckoutLink()}>
{buttonText}
</Link>
</CardFooter>
</Card>
);
}
'use client';
import { useState } from 'react';
import { PricingCard } from './PricingCard';
export default function PricingSection() {
const [isAnnual, setIsAnnual] = useState(false);
const plans = [
{
title: 'Starter',
monthlyPrice: 0,
features: [{ text: 'Feature 1', included: true }],
buttonText: 'Get Started',
},
{
title: 'Pro',
monthlyPrice: 8,
features: [{ text: 'Feature 1', included: true }],
buttonText: 'Upgrade Now',
isPro: true,
monthlyPriceId: 'pri_01j7qw0djsh1vrvh9c8gb5jn49',
},
];
return (
<section>
<h2>Pricing</h2>
<div>
{plans.map((plan, index) => (
<PricingCard key={index} {...plan} isAnnual={isAnnual} />
))}
</div>
</section>
);
}
Import the PricingSection component
import PricingSection from "@/components/PricingSection";
export default function Home() {
return (
<PricingSection />
);
}
Don't forget to set up your environment variables:
NEXT_PUBLIC_PADDLE_VENDER_ID=your-vendor-id
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=your-client-token
PADDLE_API_KEY=your-api-key
NEXT_PUBLIC_PADDLE_ENVIRONMENT=sandbox
Run your Next.js app:
npm run dev
After you successfully test the payment process in your Next.js application, you can switch to the production environment and start accepting real payments from your customers.
Now you have to make a vendor account from Paddle and get your API key and vendor ID. Then you can create a client-side token and set up your products. After that, you can integrate Paddle into your Next.js application and create a checkout page. Finally, you can update your database with the payment information when the checkout is completed.