Set Up Paddle as Payment Processor in Next.js SaaS Application

February 16, 2025Next.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.

Step 1: Create a Paddle Sandbox Account

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.

Step 2: Create Client-Side Token

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.

Step 3: Set Up Your Products

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.

Step 4: Set Up Your Next.js Application

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

Step 5: Create a Checkout Page

/app/checkout/page.tsx
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;
 

Step 6: Create PaddleCheckout Component

/app/components/PaddleCheckout.tsx
'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>;
}
 

Step 7: Update Database

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);
}

Step 8: Add Pricing Card Component

/app/components/PricingCard.tsx
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>
    );
}
 

Step 9: Add Pricing Section Component

/app/components/PricingSection.tsx
 
'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>
    );
}
 

Step 10: Final integration

Import the PricingSection component

/app/pages.tsx
import PricingSection from "@/components/PricingSection";
 
export default function Home() {
  return (
    <PricingSection />
  );
}
 

Step 11: Final Setup

Don't forget to set up your environment variables:

.env.local
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

Conclusion

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.