vantezzen/pay
Components

PayProvider

React context that loads balances and resumes checkout confirmation.

PayProvider is required by all React components and hooks. Put it above the parts of your app that show or spend credits.

BalanceLoading...
import { PayProvider } from "@/components/pay/provider";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <PayProvider
      baseUrl={process.env.NEXT_PUBLIC_PAY_URL!}
      publishableKey={process.env.NEXT_PUBLIC_PAY_KEY!}
    >
      {children}
    </PayProvider>
  );
}

Install

npx shadcn@latest add https://pay.vantezzen.io/r/pay-provider.json

Usage

Credit-code projects

app/layout.tsx
import { PayProvider } from "@/components/pay/provider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <PayProvider
          baseUrl={process.env.NEXT_PUBLIC_PAY_URL!}
          publishableKey={process.env.NEXT_PUBLIC_PAY_KEY!}
        >
          {children}
        </PayProvider>
      </body>
    </html>
  );
}

External-auth projects

For logged-in apps, keep PAY_SECRET_KEY on your server and let the browser components call same-origin bridge routes that you own.

<PayProvider
  baseUrl={process.env.NEXT_PUBLIC_PAY_URL!}
  publishableKey={process.env.NEXT_PUBLIC_PAY_KEY!}
  mode="external_auth"
  externalUserId={user.id}
  externalApiBasePath="/api/pay"
>
  {children}
</PayProvider>

The bridge should expose POST /api/pay/wallet, POST /api/pay/checkout, POST /api/pay/portal, and optionally POST /api/pay/deduct, all backed by createPayServerClient(). Validate the current session in those routes before using externalUserId.

Props

PropTypeRequiredDescription
baseUrlstringYesPublic URL of your vantezzen/pay instance.
publishableKeystringYesProject publishable key, starting with pay_pk_.
mode"credit_codes" | "external_auth"NoDefaults to credit_codes.
externalUserIdstringFor external authStable user id from your app.
externalApiBasePathstringNoSame-origin bridge path for external-auth server calls. Defaults to /api/pay.
storageKeystringNoCustom localStorage key for credit-code wallets.
childrenReact.ReactNodeYesUI that can read or spend credits.

What it does

  • Creates a createPayClient() client.
  • Loads the wallet on mount - balances, unlocked features, and subscriptions.
  • Exposes the client through usePay().
  • Exposes balances and deduction helpers through useCredits().
  • Exposes feature access through useFeature() and subscriptions through useSubscription().
  • Caches the product catalog once and shares it through useProducts().
  • Runs a handler after a completed checkout through useCheckoutPaid().
  • Starts checkout with desktop popup/mobile redirect behavior through usePayCheckout().
  • Watches pending checkout orders in the background and refreshes when the order is paid.
  • Dispatches a pay:checkout-paid browser event after popup completion or redirect resume. WalletButton uses this to show the post-purchase wallet reminder.

useProducts

Read the project's product catalog. The catalog is fetched once and shared — purchase dialogs and your own pricing UI read the same cache.

const { products, loading } = useProducts();
FieldTypeDescription
productsProduct[] | nullThe catalog, or null before the first load completes.
loadingbooleantrue until the first load settles.
errorstring | nullMessage from the last failed load, or null.
refresh() => Promise<void>Force a refetch (e.g. after you know the catalog changed).

useCheckoutPaid

Run a handler whenever a checkout started from this page is confirmed paid — popup completion, redirect resume, or background confirmation.

useCheckoutPaid((order) => toast.success(`Payment received (${order.status})`));

The wallet context (balances, features, subscriptions) has already been refreshed by the time the handler fires, and the handler does not need to be memoized. The raw PAY_CHECKOUT_PAID_EVENT constant remains available for non-React listeners.