@botparty/checkout-nextjs
TypeScript icon, indicating that this package has built-in type declarations

0.0.5 • Public • Published

@botparty/checkout-nextjs

NextAuth-style Next.js SDK for the BotParty checkout service.

pnpm add @botparty/checkout-nextjs

What 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.ts and 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 campaigns service when botpartyMiddleware is installed (or via a _mcmpid cookie/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/serverdefineCheckout, 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 /client instead so React isn't pulled into your server bundle.

See templates/nextjs for a full working example.