Aller au contenu

Paiements

Créer un paiement, suivre son statut, rejouer une requête sans doublon et traiter une issue inconnue.

Un paiement est une demande d’encaissement adressée à un client, sur un moyen de paiement donné. Il est enregistré à created, passe à pending dès que l’opérateur a répondu (la réponse de création porte déjà pending), puis se termine en confirmed, failed ou expired.

Créer un paiement

Terminal
curl -X POST "$API_URL/v1/payments" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: commande-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "5000",
    "currency": "XOF",
    "rail": "sn_wave",
    "merchant_reference": "commande-1042",
    "return_url": "https://boutique.example/paiement/retour",
    "customer_phone": "77 123 45 67"
  }'
ChampRequisDescription
amountouiMontant en XOF : chaîne de chiffres, au moins le min_amount du moyen de paiement ("5000", pas 5000).
currencyouiToujours "XOF".
railouiIdentifiant du moyen de paiement : sn_wave, ci_orange… Voir Moyens de paiement et pays.
merchant_referenceouiRéférence de la commande : 1 à 128 caractères parmi lettres, chiffres et . _ : / # -. Unique par projet.
return_urlouiURL https:// où revient le client après le paiement. Une URL http://, y compris http://localhost, est refusée (400 INVALID_REQUEST, champ return_url).
customer_phonemode réelNuméro mobile du client, valide dans le pays du moyen de paiement. Format local (77 123 45 67) ou E.164 (+221771234567) ; renvoyé au format E.164. Sert aussi aux remboursements.
customer_otpci_orangeCode à usage unique du client, 4 à 8 chiffres. Voir Moyens de paiement et pays, section « Code OTP ».
customer_devicenonsn_orange : appareil du client, mobile (défaut) ou desktop. mobile ouvre directement l’application Max it ; desktop renvoie un QR code (next_action.type = qr, valable 5 minutes) que le client scanne avec son téléphone. Le numéro customer_phone reste requis en mode réel, aussi avec desktop. Les pages de paiement 221 Pay choisissent seules selon l’appareil.
offer_codenonCode promo du projet. amount est alors réduit de la remise ; la réponse ajoute offer_code, discount_amount et original_amount.

Un champ absent de ce tableau est refusé : 400 INVALID_REQUEST, avec fields.<nom du champ>. Les champs de la réponse, comme payment_link_description, ne s’envoient pas.

Réponse

201 à la création, 200 pour une requête rejouée avec la même Idempotency-Key et le même corps.

201 · application/json
{
  "id": "8d1f0c6e-3b7a-4c55-9a51-2f0e5c1d7a90",
  "status": "pending",
  "amount": "5000",
  "currency": "XOF",
  "rail": "sn_wave",
  "merchant_reference": "commande-1042",
  "checkout_url": "https://…",
  "next_action": { "type": "redirect", "url": "https://…" },
  "created_at": "2026-10-07T10:00:00Z",
  "withdrawal_eligibility": "not_confirmed",
  "fee": null,
  "net": null,
  "livemode": false
}

Rediriger le client vers checkout_url. Sur Wave, next_action est une redirection (type redirect) vers la même page. livemode vaut false avec une clé de test, true avec une clé live. fee (frais) et net (montant crédité au marchand) sont renseignés à la confirmation.

Un paiement fait sur un lien de paiement porte en plus payment_link_description, la description du lien, à afficher à la place de merchant_reference (de la forme link:<lien>:<paiement>). Le champ est absent des autres paiements.

Action client : next_action

Selon l’opérateur, la réponse porte next_action, l’étape à accomplir par le client. checkout_url vaut alors null quand l’opérateur ne fournit pas de page.

typeChamp utileTraitement
redirecturlRediriger le client vers url.
qrqr_codeAfficher l’image qr_code (PNG en URI data:, utilisable telle quelle comme source d’une image). Le client la scanne avec son application mobile money.
instructionmessageAfficher message tel quel : le client suit l’instruction sur son téléphone.

L’issue arrive ensuite par webhook ou par relecture du paiement.

Statuts

StatutSignificationÉvénement webhook
createdPaiement enregistré.Aucun
pendingDemande transmise, paiement du client attendu.Aucun
confirmedFonds reçus, fee et net renseignés, montant net crédité au solde disponible.payment.succeeded
failedPaiement refusé ou abandonné. error_code : provider_failed.payment.failed
expiredDélai de paiement dépassé. error_code : expired.payment.expired
unknownIssue incertaine après un incident. Voir « Issue inconnue ».Aucun

confirmed est définitif. withdrawal_eligibility passe alors à confirmed : le montant net peut être retiré.

Relire et lister

Terminal
curl "$API_URL/v1/payments/$PAYMENT_ID" -H "Authorization: Bearer $API_KEY"
curl "$API_URL/v1/payments?status=confirmed,failed&limit=20" -H "Authorization: Bearer $API_KEY"

GET /v1/payments/{id} ajoute au paiement error_code, error_message (dans la langue de Accept-Language), customer.phone et refunds.

Filtres de GET /v1/payments :

ParamètreDescription
statusUn ou plusieurs statuts, répétés ou séparés par des virgules.
railMoyen de paiement.
qIdentifiant du paiement, ou merchant_reference exacte ou par préfixe.
customer_phoneNuméro du client.
from, toPlage de created_at (RFC 3339) : from inclus, to exclu.
limit1 à 100, 20 par défaut.
cursorValeur de next_cursor de la page précédente ; null en dernière page.

La réponse contient data, next_cursor et total_count.

Rejouer sans doublon : Idempotency-Key

L’en-tête Idempotency-Key est obligatoire sur POST /v1/payments (1 à 128 caractères). Une clé désigne une tentative logique, par exemple une commande.

  • Même clé, même corps : le même paiement est renvoyé (200), jamais un second.
  • Même clé, corps différent : 409 IDEMPOTENCY_CONFLICT, rien n’est créé.
  • Une clé ne vaut que pour la route où elle a servi.

Après un délai d’attente ou une coupure réseau, renvoyer exactement la même requête avec la même clé. merchant_reference est unique par projet : une référence déjà utilisée avec une autre clé renvoie 409 DUPLICATE_MERCHANT_REFERENCE.

Issue inconnue

Une coupure peut rendre l’issue d’un paiement incertaine. 221 Pay ne redemande jamais un paiement de lui-même : un double encaissement est exclu.

SituationConduite
Aucune réponse à POST /v1/paymentsRejouer la même requête avec la même Idempotency-Key.
202 avec seulement idRelire GET /v1/payments/{id} jusqu’à un statut final.
Statut unknownGarder la commande en attente, sans second paiement. Le webhook ou la relecture donne l’issue dès la réponse de l’opérateur.
503 CHANNEL_UNAVAILABLEProposer un autre moyen de paiement ou réessayer plus tard.

Un paiement unknown ne crédite le solde qu’une fois confirmé.

Erreurs fréquentes

StatutcodeCause
400INVALID_REQUESTChamps invalides, un message par champ dans fields.
403MERCHANT_SUSPENDEDCompte marchand suspendu.
403MERCHANT_LIMIT_EXCEEDEDPlafond journalier ou mensuel d’encaissement atteint.
409IDEMPOTENCY_CONFLICTClé déjà utilisée avec un autre corps.
409DUPLICATE_MERCHANT_REFERENCEmerchant_reference déjà utilisée dans le projet.
503CHANNEL_UNAVAILABLEMoyen de paiement non activé sur le projet ou momentanément fermé.

Table complète : Erreurs.