@botparty/checkout-nextjs
v0.0.5
Published
NextAuth-style Next.js SDK for the BotParty checkout service: catch-all App Router handler + React provider, hooks, and gate components.
Downloads
401
Maintainers
Readme
@botparty/checkout-nextjs
NextAuth-style Next.js SDK for the BotParty checkout service.
pnpm add @botparty/checkout-nextjsWhat you get
- 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).
60-second setup
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>
</>
);
}Routes mounted by createCheckoutHandler
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.
Imports map
@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.
