Back to roadmaps paddle Course

Project: SaaS Pricing Cards and Checkout Flow

In this project, we will build a premium SaaS pricing cards layout. The cards will trigger the Paddle Overlay checkout window and securely pass the current user account ID as metadata.


1. Interface Design Layout

graph TD
    A[Pricing Cards Options] -->|Click Pro Card| B[Inject User ID metadata]
    B --> C[Launch Paddle Checkout Overlay]

2. Implementing the Pricing Card Component

Create the React Client Component to render plans cards and trigger Paddle:

// app/pricing/PricingCardList.tsx
"use client";

import React from "react";
import { getPaddle } from "../../lib/paddleClient";

interface Plan {
  id: string;
  name: string;
  price: string;
  priceId: string;
  features: string[];
}

const plans: Plan[] = [
  {
    id: "free",
    name: "Starter Tier",
    price: "$0",
    priceId: "",
    features: ["Standard Dashboard access", "5 Projects", "Community Slack support"],
  },
  {
    id: "pro",
    name: "Business Pro",
    price: "$29/mo",
    priceId: "pri_test_pro_plan_price_id", // Paste sandbox price ID
    features: ["Advanced Metrics", "Unlimited Projects", "Premium 24/7 Email support", "API Integration"],
  },
];

export default function PricingCardList({ currentUserId }: { currentUserId: string }) {
  async function triggerCheckout(priceId: string) {
    const paddle = await getPaddle();
    if (!paddle) return;

    // Launch overlay checkout
    paddle.Checkout.open({
      items: [{ priceId, quantity: 1 }],
      customData: {
        userId: currentUserId, // Attach user database ID to match in webhook
      },
      settings: {
        theme: "dark",
        successUrl: "https://mycompany.com/dashboard?status=welcome",
      },
    });
  }

  return (
    <div className="flex flex-col md:flex-row gap-8 max-w-4xl mx-auto mt-12 p-4">
      {plans.map((plan) => (
        <div key={plan.id} className="flex-1 border p-8 rounded-3xl bg-white shadow-sm flex flex-col justify-between">
          <div>
            <h3 className="text-xl font-bold text-gray-950">{plan.name}</h3>
            <p className="text-3xl font-extrabold mt-4 text-blue-600">{plan.price}</p>
            <ul className="mt-6 space-y-3">
              {plan.features.map((feat, i) => (
                <li key={i} className="text-sm text-gray-500 flex items-center gap-2">
                  ✓ {feat}
                </li>
              ))}
            </ul>
          </div>

          <div className="mt-8">
            {plan.priceId ? (
              <button
                onClick={() => triggerCheckout(plan.priceId)}
                className="w-full bg-blue-600 text-white font-semibold py-2.5 rounded-lg text-sm transition hover:bg-blue-700"
              >
                Go Business Pro
              </button>
            ) : (
              <button
                disabled
                className="w-full bg-gray-100 text-gray-400 font-semibold py-2.5 rounded-lg text-sm"
              >
                Current Tier
              </button>
            )}
          </div>
        </div>
      ))}
    </div>
  );
}

3. Webhook Integration Verification

When a purchase is completed:

  1. Paddle triggers the webhook endpoint payload.
  2. The endpoint reads the userId custom data parameter.
  3. The server updates the database row pricingTier: "pro".
  4. Next time the user loads the dashboard, the server shows their upgraded Business Pro interface features.
Published on Last updated: