# Credits verkaufen

> Vorausbezahlte Credits für eine API oder einen AI-Dienst: Käufer laden selbst auf, Ihr Server verbraucht pro Aufruf. Von einer Aufladung wird nichts abgezogen.

Credits passen zu allem, was nach Nutzung verkauft wird: Tokens, Bilder, Minuten, Suchen. Der Käufer zahlt ein Paket in Krypto; Sie verbrauchen daraus bei jedem Aufruf; gehen die Credits aus, lädt er auf seiner eigenen Seite auf. Nichts wird automatisch abgebucht – der Käufer zahlt immer selbst –, und Perly nimmt nichts von einer Aufladung.

## Einrichten

1. **Produkt und Pakete anlegen** — Dashboard → Products → New, dann New price → **Credit pack**: etwa 1.000 Credits für $10, 5.000 für $40. Oder `pay.prices.create({ product, model: "credit_pack", unit_amount: 1000, credits: 1000 })`.
2. **Wählen, wann der Käufer vom Stand erfährt** — Auf der Produktseite, unter **Credits**: eine Warnschwelle, eine E-Mail an den Käufer und **Any amount** – das N eines Pakets, damit ein Käufer auch $25 statt nur eines festen Pakets aufladen kann.
3. **Jedem Käufer einen Kunden geben** — Legen Sie pro Nutzer einen mit Ihrer eigenen ID an: `pay.customers.create({ email, external_id: user.id })`. Danach nimmt das SDK diese ID an.

## Verbrauchen, prüfen, aufladen

**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"}'
```

- Ein Kunde ist eine ID `cus_…`, eine E-Mail oder Ihre eigene ID für ihn (`external_id`). Geben Sie `product` an, wenn Sie Credits für mehrere Produkte verkaufen.
- Ein Verbrauch ist atomar und geht nie unter null: Zwei gleichzeitige können nicht dieselben Credits nehmen. Ein Mangel ist `insufficient_credits` (409), und nichts wird verbraucht.
- Übergeben Sie die ID Ihrer Anfrage als Idempotenzschlüssel. Eine Wiederholung – nach einem Timeout, einem Absturz, sogar am nächsten Tag – liefert den ersten Verbrauch zurück, statt erneut zu verbrauchen.
- `action` ist Ihr eigenes Label (`chat`, `image.hd`, `api/search`). Die Produktseite summiert den Verbrauch nach Aktion und nach Kunde.

## Die Aufladeseite

`top_up_url` ist die eigene Seite des Käufers, immer dieselbe: Sie zeigt den Stand und Ihre Pakete im Look Ihres Checkouts und öffnet eine normale Rechnung, wenn der Käufer ein Paket wählt. Sobald sie bezahlt ist, werden die Credits einmal gutgeschrieben. Zeigen Sie den Link in Ihrer App, in Ihrer Fehlermeldung „Credits aufgebraucht“ oder senden Sie ihn – im Käuferportal gibt es auch einen Button **Aufladen**.

## Wenn die Credits knapp werden

| Ereignis | Wann |
| --- | --- |
| `credits.low` | Der Stand ist auf die Schwelle des Produkts gefallen. Einmal pro Unterschreiten: erneut erst, nachdem eine Aufladung ihn wieder darüber gebracht hat. |
| `credits.depleted` | Der Stand hat null erreicht. Einmal pro Unterschreiten. |

Beide tragen den Kunden als `data.object` sowie `data.product` und `data.balance`. Ist eine Schwelle gesetzt und die E-Mail an, erhält der Käufer außerdem eine E-Mail mit seinem Aufladelink – höchstens einmal am Tag. Im Testmodus gehen E-Mails nur an Ihr Team.

> **Hinweis:** Credits aus mehreren Paketen addieren sich; ein Verbrauch nimmt zuerst das älteste Paket.
