# Recette : crédits API

> Vendez des crédits prépayés pour une API ou une fonction d’IA, et consommez-en un par requête. Un solde insuffisant répond 402 avec un lien de recharge.

Les acheteurs prépaient un pack de crédits ; chaque requête en consomme. Perly Pay tient le registre : consommer est un appel atomique qui refuse tout découvert, et chaque acheteur a sa propre page de recharge. Rien n’est jamais débité tout seul — quand les crédits sont épuisés, l’acheteur recharge. Toute la fonction crédits est dans [Vendre des crédits](https://pay.perly.io/docs/credits.md) ; ceci est la recette autour.

> **Note:** Avec un assistant : le skill `sell-credits` du plugin Perly Pay réalise cette recette dans votre projet. L’exemple exécutable est `examples/api-credits` (MIT) dans le dépôt du plugin.

## Comment ça marche

1. **Un pack de crédits** — Un produit et un prix avec `model: "credit_pack"`, `unit_amount` en centimes et `credits`. Créé une fois par mode.
2. **Vos utilisateurs sont des clients** — Créés une fois avec `external_id` (votre id utilisateur) ; ensuite, `credits.*` acceptent cet id.
3. **Recharge** — `credits.topUpUrl(userId)` est la page propre à l’acheteur : il choisit un pack et paie. Chaque pack payé ajoute des crédits.
4. **Consommer par requête** — `credits.consume(userId, cost, { idempotencyKey })` d’abord, puis le travail. Une nouvelle tentative avec la même clé renvoie la première consommation : votre serveur crée donc la clé pour chaque requête et ne la prend jamais de l’appelant.
5. **Solde insuffisant ?** — `insufficient_credits` (409) ne consomme rien : répondez `402` avec la page de recharge, pas une 500.

**Spend one request's credits**

```js
try {
  await pay.credits.consume(userId, cost, { product: PRODUCT, action: "api", idempotencyKey: `req-${userId}-${requestId}` });
} catch (e) {
  if (e.code !== "insufficient_credits") throw e;
  return reply(402, { top_up: await pay.credits.topUpUrl(userId, { product: PRODUCT }) });
}
// now do the work
```

## Lancez l’exemple

1. **Installez** — `npm install` dans `examples/api-credits` (Node 20.12+).
2. **Clé** — Copiez `.env.example` vers `.env` et mettez une clé de test (`sk_test_…`) dans `PERLY_PAY_SECRET_KEY`.
3. **Préparez** — `npm run setup` crée le produit et le prix avec votre clé et affiche leurs ids pour `.env`.
4. **Lancez** — `npm start` (ou `npm run dev`), comme l’indique son README.
5. **Payez sans portefeuille** — Appelez `POST /v1/shout` (402), ouvrez la page que donne `POST /top-up`, choisissez le pack, touchez **Simulate payment** sur la facture, rappelez : 200 et `X-Credits-Remaining: 499`.

## Pièges

- Consommer sans clé d’idempotence : une requête relancée est facturée deux fois.
- Baser la clé de consommation sur un id envoyé par l’appelant (un en-tête `Idempotency-Key`) : la même clé renvoie la première consommation comme un succès, et l’appelant obtient à nouveau le travail sans payer.
- Vérifier le solde, puis consommer, en deux étapes. `consume` refuse à lui seul tout découvert ; le solde sert à l’affichage.
- Faire le travail avant de consommer. Consommez d’abord.
- Des ids de test avec une clé réelle. Produits, prix et endpoints de webhook appartiennent à un seul mode : refaites la mise en place avec `sk_live_…` au passage en réel.
