Aller au contenu

Remboursements

Restituer des fonds sur un paiement réussi, puis suivre le remboursement jusqu'à son propre résultat.

Un remboursement est un objet à part entière, avec son propre id, son propre statut et ses propres webhooks. Ce n'est pas un état dans lequel passe le paiement. La distinction compte plus qu'il n'y paraît : le paiement que vous remboursez reste succeeded pour toujours, et tout ce que vous voulez savoir sur l'argent qui revient se trouve sur le remboursement.

Vous pouvez restituer le montant total ou une partie, et cumuler plusieurs remboursements partiels jusqu'à épuisement du montant d'origine.

Ce qui peut être remboursé

Quatre conditions sont vérifiées avant l'enregistrement d'un remboursement, et chacune échoue avec son propre 422.

ConditionCe que vous recevez en cas d'échec
Le paiement est succeededTransaction must be completed to be refunded. Current status: …
Il reste un montant remboursableTransaction has already been fully refunded.
Le montant tient dans ce qui resteRefund amount exceeds refundable amount (…)
La devise correspond à celle du paiementRefunds must be in the transaction currency (XAF).

Le montant remboursable est le montant du paiement moins tout ce qui a déjà été remboursé, en comptant les remboursements encore pending comme ceux qui ont réussi. Un remboursement en attente réserve donc sa part immédiatement : c'est ce qui empêche deux appels simultanés de rembourser deux fois le même argent.

Un remboursement ne peut pas convertir une devise

currency est accepté, mais la seule valeur possible est la devise du paiement lui-même. Le champ existe pour que vous puissiez être explicite, pas pour rembourser en XOF un paiement en XAF. Si vous l'omettez, Wajub utilise de toute façon la devise du paiement.

Créer un remboursement

Le paiement est désigné par son id Wajub. Votre propre reference sur le paiement n'est pas acceptée ici, car la recherche porte sur l'id du paiement et rien d'autre.

POSThttps://api.wajub.com/refunds
paymentstringobligatoire
L'id du paiement, par exemple trx_test_CSUGajfv9xh0XQ5wu2lx. Votre reference marchand n'est pas acceptée.
reasonenumobligatoire
La raison du remboursement. Les valeurs acceptées sont listées plus bas.
amountnumberfacultatif
Montant dans l'unité principale, décimales autorisées et arrondies à deux. Omettez-le pour rembourser en totalité ce qui reste.
currencystringfacultatif
Doit être égale à la devise du paiement, qui est la valeur par défaut.
referencestringfacultatif
Votre propre référence pour ce remboursement, jusqu'à 255 caractères. Renvoyée à la lecture.
metadataobjectfacultatif
Données clé-valeur libres, renvoyées telles quelles sur l'objet remboursement.

reason est obligatoire et sa liste est fermée. Toute valeur hors de cette liste donne un 422 : choisissez la plus proche plutôt que d'en inventer une.

MotifQuand l'utiliser
requested_by_customerLe client l'a demandé, et vous avez accepté
duplicateLa même commande a été payée deux fois
fraudulentVous pensez que le paiement n'a pas été fait par le titulaire de la carte ou du compte
product_not_receivedLa marchandise n'est jamais arrivée
service_not_deliveredUn service a été payé mais pas fourni
wrong_amountVous avez débité plus que prévu
customer_complaintUne réclamation réglée sans désigner de responsable
merchant_errorVotre propre erreur : mauvais article, mauvais prix, mauvais client
network_errorLe paiement est passé de votre côté mais pas du leur
transaction_errorUne panne technique dans le paiement lui-même
reconciliationCorrection d'un écart trouvé lors d'un rapprochement

Envoyez l'appel depuis votre serveur, avec une Idempotency-Key si le remboursement peut être déclenché par un processus qui retente ses appels.

curl https://api.wajub.com/refunds \
  -H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-order-4172" \
  -d '{
    "payment": "trx_test_CSUGajfv9xh0XQ5wu2lx",
    "amount": 2000,
    "reason": "product_not_received",
    "reference": "rma-881"
  }'

Wajub répond 201 Created. Rien n'a encore quitté votre solde : le remboursement a été accepté et mis en file, et status vous indique où il en est.

Réponse · 201 Created
{
"code": 201,
"status": "Created",
"message": "Refund created successfully",
"refund": {
"id": "rfd_test_9xh0XQ5wu2lxCSUGajfv",
"transaction": "trx_test_CSUGajfv9xh0XQ5wu2lx",
"reference": "rma-881",
"amount": 2000,
"currency": "XAF",
"status": "processing",
"reason": "product_not_received",
"metadata": {
},
"sandbox": true,
"created_at": "2026-09-11T10:30:00Z"
}
}

Le paiement est porté par transaction, pas par payment. Les lectures renvoient aussi ledger_balance_before, ledger_balance_after et ledger_balance_currency une fois le remboursement réglé, un détail tax quand une taxe s'applique, une timeline des changements de statut, et failure_reason avec failure_message quand le remboursement a échoué.

Idempotency-Key, et ce sur quoi porte la clé

La clé est hachée avec le payload, et la réponse est rejouée pendant 24 heures. Réutilisez la même clé avec un montant différent et vous obtenez un nouveau remboursement, pas celui en cache : dérivez donc la clé de ce que vous remboursez plutôt que de la tentative.

Le remboursement a son propre cycle de vie

Cinq états, dont trois terminaux. Un remboursement ne revient jamais à un état précédent.

Le délai ne dépend pas de Wajub. Sur une carte, c'est l'émetteur qui décide, et plusieurs jours sont normaux. Sur Mobile Money, le crédit arrive généralement en quelques minutes, parfois en quelques heures. Rien dans votre intégration n'y change quoi que ce soit : annoncez au client une fourchette plutôt qu'une heure précise.

Le statut du paiement ne change jamais

C'est le point qui piège tout le monde. Rembourser ne fait pas passer le paiement à refunded ou partially_refunded. Ces deux valeurs existent dans le vocabulaire de l'API, mais rien ne les écrit jamais sur un paiement, et un paiement remboursé en totalité affiche toujours succeeded.

Il n'y a donc aucun indicateur à interroger. Pour savoir où en est un paiement, listez ses remboursements et additionnez ceux qui sont pending ou succeeded.

GEThttps://api.wajub.com/payments/{id}/refunds

Soustrayez ce total du montant du paiement et vous obtenez ce qui reste remboursable, c'est-à-dire exactement le chiffre que l'API vérifie quand vous envoyez le remboursement suivant.

L'argent bouge au succès, pas à la création

Créer un remboursement réserve le montant sur ce qui est remboursable, mais ne touche à aucun solde. Le débit a lieu quand le remboursement atteint succeeded, et seulement à ce moment-là.

Deux conséquences à anticiper. Un remboursement en sandbox ne fait jamais bouger un solde : les chiffres de la sandbox ne vous disent donc rien de votre trésorerie. Et un remboursement émis sur un paiement déjà perdu dans un litige est plafonné au montant principal qui n'a pas déjà été repris : le même argent ne peut pas sortir deux fois par deux portes différentes.

Ce que devient ensuite le solde, et comment se comporte un solde négatif, est expliqué dans Solde et règlements.

Webhooks

Quatre événements suivent un remboursement. Abonnez-vous à refund.* pour les recevoir tous.

ÉvénementDéclenché quand
refund.createdLe remboursement a été accepté et enregistré
refund.succeededFonds restitués. C'est sur celui-ci qu'il faut faire le rapprochement
refund.failedLe prestataire l'a refusé ou n'a pas pu le finaliser, lisez failure_reason
refund.cancelledArrêté avant d'être finalisé
refund.succeededévénement
Un remboursement total ou partiel est terminé sur le paiement.
Payload
{
"id": "evt_4Rf8Lm2Xc9",
"type": "refund.succeeded",
"created_at": "2026-09-11T11:02:41.000Z",
"sandbox": false,
"data": {
"id": "rfd_9xh0XQ5wu2lxCSUGajfv",
"transaction": "trx_CSUGajfv9xh0XQ5wu2lx",
"reference": "rma-881",
"amount": 2000,
"currency": "XAF",
"status": "succeeded",
"reason": "product_not_received"
}
}

Dédupliquez sur X-Wajub-Delivery-Id comme partout ailleurs. L'enregistrement et la vérification de signature sont dans Webhooks.

Tester un remboursement en échec dans la sandbox

Les résultats en sandbox sont déterministes, et ils dépendent du moyen de paiement du paiement d'origine, pas de ce que vous envoyez sur le remboursement. Payez avec le bon numéro, remboursez ensuite, et vous obtenez le résultat voulu.

Pour un paiement fait par Mobile Money, ce sont les six derniers chiffres du numéro de téléphone qui décident.

Suffixe sur le paiement d'origineRésultat du remboursement
000000Réussit
000009Le paiement réussit, le remboursement échoue toujours. C'est celui à utiliser pour vos tests
000001, 000002, 000003, 000004Échoue
Tout autre suffixeRéussit

Pour un paiement par carte, 4000000000005423 débite normalement puis fait échouer chaque remboursement. Toutes les autres cartes de test se remboursent sans problème. Les paiements en cryptomonnaies et les paiements bancaires se remboursent toujours avec succès, car il n'existe pas d'adresse d'échec pour les tester.

Un remboursement échoué porte un failure_reason parmi insufficient_funds, refund_rejected_by_issuer, transaction_already_reversed, network_error, provider_error ou unknown, accompagné d'un failure_message lisible.

Le seul cas aléatoire

Quand le paiement d'origine n'a aucun moyen de paiement rattaché, la sandbox se rabat sur un tirage au sort pondéré pour réussir environ sept fois sur dix. Si vos tests sont instables, voici pourquoi : payez avec un vrai numéro de test pour que le résultat soit choisi et non tiré au hasard.

Lister les remboursements

Les remboursements sont renvoyés du plus récent au plus ancien, tous paiements confondus.

GEThttps://api.wajub.com/refunds
Paramètre de requêteAccepte
per_pageDe 1 à 100, 25 par défaut
statuspending, processing, succeeded, failed, cancelled
searchTexte libre sur la référence et l'id du remboursement
date_from, date_toDates, date_to jamais antérieure à date_from
cursorPasse en pagination par curseur, pour les exports

La récupération d'un remboursement se fait par son id.

GEThttps://api.wajub.com/refunds/{id}

Dans le Dashboard

Un remboursement peut être émis sans écrire de code. Ouvrez un paiement dans Payments, utilisez son action de remboursement, et le résultat est le même objet que celui que crée l'API. L'action exige la permission refund_payments, distincte de manage_payments : un membre de l'équipe peut ainsi gérer les commandes sans pouvoir rendre de l'argent.

Refunds les liste tous avec leur statut, et les exporte en CSV ou Excel en production.

Refund requests est le côté entrant. Un payeur qui utilise Pocket peut demander le remboursement d'un paiement qu'il a effectué, et la demande y attend jusqu'à ce que quelqu'un l'approuve ou la rejette. Rien n'est remboursé avant l'approbation, et l'approbation crée le remboursement exactement comme le ferait l'API.

Que pensez-vous de ce contenu ?