# Payments in the app (@vibiz/app)

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

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.

```ts
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.

```ts
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:

```ts
'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?

```ts
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=
```

```ts
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.
