> ## 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.

# Mise à niveau & (ré)installation

> Procédure générique pour installer, réinstaller ou mettre à niveau une intégration SandPay depuis zéro — sans hypothèse sur votre base de code.

Voici la **procédure** à suivre depuis votre propre application — que vous
intégriez SandPay pour la première fois, que vous le **réinstalliez** proprement
ou que vous **mettiez à niveau** une intégration existante vers la version courante
de l'API. Elle ne présuppose **aucun code préalable** ; chaque étape est générique
et fonctionne avec n'importe quelle stack (une application Node avec `@drwintech/sandpay`,
ou un client fait maison en Deno / Python / Go via HTTP brut).

<Info>
  **SandPay est la source de vérité.** Ancrez votre comportement sur ce que
  `GET /v1/meta` rapporte et sur ce que dit le
  [Changelog d'intégration](/fr/integration-changelog) — jamais sur une hypothèse
  codée en dur. Ce guide reste valide à travers les versions car chaque détail
  spécifique à une version provient de ces deux sources.
</Info>

## Quel chemin prenez-vous ?

<CardGroup cols={2}>
  <Card title="Chemin A — Mise à niveau" icon="arrow-up">
    Vous avez déjà une intégration fonctionnelle sur une ancienne version de l'API
    et souhaitez passer à la version courante (adopter les remboursements/décaissements,
    etc.).
  </Card>

  <Card title="Chemin B — Installation / réinstallation" icon="rotate">
    Vous intégrez depuis zéro, ou vous supprimez l'ancienne intégration pour la
    refaire proprement.
  </Card>
</CardGroup>

***

## Chemin A — Mettre à niveau une intégration existante

<Steps>
  <Step title="Faites le point : version actuelle vs version cible">
    Demandez à l'instance ce qu'elle supporte. La version qu'elle rapporte est
    votre **cible** :

    ```bash theme={null}
    curl $SANDPAY_BASE_URL/api/v1/meta
    # hébergé : curl https://api.sandpay.dev/v1/meta
    ```

    ```json theme={null}
    { "api_version": "2026-05-30", "capabilities": ["payments","refunds","disbursements","webhooks","commission_split", "..."], "min_sdk_version": "0.2.0" }
    ```

    Puis lisez le [Changelog d'intégration](/fr/integration-changelog) depuis votre
    version actuelle jusqu'à la cible — notez chaque ligne **"Action requise ?"**.
  </Step>

  <Step title="Mettez à jour le client vers ≥ min_sdk_version">
    * **Node :** `npm i @drwintech/sandpay@latest` (doit être ≥ `min_sdk_version`).
    * **Client fait maison** (Deno/Python/Go) : reproduisez manuellement les
      nouvelles méthodes/champs (les nouveaux endpoints + champs de réponse sont
      dans le changelog et le [Guide d'intégration](/fr/integration-guide)).
  </Step>

  <Step title="Migrez la base de données — uniquement si vous hébergez SandPay vous-même">
    Contre le service hébergé `api.sandpay.dev` : **rien à faire**. Si vous
    faites tourner votre propre instance : `corepack pnpm --filter sandpay-app run db:migrate`.
  </Step>

  <Step title="Appliquez chaque modification 'Action requise'">
    Faites exactement ce que les entrées du changelog indiquent. À partir de
    `2026-05-30`, la seule qui affecte le code existant est : **branchez votre
    handler webhook sur `event`** (pour que les remboursements/décaissements ne
    soient pas confondus avec des collectes) — voir
    [Récepteur webhook](#étape-6--récepteur-webhook) ci-dessous. Tout le reste
    est additif et ne nécessite aucune modification.
  </Step>

  <Step title="Vérifiez + redéployez">
    Relancez vos tests d'intégration, redéployez, puis confirmez que
    `GET /api/v1/meta` (ou l'en-tête `X-SandPay-Api-Version` sur n'importe quelle
    réponse `/v1/*`) rapporte la version attendue.
  </Step>
</Steps>

<Tip>
  **Le retour arrière est simple** car `v1` est additif : si quelque chose
  dysfonctionne, rétrogradez votre SDK vers la version précédente et redéployez —
  les anciens appels fonctionnent toujours contre la nouvelle instance. Puis
  retentez la mise à niveau.
</Tip>

***

## Chemin B — Installer (ou réinstaller) depuis zéro

<Warning>
  **Vous réinstallez ? Supprimez d'abord les anciennes hypothèses.** Si vous
  refaites une intégration écrite contre une ancienne version, supprimez tout code
  qui supposait que « SandPay ne peut pas faire de versements » ou qui traitait
  chaque webhook comme une collecte — les deux sont faux depuis `2026-05-30`.
  Repartez des étapes ci-dessous.
</Warning>

### Étape 0 — Prérequis

* Un compte SandPay + une **clé API** (`sp_sk_test_…`).
* Au moins un **environnement opérateur** configuré dans le tableau de bord
  (Paramètres → Pays & opérateurs), ex. `MTN / RW / RWF`.
* Un **secret de signature webhook** (affiché dans les paramètres webhook du
  tableau de bord).

### Étape 1 — Variables d'environnement (côté serveur uniquement)

<Warning>
  La clé API est un **secret serveur**. Ne l'envoyez jamais vers un navigateur,
  une application mobile ou un bundle client quelconque. Tous les appels SandPay
  transitent par **client → votre backend → SandPay**.
</Warning>

```bash theme={null}
# Supprimez tout slash final de l'URL de base.
SANDPAY_BASE_URL=https://api.sandpay.dev      # ou votre URL de tunnel/instance auto-hébergée
SANDPAY_API_KEY=sp_sk_test_xxxxxxxx
SANDPAY_WEBHOOK_SECRET=whsec_xxxxxxxx
```

### Étape 2 — Installez le client

```bash theme={null}
npm i @drwintech/sandpay@latest
```

```ts theme={null}
import { SandPay } from "@drwintech/sandpay";
const sandpay = new SandPay({
  apiKey: process.env.SANDPAY_API_KEY!,
  baseUrl: process.env.SANDPAY_BASE_URL, // optionnel ; pointe vers api.sandpay.dev par défaut
});
```

Pas de Node ? Utilisez HTTP brut. Chaque appel utilise
`Authorization: Bearer $SANDPAY_API_KEY` vers `${SANDPAY_BASE_URL}/api/v1/…`
(hébergé : `/v1/…`).

### Étape 3 — Testez la connectivité + les capacités

```bash theme={null}
curl $SANDPAY_BASE_URL/api/v1/meta
```

Confirmez `api_version`, `min_sdk_version`, et que `capabilities` liste ce que
vous comptez utiliser (`payments`, et `refunds`/`disbursements` si vous avez besoin
de versements). **Détectez les fonctionnalités** via cela plutôt qu'en supposant.

### Étape 4 — Créez un paiement (collecte : client → marchand)

```ts theme={null}
const tx = await sandpay.payments.create({
  amount: 25000, currency: "RWF",
  operator: "mtn", country: "RW",
  msisdn: "+250788000001",
  reference: "ORDER-2026-0001", // votre clé d'idempotence
});
// → { id: "TX_…", status: "PENDING" | "SUCCESS" | … }
```

```bash theme={null}
curl -X POST $SANDPAY_BASE_URL/api/v1/payments \
  -H "Authorization: Bearer $SANDPAY_API_KEY" -H "Content-Type: application/json" \
  -d '{"amount":25000,"currency":"RWF","operator":"mtn","country":"RW","msisdn":"+250788000001","reference":"ORDER-2026-0001"}'
```

Persistez l'`id` retourné (votre `provider_ref`). Mappez **les 11 statuts** ;
seul `PENDING` est non terminal. `USER_CANCELLED` est **terminal** (ne pollez
pas indéfiniment).
Voir [Vocabulaire des statuts](/fr/integration-guide#3-vocabulaire-des-statuts).

### Étape 5 — Obtenez le statut final (polling + webhook)

```ts theme={null}
const p = await sandpay.payments.get("TX_…");   // GET /v1/payments/{id}
```

Utilisez **les deux** : pollez `GET /v1/payments/{id}` (\~toutes les 4s) comme
mécanisme de repli résilient, et le webhook (ci-dessous) comme notification
instantanée. Réconciliez votre grand livre marchand sur **`net_amount`**, pas sur
`amount` (commission opérateur). Voir
[Commission & règlement](/fr/integration-guide#4-commission--règlement).

### Étape 6 — Récepteur webhook

Un petit endpoint sur **votre backend** vers lequel SandPay envoie des requêtes
`POST`. Configurez son URL dans le tableau de bord. Il doit :

1. **Vérifier la signature HMAC** en temps constant, **échouer fermé** si le
   secret est absent. L'en-tête est `X-SandPay-Signature: sha256=<hex>` sur le
   corps brut.
2. Être déployé **sans authentification JWT** (l'authenticité provient de la
   signature).
3. **Associer la transaction par `tx_id`** (SandPay ne renvoie pas votre
   `reference`).
4. **Se brancher sur `event`** — c'est ce qui piège le plus souvent :

```ts theme={null}
switch (payload.event) {
  case "payment.completed":      // une collecte a atteint un statut terminal
    settleOrder(payload.tx_id, payload.status, payload.net_amount);
    break;
  case "payment.refunded":       // un remboursement (payload.parent_tx_id = l'original)
    recordRefund(payload.parent_tx_id, payload.tx_id, payload.status);
    break;
  case "disbursement.completed": // un versement libre
    recordPayout(payload.tx_id, payload.status);
    break;
}
```

Chaque événement porte le `status` terminal — vérifiez-le, pas seulement le nom
de l'événement. Voir [Webhooks](/fr/integration-guide#6-webhooks).

### Étape 7 — Versements (optionnel : remboursements & décaissements)

Uniquement si `capabilities` les inclut (vérifiez au moment de l'exécution) :

```ts theme={null}
// Rembourser tout ou partie d'une collecte SUCCESS (marchand → payeur original)
await sandpay.payments.refund("TX_…", { amount: 500 }); // omettre amount = intégral
// Versement libre à un msisdn
await sandpay.disbursements.create({ amount: 5000, currency: "RWF", operator: "mtn", country: "RW", msisdn: "+250788000001" });
```

Aucune commission n'est prélevée sur un versement. Voir
[Remboursements & décaissements](/fr/integration-guide#5-remboursements--décaissements).

### Étape 8 — Développement local

Votre URL webhook doit être **publiquement accessible**, et SandPay envoie les
webhooks via Inngest. Donc en local :

* Exposez votre backend avec un **tunnel stable** (domaine réservé ngrok) et
  configurez `SANDPAY_BASE_URL`/l'URL webhook une seule fois. Voir
  [la configuration du tunnel](/fr/integration-guide#6-webhooks).
* Faites tourner le serveur de développement Inngest de SandPay **ou** reposez-vous
  sur le polling (Étape 5) — voir
  [Inngest local vs cloud](/fr/integration-faq#why-isn-t-my-webhook-firing-in-local-dev).

### Étape 9 — Checklist finale

* [ ] Les variables `SANDPAY_*` sont dans l'environnement **serveur** uniquement ; l'URL de base n'a pas de slash final.
* [ ] L'environnement opérateur est activé dans le tableau de bord.
* [ ] Création de collecte → stocker l'`id` ; **les 11 statuts** mappés ; `USER_CANCELLED` terminal.
* [ ] Statut résolu par **polling + webhook** ; grand livre réconcilié sur `net_amount`.
* [ ] Webhook : `--no-verify-jwt`, HMAC en temps constant, associer par `tx_id`, **se brancher sur `event`**.
* [ ] URL webhook + secret configurés dans le tableau de bord (secret identique des deux côtés).
* [ ] Versements conditionnés aux `capabilities` (si utilisés).
* [ ] Développement local : tunnel + (serveur Inngest dev **ou** polling).
* [ ] `GET /v1/meta` rapporte la version attendue après déploiement.

## Voir aussi

<CardGroup cols={2}>
  <Card title="Guide d'intégration" icon="book" href="/integration-guide">
    La référence complète faisant autorité.
  </Card>

  <Card title="Changelog d'intégration" icon="timeline" href="/integration-changelog">
    Ce qui a changé par version — et quoi faire.
  </Card>

  <Card title="FAQ d'intégration" icon="circle-question" href="/integration-faq">
    Pièges courants + conseils.
  </Card>

  <Card title="Démarrage rapide" icon="rocket" href="/quickstart">
    Premier paiement en 10 minutes.
  </Card>
</CardGroup>
