NextAuth-style Next.js SDK for the BotParty checkout service.
pnpm add @botparty/checkout-nextjs- A single
defineCheckout({...})factory that drives both the server route handler and the React client. - A catch-all App Router handler — drop it at
app/api/checkout/[...checkout]/route.tsand you're done. - A
<CheckoutProvider>, hooks (usePurchaseStatus,useLedger,useInvoices,useStartPurchase), gates (<HasPurchased>,<HasNotPurchased>,<PurchaseGate>), and headless components (<PurchaseButton>,<Ledger>,<Invoices>). - Automatic campaign attribution via the BotParty
campaignsservice whenbotpartyMiddlewareis installed (or via a_mcmpidcookie/query as a fallback).
src/lib/checkout.ts — the source of truth:
import { defineCheckout } from "@botparty/checkout-nextjs/server";
import { auth } from "@botparty/nextjs/server";
export const checkoutConfig = defineCheckout({
apiToken: process.env.CHECKOUT_API_TOKEN!,
async getUser() {
const s = await auth();
return s.isAuthenticated ? { id: s.userId!, email: s.email } : null;
},
async getCampaignShortId() {
const s = await auth();
return (s as any).meta?.cmpid ?? null;
},
successUrl: "/thank-you",
cancelUrl: "/pricing",
products: [
{ slug: "lifetime", title: "Lifetime", amountInCents: 4999, type: "one-time", maxPerUser: 1 },
{ slug: "credits-10", title: "10 Credits", amountInCents: 999, type: "one-time" },
{ slug: "pro-plan", title: "Pro", amountInCents: 1999, type: "subscription", period: "monthly" },
],
});src/app/api/checkout/[...checkout]/route.ts — the catch-all handler:
import { createCheckoutHandler } from "@botparty/checkout-nextjs/server";
import { checkoutConfig } from "@/lib/checkout";
const handler = createCheckoutHandler(checkoutConfig);
export const GET = handler.GET;
export const POST = handler.POST;src/app/layout.tsx — wrap the tree:
import { CheckoutProvider } from "@botparty/checkout-nextjs/client";
export default function Layout({ children }) {
return (
<html><body>
<CheckoutProvider basePath="/api/checkout">{children}</CheckoutProvider>
</body></html>
);
}src/app/pricing/page.tsx:
"use client";
import { PurchaseButton, HasPurchased } from "@botparty/checkout-nextjs/client";
export default function Pricing() {
return (
<>
<PurchaseButton productId="lifetime" redirectUrl="/thank-you">Buy lifetime</PurchaseButton>
<PurchaseButton productId="pro-plan" redirectUrl="/thank-you">Subscribe</PurchaseButton>
<HasPurchased productId="lifetime">
<p>Welcome back, lifetime member 👋</p>
</HasPurchased>
</>
);
}All paths are relative to basePath (default /api/checkout):
| Method | Path | Description |
|---|---|---|
| GET | /products |
Returns the static catalog (or live from apps/checkout) |
| GET | /:slug |
Single product info |
| GET | /:slug/purchase?... |
Starts upsell/hosted Stripe Checkout, 302s away |
| GET | /:slug/status |
{ hasPurchased, purchase? } for the current user |
| GET | /ledger |
Current user's purchase ledger |
| GET | /invoices |
Current user's invoices |
| POST | /sync |
Force product sync into apps/checkout
|
The /:slug/purchase route reads _mcmpid (via getCampaignShortId,
_mcmpid cookie, or ?_mcmpid= query) and forwards it to apps/checkout as
campaignShortId, so attribution survives the Stripe round-trip.
-
@botparty/checkout-nextjs/server—defineCheckout,createCheckoutHandler, fetch helpers (createLink,upsell,hasPurchased,listLedger, …). -
@botparty/checkout-nextjs/client—<CheckoutProvider>, hooks, gates, buttons, ledger components. -
@botparty/checkout-nextjs— re-exports the server entry. Don't import this from a client component; use/clientinstead so React isn't pulled into your server bundle.
See templates/nextjs for a full working example.