# Vender créditos

> Créditos pré-pagos para uma API ou um serviço de IA: os compradores recarregam por conta própria e seu servidor gasta por chamada. Nada é tirado de uma recarga.

Créditos servem para tudo o que se vende por uso: tokens, imagens, minutos, buscas. O comprador paga um pacote em cripto; você gasta dele a cada chamada; quando os créditos acabam, ele recarrega na própria página. Nada é cobrado automaticamente (o comprador sempre paga ele mesmo) e a Perly não fica com nada da recarga.

## Configuração

1. **Crie um produto e os pacotes dele** — Dashboard → Products → New, depois New price → **Credit pack**: por exemplo, 1.000 créditos por $10, 5.000 por $40. Ou `pay.prices.create({ product, model: "credit_pack", unit_amount: 1000, credits: 1000 })`.
2. **Escolha quando o comprador fica sabendo do saldo** — Na página do produto, em **Credits**: o limite de aviso, um e-mail ao comprador e **Any amount** (N de um pacote), para o comprador poder recarregar $25 e não só um pacote fixo.
3. **Dê a cada comprador um cliente** — Crie um por usuário com o seu id: `pay.customers.create({ email, external_id: user.id })`. A partir daí, o SDK aceita esse id.

## Gastar, consultar, recarregar

**Node**

```js
import { PerlyPay, InvalidRequestError } from "perly-pay";
const pay = new PerlyPay(process.env.PERLY_PAY_SECRET_KEY);

// Before the work: enough left?
const { balance, top_up_url } = await pay.credits.balance(user.id);

// After it: spend. The same idempotencyKey never spends twice.
try {
  await pay.credits.consume(user.id, 3, { action: "chat", idempotencyKey: request.id });
} catch (e) {
  if (e instanceof InvalidRequestError && e.code === "insufficient_credits") {
    return { error: "Out of credits", topUp: await pay.credits.topUpUrl(user.id) };
  }
  throw e;
}
```

**PHP**

```php
$perly = new \Perly\Pay\PerlyPay(getenv('PERLY_PAY_SECRET_KEY'));

$left = $perly->credits->balance($user->id)['balance'];

try {
    $perly->credits->consume($user->id, 3, ['action' => 'chat', 'idempotency_key' => $requestId]);
} catch (\Perly\Pay\Exception\InvalidRequestException $e) {
    if ($e->getErrorCode() === 'insufficient_credits') {
        header('Location: ' . $perly->credits->topUpUrl($user->id));
    }
}
```

**curl**

```bash
curl https://pay.perly.io/api/v1/credits/consume \
  -H "Authorization: Bearer sk_test_…" \
  -H "Idempotency-Key: req_8f2c" \
  -d '{"customer_external_id":"user_42","credits":3,"action":"chat"}'
```

- O cliente é um id `cus_…`, um e-mail ou o seu próprio id para ele (`external_id`). Informe `product` se você vende créditos para vários produtos.
- O gasto é atômico e nunca fica abaixo de zero: dois gastos simultâneos não passam com os mesmos créditos. Se não for suficiente, retorna `insufficient_credits` (409) e nada é gasto.
- Passe o id da sua requisição como chave de idempotência. Uma nova tentativa (após um timeout, uma falha, até no dia seguinte) devolve o primeiro gasto em vez de gastar de novo.
- `action` é o seu rótulo (`chat`, `image.hd`, `api/search`). A página do produto soma o gasto por ação e por cliente.

## A página de recarga

`top_up_url` é a página do próprio comprador, sempre a mesma: mostra o saldo e os seus pacotes com o visual do seu checkout, e abre uma fatura normal quando o comprador escolhe um pacote. Assim que é paga, os créditos entram no saldo uma única vez. Mostre o link no seu app ou no erro de “sem créditos”, ou envie: o portal do comprador também tem um botão **Recarregar**.

## Quando os créditos acabam

| Evento | Quando |
| --- | --- |
| `credits.low` | O saldo caiu até o limite do produto. Uma vez por cruzamento: só chega de novo depois que uma recarga o levar para cima do limite. |
| `credits.depleted` | O saldo chegou a zero. Uma vez por cruzamento. |

Os dois trazem o cliente como `data.object`, além de `data.product` e `data.balance`. Se houver limite e o e-mail estiver ativado, o comprador também recebe um e-mail com o link de recarga, no máximo uma vez por dia. No modo de teste, os e-mails vão só para a sua equipe.

> **Nota:** Os créditos de vários pacotes se somam; o gasto usa primeiro o pacote mais antigo.
