AI agent guide
Instructions for coding agents that integrate vantezzen/pay into an existing app.
This page is written for coding agents. Use it when a user asks you to add vantezzen/pay to an existing project.
If you are a human developer, read the getting started guide instead.
Raw Markdown: /docs/agent.mdx
About
You are being tasked to implement "vantezzen/pay" (or just "pay") into this codebase. pay allows easily charging users for features, consumable credits, or subscriptions. It handles the surrounding work of checkout, webhooks, and credit wallets so that the app can focus on its core functionality. Internally, pay uses Stripe or Polar to handle money, invoices, and taxes.
First, clarify what is being monetized
Before editing files, make sure you know exactly what is being monetized. If the user didn't already provide this, you can ask questions like these, already suggesting your recommendations:
- What should users pay for? Name the exact button, route, action, page, API call, or feature.
- Should payment buy consumable credits, buy a subscription, or do a one-time payment?
- If credits are consumed, how many credits should one paid action cost? What does one "credit" represent in the app (minutes, images, exports, API calls, processed files, etc.)?
- Where should wallet or billing controls live: header, sidebar, account menu, settings page, or near the paid action?
- Does the app already require login?
Default to credit codes when the app does not already require login. Use external auth only when the app already has stable user accounts and the user wants wallets attached to those accounts.
Read the relevant docs
You can read https://pay.vantezzen.io/docs/llms.txt for list of all docs pages, but the following pages are usually most important:
/docs/getting-started/install.mdxcovers installation and env vars./docs/getting-started/first-feature.mdxcovers a basic example of gating a feature using credits./docs/components/provider.mdxcoversPayProvider./docs/components/credit-gate.mdxcovers credit-gated actions./docs/components/feature-gate.mdxcovers feature-gated UI./docs/components/purchase.mdxcovers purchase buttons and dialogs./docs/components/wallet-button.mdxcovers anonymous wallet recovery./docs/concepts/external-auth.mdxcovers apps with existing user accounts.
/docs/llms-full.txt will give you the full content of all docs pages. This is very huge and should be used only when the targeted pages are not enough. Append ".mdx" to any docs page path to get the raw Markdown source.
Install the app-side components
For the default credit-code integration, install the common shadcn registry items in the target app:
npx shadcn@latest add \
https://pay.vantezzen.io/r/pay-provider.json \
https://pay.vantezzen.io/r/pay-credits.json \
https://pay.vantezzen.io/r/pay-purchase.json \
https://pay.vantezzen.io/r/pay-gate.json \
https://pay.vantezzen.io/r/pay-wallet.jsonIf the user self-hosts vantezzen/pay, replace https://pay.vantezzen.io with
their vantezzen/pay domain.
For external-auth projects, also read /docs/concepts/external-auth.mdx. Those
projects usually need the server SDK:
npx shadcn@latest add https://pay.vantezzen.io/r/pay-server.jsonAdd environment placeholders
Do not invent real keys. Add placeholders to the app's env example and, if the user wants it, to local env files. If you are already supplied with real keys, add those to the real env file, not the example.
NEXT_PUBLIC_PAY_URL=https://pay.vantezzen.io
NEXT_PUBLIC_PAY_KEY=pay_pk_replace_meHere we're using "NEXT_PUBLIC_" to make variables publicly available - if the project doesn't use Next, instead use the framework's public env var prefix.
For self-hosted vantezzen/pay, use the user's own public vantezzen/pay URL.
For external-auth server integrations, use server-only env vars:
NEXT_PUBLIC_PAY_URL=https://pay.vantezzen.io
PAY_SECRET_KEY=pay_sk_replace_meNever expose a pay_sk_... secret key to browser code.
Add PayProvider
Wrap the part of the app that displays balances, opens checkout, or spends credits.
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>
);
}If the app has a narrower authenticated shell or dashboard layout, put
PayProvider there instead of wrapping unrelated public marketing pages.
Add wallet and balance UI
Show the current balance in the app shell (e.g. in the header or sidebar) or near paid work:
import { RemainingCredits } from "@/components/pay/remaining-credits";
<RemainingCredits />For credit-code projects, add wallet recovery somewhere persistent, usually the header, sidebar, account menu, or settings page:
import { WalletButton } from "@/components/pay/wallet-button";
<WalletButton />
// or with custom styling:
<WalletButton variant="outline" size="sm" />Skip WalletButton only when the project uses external auth and wallets are
attached to logged-in users on the server.
Gate paid actions
Next we should gate the paid work itself. Which gate to use depends on whether the paid work is a consumable action or a feature that stays unlocked.
Gate consumable credit actions
Use credits for paid work that is consumed per run, such as minutes, generated images, exports, API calls, or processed files.
"use client";
import { CreditGate } from "@/components/pay/credit-gate";
import { PurchaseButton } from "@/components/pay/purchase-dialog";
import { useCredits } from "@/components/pay/provider";
import { Button } from "@/components/ui/button";
const COST = 10;
export function PaidAction() {
return (
<CreditGate
cost={COST}
>
<Button disabled={loading}>
Run paid action
</Button>
</CreditGate>
);
}Important: CreditGate only checks access. It does not spend credits. Deduct
inside the click handler, form action, or job-start path that actually starts
the paid work. Use a stable idempotencyKey for retries.
Gate feature access
Use features for access that should stay unlocked while a subscription is active, after a one-time purchase, or after a manual grant.
import { FeatureGate } from "@/components/pay/feature-gate";
export function AnalyticsSection() {
return (
<FeatureGate feature="analytics">
<AnalyticsDashboard />
</FeatureGate>
);
}Use the same feature slug in the vantezzen/pay dashboard when configuring the price that unlocks the feature.
Deduct credits
Next, deduct credits in the paid work path. This is usually a click handler, form action, or job-start path. Deduct only after the user has confirmed the paid action.
When deducing credits, its best to pass a stable idempotencyKey for retryable work, such as a job id or request id. If you omit it, the client generates one, which is fine for a single button click but not for server retries.
Deduct in a client click handler
"use client";
import { CreditGate } from "@/components/pay/credit-gate";
import { PurchaseButton } from "@/components/pay/purchase-dialog";
import { useCredits } from "@/components/pay/provider";
import { Button } from "@/components/ui/button";
const COST = 10;
export function PaidAction() {
const { deduct, loading } = useCredits();
async function runPaidWork() {
const jobId = crypto.randomUUID();
await deduct(COST, {
idempotencyKey: jobId,
meta: { feature: "paid-action" },
});
// Start the paid work only after deduction succeeds.
}
return (
<CreditGate
cost={COST}
fallback={<PurchaseButton>Buy credits</PurchaseButton>}
>
<Button disabled={loading} onClick={runPaidWork}>
Run paid action
</Button>
</CreditGate>
);
}Deduct in a server action or API route
import { createPayServerClient } from "@/lib/pay/server";
export const payServer = createPayServerClient({
baseUrl: process.env.NEXT_PUBLIC_PAY_URL!,
secretKey: process.env.PAY_SECRET_KEY!, // pay_sk_… - server only
});
export async function GET() {
const session = await getAuth(); // your auth session
try {
// 1. Charge for the work (creates the wallet lazily via getOrCreateWallet
// on signup, or grant a welcome balance first).
await payServer.deduct({
externalUserId: userId,
amount: 10,
idempotencyKey: jobId, // retries never double-charge
});
} catch (err) {
if (err instanceof PayError && err.isInsufficientCredits) {
// 2. Out of credits → send the user to checkout or show a message. The client can also show a purchase dialog instead of redirecting.
const { url } = await payServer.createCheckout("price_...", {
externalUserId: userId,
successUrl: "https://your-app.com/billing?status=success",
});
return { needsPayment: url };
}
throw err;
}
// 3. Run the paid work.
return startRenderJob(jobId);
}Or for feature-gated work, check access with hasFeature() instead of deducting credits:
if (await payServer.hasFeature(user.id, "analytics")) {
return runAnalyticsExport();
}Tell the user what to do in vantezzen/pay
After code integration, direct the user to:
https://pay.vantezzen.io/docs/getting-started/dashboard
Tell them to complete these dashboard steps:
- If not already, create a vantezzen/pay account and log in.
- Follow the Get started steps at the top to connect to Stripe or Polar
- Follow the Get started steps at the top to create a new project or open Projects and press New project.
- In the new project, follow the First steps guide at the top to create a new product.
- Here the type should be that same as what we implemented (Credit pack, Subscription or One-time unlock).
- The name should be the top-level product name (e.g. "Processing time" or "Membership").
- If its credits, the "Credit unit" should be the matching unit (e.g. "minutes" or "images").
- Add a price, pack, or subscription tier matching the code:
- For
CreditGate/Credit features, configure credits granted by the purchased price, e.g. 100 credits for 9.99 USD. - For
FeatureGate/Subscription or one-time purchases, configure the feature slug used in code (e.g. "analytics").
- For
- Open the project Developers tab.
- Copy
NEXT_PUBLIC_PAY_URLandNEXT_PUBLIC_PAY_KEYinto the app env file, replacing the placeholders added earlier.
- If the app uses external auth, the user also has to click on "Secret key" and copy
PAY_SECRET_KEYinto the app env file.
- Restart the app process if its framework requires env variables to be loaded at startup.
If the user self-hosts vantezzen/pay, tell them to use their own dashboard URL
and registry URL instead of pay.vantezzen.io.
Verify the integration
Run the target project's normal checks: typecheck, lint, and focused tests. Do not remove tests to make the integration pass.
Finally, summarize what you did and what the user should do next.