Encaisser un paiement
Créer
curl -X POST "$VALSORIA_BASE/v1-payments" \
-H "Authorization: Bearer $VALSORIA_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.
Un remboursement la rend — et il se demande à LibrePay, non par l'API (traités par LibrePay sur justification, voir catalogue des erreurs). Rembourser intégralement un paiement ne vous coûte aucune commission : elle est contre-passée au prorata, et intégralement sur un remboursement total. Vous ne pouvez donc jamais vous retrouver débiteur du fait d'un remboursement.
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 | Remboursé par LibrePay. Vous le recevez par webhook — vous ne le demandez pas. |
Ne livrez jamais sur created ni sur pending. Attendez succeeded, par webhook — c'est ce pour quoi il existe.
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 "$VALSORIA_BASE/v1-payments/txn_6INMt2lIL1Pv_y6RgqZvag" \
-H "Authorization: Bearer $VALSORIA_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