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

# Scénarios

> Les 10 scénarios déterministes du sandbox

Chaque appel à `POST /v1/payments` peut spécifier un champ `scenario` qui pilote **de façon déterministe** le résultat de la simulation : statut final, latence, message d'erreur. C'est le levier principal pour tester vos chemins d'échec.

## Tableau de référence

| ID               | Libellé FR             | Statut produit        | Latence (ms)  | Sévérité |
| ---------------- | ---------------------- | --------------------- | ------------- | -------- |
| `success`        | Succès                 | `SUCCESS`             | 800–1 800     | ok       |
| `pin_invalid`    | PIN invalide           | `PIN_INVALID`         | 600–1 400     | erreur   |
| `low_balance`    | Solde insuffisant      | `INSUFFICIENT_FUNDS`  | 600–1 400     | erreur   |
| `timeout`        | Délai dépassé          | `TIMEOUT`             | 25 000–35 000 | warn     |
| `blocked`        | Compte bloqué          | `ACCOUNT_BLOCKED`     | 500–1 000     | erreur   |
| `cancelled`      | Annulé utilisateur     | `USER_CANCELLED`      | 3 000–8 000   | warn     |
| `unknown_msisdn` | Numéro inconnu         | `UNKNOWN_MSISDN`      | 400–900       | erreur   |
| `limit_exceeded` | Plafond atteint        | `LIMIT_EXCEEDED`      | 500–1 000     | erreur   |
| `maintenance`    | Service en maintenance | `SERVICE_UNAVAILABLE` | 200–600       | warn     |
| `duplicate`      | Référence en double    | `DUPLICATE_REFERENCE` | 200–500       | erreur   |

La latence est tirée aléatoirement dans la fourchette indiquée à chaque appel — pour reproduire les variations naturelles d'un opérateur réel.

<Tip>
  Passez un `scenario` explicite pour **forcer** un résultat déterministe (idéal pour les tests / la CI). Si vous l'omettez, le résultat dépend de votre registre de clients de test — voir la section ci-dessous.
</Tip>

## Sans scénario : le registre SIM (clients de test)

Si vous **omettez** le champ `scenario`, SandPay se comporte comme un opérateur réel et utilise vos **clients de test** (`/clients` dans le tableau de bord) comme un registre SIM :

| Cas                                   | Statut renvoyé       | Final ?                                   |
| ------------------------------------- | -------------------- | ----------------------------------------- |
| Numéro inconnu (aucun client de test) | `UNKNOWN_MSISDN`     | oui                                       |
| Client trouvé + bloqué                | `ACCOUNT_BLOCKED`    | oui                                       |
| Client actif + solde \< montant       | `INSUFFICIENT_FUNDS` | oui                                       |
| Client actif + solde ≥ montant        | `PENDING`            | non — le client confirme sur son appareil |

<Tip>
  Créez au moins un client de test dans `/clients` avant d'envoyer un paiement sans scénario, sinon tous les numéros renvoient `UNKNOWN_MSISDN`. Pour un résultat déterministe, passez plutôt un `scenario` explicite — il l'emporte toujours.
</Tip>

<Note>
  Comportement configurable par environnement : la politique `unknownMsisdnPolicy` vaut `reject` par défaut (numéro inconnu → `UNKNOWN_MSISDN`). En mode `passthrough`, un numéro inconnu est transmis à l'adaptateur opérateur (ancien comportement). Les cas bloqué / solde insuffisant restent toujours définitifs.
</Note>

## Exemple — déclencher `pin_invalid`

```bash theme={null}
curl -X POST https://api.sandpay.dev/v1/payments \
  -H "Authorization: Bearer $SANDPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "FCFA",
    "operator": "mtn",
    "country": "CI",
    "msisdn": "+22507123456",
    "reference": "ORDER-PIN-01",
    "scenario": "pin_invalid"
  }'
```

Réponse :

```json theme={null}
{
  "id": "TX_2L9P4R",
  "status": "PIN_INVALID",
  "latencyMs": 940,
  "scenario": "pin_invalid"
}
```

## Statuts et finalité

Les 11 statuts canoniques sont stables — votre `switch` peut les compter. `PENDING` est le seul statut non-final : il indique qu'une transaction est en cours de traitement (utile sur les flots asynchrones). Tous les autres statuts sont définitifs et déclenchent le webhook `payment.completed`.

## Hors-scénario : `description`

Le champ `description` (optionnel) est purement informatif — il est conservé dans la transaction et renvoyé dans le payload du webhook, mais n'influence pas le résultat.

## Raw operator response (`raw`)

Chaque réponse de paiement (`POST /v1/payments`, `GET /v1/payments/{id}`, items de `GET /v1/payments`, et webhook `payment.completed`) contient un champ `raw` avec la **shape native de l'opérateur** correspondante au scénario joué.

Cette shape est synthétisée avec `_simulated: true` au top-level — elle reproduit fidèlement la structure native que renvoie chaque opérateur.

Exemple MTN — `scenario: "success"` :

```json theme={null}
{
  "_simulated": true,
  "amount": "25000",
  "currency": "FCFA",
  "externalId": "ORDER-2026-A1",
  "payer": { "partyIdType": "MSISDN", "partyId": "22507123456" },
  "payerMessage": "Premium upgrade",
  "payeeNote": "ORDER-2026-A1",
  "status": "SUCCESSFUL",
  "financialTransactionId": "SIM_A1B2C3D4"
}
```

Exemple Moov — `scenario: "low_balance"` :

```json theme={null}
{
  "_simulated": true,
  "responseCode": "51",
  "responseMessage": "Insufficient funds",
  "transactionId": "MV-SIM_A1B2C3D4",
  "externalReference": "ORDER-2026-A1",
  "amount": "25000",
  "currency": "FCFA",
  "phoneNumber": "22507123456",
  "timestamp": "2026-05-23T10:14:02.412Z"
}
```

Exemple Orange — `scenario: "success"` (notez la faute d'orthographe `SUCCESSFULL` reproduite fidèlement de l'API réelle) :

```json theme={null}
{
  "_simulated": true,
  "status": "SUCCESSFULL",
  "subscriber_msisdn": "22507123456",
  "amount": 25000,
  "txnid": "SIM_A1B2C3D4",
  "pay_token": "ORANGE_SIM_A1B2C3D4",
  "confirmtxnstatus": "200",
  "confirmtxnmessage": "Transaction successfully processed",
  "order_id": "ORDER-2026-A1"
}
```

## Voir aussi

* [Démarrage rapide](/fr/quickstart) — premier appel
* [Webhooks](/fr/webhooks) — recevoir le statut final en push
* [Erreurs](/fr/errors) — codes HTTP et stratégie de retry
