Use it in Next.js
app/pricing/Pricing.tsx
"use client";
// Before: <stripe-pricing-table pricing-table-id="prctbl_..." publishable-key="pk_live_..." />
import { useRouter } from "next/navigation";
import PricingSwitch, { type Plan } from "@/components/pricing-switch/PricingSwitch";
import { startCheckout } from "./actions";
// The table shows these numbers; Stripe charges the Price behind each lookup key. Keep them in step.
const plans: Plan[] = [
{ id: "starter", name: "Starter", description: "For side projects", monthly: 9, yearly: 7, cta: "Start Starter", features: ["1 project", "Email support"] },
{ id: "pro", name: "Pro", description: "For a growing team", monthly: 29, yearly: 23, cta: "Start Pro", features: ["10 projects", "Priority support"], featured: true },
{ id: "team", name: "Team", note: "up to 20 seats", description: "For the whole company", monthly: 79, yearly: 63, cta: "Start Team", features: ["Unlimited projects", "SSO"] },
];
export default function Pricing({ signedIn }: { signedIn: boolean }) {
const router = useRouter();
return (
<PricingSwitch
plans={plans}
yearlyBadge="2 months free"
onSelect={async (plan, yearly) => {
// The step the embed can't do: an account first, then Checkout.
if (!signedIn) {
router.push(`/sign-up?plan=${plan.id}&yearly=${yearly ? 1 : 0}`);
return;
}
const res = await startCheckout(plan.id, yearly);
if ("url" in res) window.location.assign(res.url);
else alert(res.error);
}}
/>
);
}
app/pricing/actions.ts
"use server";
import Stripe from "stripe";
import { auth } from "@/lib/auth"; // your session helper
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Lookup keys you set on each recurring Price in the Dashboard, so price ids never live in code.
const LOOKUP: Record<string, { monthly: string; yearly: string }> = {
starter: { monthly: "starter_monthly", yearly: "starter_yearly" },
pro: { monthly: "pro_monthly", yearly: "pro_yearly" },
team: { monthly: "team_monthly", yearly: "team_yearly" },
};
export async function startCheckout(planId: string, yearly: boolean): Promise<{ url: string } | { error: string }> {
const session = await auth();
if (!session) return { error: "Sign in first." };
const keys = LOOKUP[planId];
if (!keys) return { error: "Unknown plan." };
const { data } = await stripe.prices.list({ lookup_keys: [yearly ? keys.yearly : keys.monthly], active: true });
const price = data[0];
if (!price) return { error: "That plan isn't on sale right now." };
const checkout = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: price.id, quantity: 1 }],
client_reference_id: session.user.id, // what the embed's client-reference-id attribute set
customer_email: session.user.email ?? undefined,
subscription_data: { trial_period_days: 14 }, // drop this line for no trial
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/welcome?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing`,
});
return checkout.url ? { url: checkout.url } : { error: "Stripe returned no checkout URL." };
}
app/pricing/page.tsx
import Pricing from "./Pricing";
import { auth } from "@/lib/auth";
export default async function PricingPage() {
const session = await auth();
return <Pricing signedIn={!!session} />;
}