> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sandpay.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Node

> @drwintech/sandpay — référence complète

`@drwintech/sandpay` est le SDK officiel TypeScript pour Node.js 20+. Il enveloppe l'API HTTP, normalise les erreurs et expose un helper de vérification des signatures webhook.

<Tip>
  Prefer a ready-to-run starter? See [Stack Builder](/fr/quickstart#stack-builder-recommended) — il génère un projet Next.js/Express/Hono avec `@drwintech/sandpay` déjà câblé et vos clés pré-remplies.
</Tip>

## Installation

```bash theme={null}
npm install @drwintech/sandpay
# ou
pnpm add @drwintech/sandpay
```

## Import et constructeur

```ts theme={null}
import { SandPay } from "@drwintech/sandpay";

const sp = new SandPay({
  apiKey: process.env.SANDPAY_API_KEY!,
  // baseUrl: "https://api.sandbox.sandpay.dev", // optionnel — défaut: https://api.sandpay.dev
});
```

| Option    | Type   | Défaut                    | Description                                                |
| --------- | ------ | ------------------------- | ---------------------------------------------------------- |
| `apiKey`  | string | —                         | Requis. Clé `sp_sk_test_...`.                              |
| `baseUrl` | string | `https://api.sandpay.dev` | URL racine de l'API. Pour pointer un environnement custom. |

## `payments.create(input)`

Crée un paiement simulé.

```ts theme={null}
const tx = await sp.payments.create({
  amount: 25000,
  currency: "FCFA",
  operator: "orange",
  country: "CI",
  msisdn: "+22507123456",
  reference: "ORDER-2026-A1",
  scenario: "success", // optionnel — défaut: "success"
});
```

| Champ         | Type                           | Requis | Notes                                                  |
| ------------- | ------------------------------ | ------ | ------------------------------------------------------ |
| `amount`      | number                         | oui    | Entier positif, unité minimale (FCFA = unité entière). |
| `currency`    | string                         | oui    | `FCFA`, `XOF`, `XAF`, `RWF`…                           |
| `operator`    | `mtn`/`orange`/`moov`/`airtel` | oui    | Voir [guides opérateurs](/fr/operators/mtn).           |
| `country`     | string (2 lettres)             | oui    | ISO-3166. `CI`, `BJ`, `TG`, `RW`.                      |
| `msisdn`      | string                         | oui    | Format E.164 (`+225…`).                                |
| `reference`   | string                         | oui    | Votre référence interne (idempotence).                 |
| `description` | string                         | non    | Description libre (max 200 caractères).                |
| `scenario`    | `PaymentScenario`              | non    | Voir [Scénarios](/fr/scenarios).                       |

## `payments.get(id)`

```ts theme={null}
const tx = await sp.payments.get("TX_8K3M9F");
```

Retourne le même objet `Payment` que `create`. Lève `SandPayApiError` avec `status: 404` si l'id est inconnu.

## `payments.list(params?)`

```ts theme={null}
const page = await sp.payments.list({
  limit: 50,
  country: "CI",
  operator: "orange",
  status: "SUCCESS",
  cursor: previousPage.nextCursor ?? undefined,
});

console.log(page.data.length, page.hasMore, page.nextCursor);
```

| Param      | Type              | Défaut | Notes                                                |
| ---------- | ----------------- | ------ | ---------------------------------------------------- |
| `limit`    | number (1–100)    | 50     |                                                      |
| `country`  | string            | —      | Filtre 2 lettres.                                    |
| `operator` | `PaymentOperator` | —      |                                                      |
| `status`   | `PaymentStatus`   | —      | Voir [scénarios](/fr/scenarios) pour les 11 valeurs. |
| `cursor`   | string            | —      | Opaque, fourni par la page précédente.               |

## `webhooks.verify(header, payload, secret)`

Vérifie la signature HMAC-SHA256 d'un webhook. **Le `payload` doit être le corps brut**, exactement comme reçu.

```ts theme={null}
const ok = sp.webhooks.verify(
  req.headers["x-sandpay-signature"] as string,
  rawBody,
  process.env.SANDPAY_WEBHOOK_SECRET!,
);
```

### Récupérer le `rawBody`

<CodeGroup>
  ```ts Next.js (App Router) theme={null}
  // app/api/webhooks/sandpay/route.ts
  export async function POST(req: Request) {
    const rawBody = await req.text();
    const ok = sp.webhooks.verify(
      req.headers.get("x-sandpay-signature") ?? "",
      rawBody,
      process.env.SANDPAY_WEBHOOK_SECRET!,
    );
    if (!ok) return new Response("invalid", { status: 401 });
    const event = JSON.parse(rawBody);
    // ...
    return Response.json({ received: true });
  }
  ```

  ```ts Express theme={null}
  import express from "express";
  app.post(
    "/webhooks/sandpay",
    express.raw({ type: "application/json" }),
    (req, res) => {
      const rawBody = req.body.toString("utf8");
      const ok = sp.webhooks.verify(
        req.header("x-sandpay-signature") ?? "",
        rawBody,
        process.env.SANDPAY_WEBHOOK_SECRET!,
      );
      if (!ok) return res.status(401).end();
      const event = JSON.parse(rawBody);
      res.json({ received: true });
    },
  );
  ```

  ```ts Hono theme={null}
  app.post("/webhooks/sandpay", async (c) => {
    const rawBody = await c.req.text();
    const ok = sp.webhooks.verify(
      c.req.header("x-sandpay-signature") ?? "",
      rawBody,
      process.env.SANDPAY_WEBHOOK_SECRET!,
    );
    if (!ok) return c.text("invalid", 401);
    const event = JSON.parse(rawBody);
    return c.json({ received: true });
  });
  ```
</CodeGroup>

## `webhooks.parseEvent(rawBody)`

Parse un corps webhook **déjà vérifié** en payload typé `WebhookPayload`. Appeler après `verify(...)`.

```ts theme={null}
const rawBody = await req.text();
const signature = req.headers.get("x-sandpay-signature") ?? "";

if (!sp.webhooks.verify(signature, rawBody, process.env.SANDPAY_WEBHOOK_SECRET!)) {
  return new Response("invalid signature", { status: 401 });
}

const event = sp.webhooks.parseEvent(rawBody);
// event.event === "payment.completed"
// event.tx_id, event.status, event.amount, etc. — tous typés
```

Lève une `Error` si :

* le corps n'est pas du JSON valide (`Invalid JSON in webhook body`),
* le corps n'est pas un objet JSON (`Webhook body must be a JSON object`),
* le nom d'événement est inconnu (`Unknown webhook event: ...`),
* un champ requis est manquant ou n'a pas le bon type (`Missing or invalid field: ...`).

Le contrôle est volontairement minimal — il valide la forme suffisamment pour donner un typage sûr en aval. Pour une validation exhaustive, composer avec zod.

### Types exportés

* `WebhookEventName` — union littérale des noms d'événements (`"payment.completed"` aujourd'hui).
* `WebhookPayload` — interface du payload renvoyé par `parseEvent`.

## `SandPayApiError`

Lancée sur tout statut HTTP non-2xx.

```ts theme={null}
import { SandPay, SandPayApiError } from "@drwintech/sandpay";

try {
  await sp.payments.create({ /* ... */ });
} catch (err) {
  if (err instanceof SandPayApiError) {
    console.error(err.status, err.code, err.message, err.requestId);
    console.error(err.detail); // payload structuré éventuel (erreurs Zod par champ)
  }
}
```

| Propriété   | Type    | Description                                                   |
| ----------- | ------- | ------------------------------------------------------------- |
| `status`    | number  | Status HTTP (400, 401, 402, 404, 429, 5xx).                   |
| `code`      | string  | Code machine (voir [Erreurs](/fr/errors)).                    |
| `message`   | string  | Texte humain.                                                 |
| `detail`    | unknown | Payload structuré optionnel.                                  |
| `requestId` | string? | En-tête `X-Request-Id` de la réponse (utile pour le support). |

## Code source

Le SDK est open source : [github.com/htleclerc/sandpay](https://github.com/htleclerc/sandpay). PRs bienvenues.
