vibizStack
Console
Agent guides

Payments in the app (@vibiz/app)

Checkout, subscription checks and the customer portal from the app's server, with @vibiz/app or plain HTTP.

Open as .md

The app talks to Vibiz with one server secret, VIBIZ_APP_KEY (already in the env on Vibiz hosting and connected hosts; the development env from vibiz_dev_env has its own). Server-side only: never in client components or NEXT_PUBLIC_ variables.

TypeScript
import { vibiz } from '@vibiz/app'; // reads VIBIZ_APP_KEY and VIBIZ_API_URL from the env

Install it with npm install @vibiz/app. A project that can't add a dependency calls the same HTTP routes with fetch (below), which is all it does.

Checkout

An Upgrade button → a server action or route handler → redirect.

TypeScript
const { url } = await vibiz.payments.checkout({
  lookup_key: 'pro-monthly', // or price_id: 'pprice_…', or amount_cents + name for a one-off
  user: session.user.id, // your own user id: subscriptions and payments are found by it
  success_url: `${origin}/billing/success`,
  cancel_url: `${origin}/pricing`, // https; http://localhost is fine in development
  // customer_email?, quantity?, metadata? (≤ 20 keys, echoed on the payment and webhooks)
});
redirect(url); // the hosted page is for one buyer and expires after 1 hour

Next.js server action:

TypeScript
'use server';
import { redirect } from 'next/navigation';
import { vibiz } from '@vibiz/app';

export async function upgrade() {
  const user = await currentUser(); // your sign-in
  const { url } = await vibiz.payments.checkout({
    lookup_key: 'pro-monthly',
    user: user.id,
    success_url: 'https://app.example.com/billing/success',
    cancel_url: 'https://app.example.com/pricing',
  });
  redirect(url);
}
// <form action={upgrade}><button>Upgrade</button></form>

Create the checkout on every click: never hard-code a checkout URL, it is for one buyer and expires.

Is this user subscribed?

TypeScript
await vibiz.payments.hasActive(userId, 'pro-monthly'); // boolean; price id or lookup key, or omit for any plan
await vibiz.payments.subscription(userId); // { id, status, price_id, current_period_end, cancel_at_period_end } | null

Manage or cancel: const { url } = await vibiz.payments.portal(userId, { return_url }); Catalog: await vibiz.payments.products(). What sold: await vibiz.payments.payments({ user }). Refunds and withdrawals are not the app's (the app key is refused): they are the owner's.

The same over HTTP

Authorization: Bearer $VIBIZ_APP_KEY, base $VIBIZ_API_URL (default https://app.vibiz.app).

Text
POST /v1/app/payments/checkout     { lookup_key | price_id | amount_cents+name, user, success_url, cancel_url } → { url }
GET  /v1/app/payments/subscriptions?user=u_123&status=active,trialing   → { data: [...] }
POST /v1/app/payments/portal       { user, return_url } → { url }
GET  /v1/app/payments/products
GET  /v1/app/payments/payments?user=
TypeScript
const res = await fetch(`${process.env.VIBIZ_API_URL ?? 'https://app.vibiz.app'}/v1/app/payments/checkout`, {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.VIBIZ_APP_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({ lookup_key: 'pro-monthly', user: user.id, success_url, cancel_url }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { url } = await res.json();

A key works in one environment: the development key sees test mode, the production key live.