Numéros de test
La sandbox n'est pas une maquette : un paiement de test traverse la même file, le même ledger et le même webhook qu'un paiement réel. Seul l'opérateur est simulé.
Le scénario est choisi par les QUATRE DERNIERS CHIFFRES du numéro. Tout numéro non listé réussit : le cas nominal ne doit demander aucun effort.
Ils n'ont d'effet qu'avec une clé de test. En production, le numéro est celui du vrai payeur — ou du vrai bénéficiaire.
Le catalogue — encaissements
Le numéro est celui du payeur.
| Numéro | Statut final | Code d'échec | Ce qui se passe |
|---|---|---|---|
…0000 | succeeded | — | Encaissement réussi. |
…0001 | failed | declined_by_payer | Le payeur refuse la demande sur son téléphone. |
…0002 | failed | insufficient_funds | Le compte du payeur n'est pas assez approvisionné. |
…0003 | succeeded | — | L'opérateur ne répond pas. L'issue est INCERTAINE : l'appel n'est jamais rejoué, une re-vérification tranche — ici en faveur du succès. C'est le scénario à éprouver en priorité. |
…0004 | succeeded | — | Réponse 200 au corps inexploitable. Issue incertaine également, résolue par re-vérification. |
…0005 | failed | operator_unavailable | L'opérateur est momentanément hors service. |
…0006 | succeeded | — | Le paiement reste pending le temps que le payeur valide, puis réussit. |
…0007 | failed | limit_exceeded | Le montant dépasse le plafond du payeur. |
…0008 | failed | invalid_recipient | Numéro inconnu chez cet opérateur. |
…0009 | failed | declined_by_payer | Le paiement reste pending, puis échoue faute de validation à temps. |
Les mêmes numéros pilotent les décaissements
Le numéro est alors celui du bénéficiaire. Statuts et codes d'échec sont identiques — le connecteur simulé emprunte le même chemin pour l'entrée et pour la sortie.
| Numéro | Statut final | Code d'échec | Ce qui se passe |
|---|---|---|---|
…0000 | succeeded | — | Décaissement réussi : le bénéficiaire reçoit les fonds. |
…0001 | failed | declined_by_payer | Le compte du bénéficiaire refuse le versement. Le code reste declined_by_payer — il est écrit du point de vue de l'encaissement. |
…0002 | failed | insufficient_funds | Le flotteur de l'opérateur est à sec. Ce n'est pas votre solde : une provision insuffisante chez vous est refusée bien plus tôt, en 409. |
…0003 | succeeded | — | L'opérateur ne répond pas. Issue INCERTAINE — l'envoi est peut-être parti. |
…0004 | succeeded | — | Réponse inexploitable. Issue incertaine également. |
…0005 | failed | operator_unavailable | L'opérateur est hors service ; rien n'est parti. |
…0006 | succeeded | — | L'envoi reste pending, puis aboutit. |
…0007 | failed | limit_exceeded | Le versement dépasse un plafond de l'opérateur. Distinct de VOS plafonds, qui mettent la demande en attente de validation sans jamais l'envoyer. |
…0008 | failed | invalid_recipient | Numéro inconnu chez cet opérateur : le bénéficiaire n'existe pas. |
…0009 | failed | declined_by_payer | L'envoi reste pending, puis échoue. |
Deux codes sont écrits du point de vue de l'encaissement et se lisent de travers sur un décaissement. Nous ne les renommons pas : votre intégration les traite peut-être déjà, et changer un code stable coûte plus cher que l'expliquer.
declined_by_payersur un décaissement : c'est le compte du bénéficiaire qui refuse.insufficient_fundssur un décaissement : c'est le flotteur de l'opérateur, pas votre solde. Une provision insuffisante chez vous est refusée bien avant, à la création, en409.
Un échec de décaissement vous rend l'argent, commission comprise — l'événement payout.failed porte reverted: true. Éprouvez-le : c'est le chemin qu'une intégration oublie le plus souvent de traiter.
Le scénario qu'il faut vraiment éprouver
…0003 et …0004 produisent une issue incertaine : l'appel est parti, aucune réponse exploitable n'est revenue, et l'argent a peut-être bougé.
Nous ne rejouons jamais un appel dans cet état — un rejeu débiterait possiblement deux fois. Le paiement reste en cours, une re-vérification est programmée, et c'est elle qui tranche.
Pour vous, cela veut dire une chose : un paiement peut rester pending plus longtemps que d'habitude, puis conclure. Si votre intégration considère qu'un paiement non conclu en dix secondes a échoué, elle se trompera — et sur ces deux numéros, elle se trompera en votre défaveur.
Essayer
curl -X POST "https://qkmvexkljdcfarulydao.supabase.co/functions/v1/v1-payments" \
-H "Authorization: Bearer $VALSORIA_CLE" \
-H "Idempotency-Key: essai-refus-1" \
-H "Content-Type: application/json" \
-d '{ "amount": 5000, "currency": "XOF", "operator": "orange_ci",
"counterparty": { "msisdn": "0779140002" } }'
Le paiement naît created, puis échoue en insufficient_funds. Vous recevez payment.failed sur votre endpoint.
Pour une confirmation synchrone, sans passer par la file, ajoutez "simulate": "succeed" au corps. Pratique pour un test d'intégration continue ; inutile pour éprouver votre gestion des échecs.
Version d'API 2026-08-25 ·
changements ·
spécification OpenAPI