Encaisser un paiement
Créer
curl -X POST "$LIBREPAY_BASE/v1-payments" \
-H "Authorization: Bearer $LIBREPAY_CLE" \
-H "Idempotency-Key: cmd-2026-00412" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"currency": "XOF",
"operator": "orange_ci",
"counterparty": { "msisdn": "0779149021" },
"merchant_reference": "CMD-2026-00412"
}'
{
"id": "txn_6INMt2lIL1Pv_y6RgqZvag",
"object": "payment",
"amount": 10000,
"currency": "XOF",
"status": "created",
"operator": "orange_ci",
"merchant_reference": "CMD-2026-00412",
"livemode": false,
"created_at": "2026-08-26T09:12:44.318Z"
}
Ce que vous touchez réellement
La réponse porte quatre montants, et ils ne disent pas la même chose :
| Champ | Ce que c'est |
|---|---|
amount | Ce que vous avez demandé |
amount_collected | Ce que le payeur débourse |
fee_amount | La commission LibrePay |
net_amount | Ce que vous touchez |
Qui supporte les frais dépend de votre barème, réglé par service. Si c'est vous, le payeur verse amount et vous recevez amount − fee_amount. Si c'est le payeur, il verse amount + fee_amount et vous recevez amount entier.
La commission est figée à la création. Un changement de barème ne modifie jamais une transaction passée, et vous la connaissez avant que l'argent ne bouge — pas après.
Le cycle de vie
Un paiement passe par des états, et la réponse HTTP ne vous donne que le premier. Le payeur doit encore valider sur son téléphone.
| Statut | Ce que ça veut dire |
|---|---|
created | Le paiement existe. Rien n'est parti chez l'opérateur. |
pending | L'opérateur travaille. Le payeur valide, ou pas. |
succeeded | L'argent est encaissé. C'est le seul état où vous pouvez livrer. |
failed | Refusé. failure_code dit pourquoi. |
expired | Le payeur n'a jamais répondu. |
refunded / partially_refunded | Tout ou partie du montant a été rendu au payeur. Vous l'apprenez par webhook. |
Ne livrez jamais sur created ni sur pending. Attendez succeeded, par webhook — c'est ce pour quoi il existe.
Les opérateurs qui hébergent leur page : redirect_url
Orange Money, MTN MoMo et Moov Money poussent : ils ouvrent un dialogue sur le téléphone de votre client, qui n'a nulle part à aller. redirect_url vaut alors null, et ce n'est pas un manque.
Wave ne pousse pas. Il héberge sa propre page de paiement, et tant que votre client ne l'a pas ouverte, il ne se passe strictement rien : pas de dialogue, pas de SMS, aucun événement. Le paiement expire, et personne ne saura pourquoi.
redirect_url est donc, sur Wave, le seul chemin vers l'argent. Envoyez-y votre client.
{
"id": "txn_6INMt2lIL1Pv_y6RgqZvag",
"status": "pending",
"operator": "wave_ci",
"redirect_url": "https://pay.wave.com/c/cos-xxxx"
}
⚠ Il est null dans la réponse de création. À cet instant, l'opérateur n'a pas encore été appelé — nous ne vous faisons pas attendre sa disponibilité pour vous répondre. Deux façons de l'obtenir :
- l'événement
payment.action_required, qui le porte et n'est émis que lorsqu'il y a réellement quelque chose à faire faire à votre client ; GET /v1/payments/{id}, où il apparaît en une seconde ou deux.
Il n'est rendu que tant que l'issue est ouverte. Après coup, la page de l'opérateur proposerait à votre client de payer une seconde fois.
Si vous utilisez la page de paiement LibrePay, vous n'avez rien à faire : elle y envoie le payeur et le ramène elle-même.
L'idempotence, et pourquoi elle vous concerne
Idempotency-Key est obligatoire. Rejouez la même clé avec le même corps : vous recevez la réponse d'origine, statut compris, et aucun second paiement n'est créé.
Un rejeu rend donc 201, comme la création. 201 signifie « voici le paiement qui correspond à cette clé », pas « un de plus ». Fiez-vous à l'id, jamais au code HTTP, pour savoir si vous devez décrémenter un stock.
La clé doit venir de votre commande — cmd-2026-00412 — et pas d'un uuid() régénéré à chaque tentative. Une clé neuve par tentative ne protège de rien : c'est l'erreur la plus fréquente, et la plus coûteuse.
Réutiliser une clé avec un corps différent rend 422 idempotency_key_reused : c'est une erreur d'intégration, pas une reprise, et vous le dire tôt évite de vous rendre la réponse d'une autre requête.
merchant_reference
Votre référence, unique par marchand. Une seconde transaction portant la même valeur est refusée en 409 duplicate_reference. C'est une seconde protection contre le double encaissement, indépendante de l'idempotence — utile quand la reprise ne vient pas du même processus.
Lire un paiement
curl "$LIBREPAY_BASE/v1-payments/txn_6INMt2lIL1Pv_y6RgqZvag" \
-H "Authorization: Bearer $LIBREPAY_CLE"
Le paiement d'un autre marchand rend 404, jamais 403 : répondre 403 confirmerait son existence.
N'interrogez pas en boucle. Le webhook vous prévient ; le GET sert à réconcilier, ou à répondre à un client qui appelle.
Version d'API 2026-08-25 ·
changements ·
spécification OpenAPI