Motifs d'échec
Lire failure_reason sur un paiement, un transfert ou un remboursement échoué.
Un 4xx signifie que la requête a été refusée. Cette page traite de l'autre type d'échec : une
requête qui a réussi, a créé une ressource, puis a échoué plus tard chez l'opérateur. L'objet et son
webhook portent deux champs qui l'expliquent.
failure_reasonstringfacultatifstatus: "failed".failure_messagestringfacultatif`reason` est un autre champ
Les transferts et les remboursements ont aussi un champ reason, qui n'a rien à voir avec l'échec :
c'est le libellé que vous avez fourni en créant le payout ou le remboursement
(requested_by_customer, Salaire septembre). Un transfert échoué porte les deux, et ils
signifient des choses opposées. Lisez toujours failure_reason.
Où il apparaît
Cette même paire est livrée dans les webhooks payment.failed, transfer.failed et
refund.failed, puisque ces payloads sont la ressource elle-même.
| Objet | Champs portés | Présents quand |
|---|---|---|
| Transfert | failure_reason, failure_message | Tant que status vaut failed ou cancelled. |
| Remboursement | failure_reason, failure_message | Uniquement tant que status vaut failed. |
| Paiement | failure_reason, failure_message | Uniquement tant que status vaut failed. Voir Paiements échoués. |
Codes émis par Wajub
Ces codes sont stables et la liste est exhaustive.
Transferts
failure_reason | Ce qui s'est passé | Que faire |
|---|---|---|
team_restricted | Les payouts sont suspendus sur votre compte. | Contactez le support. Ne relancez pas. |
invalid_recipient | Le bénéficiaire est absent, ou n'a aucun canal de payout. | Corrigez le bénéficiaire, puis créez un nouveau transfert. |
no_provider | Aucun prestataire n'est éligible pour ce canal et ce montant pour le moment. | Réessayez plus tard, ou passez par un autre canal. |
provider_rejected | Tous les prestataires éligibles ont refusé le payout. | Lisez failure_message, corrigez, puis créez-en un nouveau. |
admin_rejected | Wajub l'a refusé après examen. | Contactez le support. Ne relancez pas. |
merchant_rejected | Vous l'avez rejeté depuis le Dashboard avant son envoi. | Rien : vous l'avez arrêté. Ne relancez pas. |
cancelled_by_wajub | Wajub l'a annulé avant son envoi. | Contactez le support. Ne relancez pas. |
expired | Il n'a pas été envoyé dans le délai de transfert de votre compte (réglages des transferts) : il a été annulé et son montant rendu à votre solde. Live uniquement. | Rien à défaire : créez un nouveau transfert s'il est toujours dû. |
Les trois derniers accompagnent un payout cancelled.
Remboursements
Un remboursement échoué porte toujours l'un de ces codes, en live comme en sandbox.
failure_message garde la phrase du fournisseur quand il en a envoyé une.
failure_reason | Ce qui s'est passé | Que faire |
|---|---|---|
insufficient_funds | Le fournisseur n'a pas pu financer le remboursement. | Réessayez plus tard, une fois. |
refund_rejected_by_issuer | Un émetteur de carte l'a refusé. | Remboursez le client autrement. |
transaction_already_reversed | Le paiement était déjà annulé ou remboursé chez le fournisseur. | Vérifiez le Dashboard avant d'agir. |
invalid_number | Le numéro ou le compte du payeur ne peut pas le recevoir. Live uniquement. | Demandez au client un autre moyen de le rembourser. |
limit_exceeded | Un plafond du compte destinataire ou du fournisseur a été atteint. Live uniquement. | Réessayez plus tard, une fois. |
network_error | Le fournisseur n'a pas pu être joint à temps. | Une nouvelle tentative est raisonnable. |
provider_error | Le fournisseur était indisponible, ou a refusé l'appel lui-même. | Attendez, puis réessayez une fois. |
provider_declined | Le fournisseur l'a refusé sans dire pourquoi. | Lisez failure_message, puis décidez. |
ops_failed | Wajub n'a pas pu mener le remboursement à bien. Live uniquement. | Contactez le support avant de rembourser à nouveau. |
cancelled_by_wajub | Wajub a annulé le remboursement avant de l'envoyer : rien n'a quitté votre solde. Le remboursement est cancelled. Live uniquement. | Remboursez de nouveau si le client attend toujours son argent. |
Un remboursement peut rester pending ou processing un moment ; failed est définitif. Une
nouvelle tentative est un nouveau remboursement.
Paiements échoués
Un paiement échoué porte la raison de sa dernière tentative échouée, sur le paiement et dans le
webhook payment.failed :
failure_reason | Quand |
|---|---|
| Le code du prestataire | Le prestataire a refusé la tentative avec un code. Il est transmis tel quel, voir Codes émis par l'opérateur, et failure_message porte la phrase du prestataire. |
payment_failed | La tentative a échoué sans code du prestataire. failure_message vaut The payment could not be completed. |
declined, authentication_failed | Sandbox uniquement, voir La sandbox est déterministe. |
Les paiements échoués plus anciens peuvent ne porter aucun de ces deux champs.
Le status distingue toujours les trois cas qui changent la suite :
status | Ce qui s'est passé |
|---|---|
failed | L'opérateur a refusé le débit. |
expired | Le client n'a pas validé à temps et la session a expiré. |
cancelled | Le client, ou votre propre DELETE /payments/{id}, l'a arrêté. |
Pour chaque tentative, pas seulement la dernière, ouvrez le paiement dans Konsole : sa chronologie montre chaque tentative, le prestataire concerné et la réponse brute de la passerelle.
Codes émis par l'opérateur
Tout ce qui ne figure pas dans les tableaux ci-dessus est le code d'erreur propre au prestataire, transmis sans modification. Les opérateurs ne partagent aucun vocabulaire : MTN, Orange, Wave et un acquéreur de cartes nomment chacun différemment la même erreur du client, et un nouveau code peut apparaître à tout moment.
Ne construisez pas un switch exhaustif
Traitez les deux tableaux ci-dessus comme des cas connus et tout le reste comme une seule branche
par défaut : affichez failure_message, laissez le client agir et ne relancez pas
automatiquement. Un match sans branche par défaut cassera la première fois qu'un opérateur
publiera un nouveau code.
const RETRY_NEVER = new Set([
'team_restricted',
'admin_rejected',
'merchant_rejected',
'invalid_recipient',
]);
function onTransferFailed(transfer) {
const reason = transfer.failure_reason;
if (RETRY_NEVER.has(reason)) {
return escalateToSupport(transfer);
}
if (reason === 'no_provider') {
return scheduleRetry(transfer, { in: '15m' });
}
return notifyMerchant(transfer.failure_message);
}La sandbox est déterministe
La sandbox n'appelle aucun opérateur : le numéro, le compte ou l'adresse de test que vous utilisez décide du résultat, ce qui permet d'exercer volontairement chaque branche de votre handler avant de passer en live. Les tables des numéros de test se trouvent dans Scénarios de test.
Transferts. Les six derniers chiffres d'un numéro de test Mobile Money (et le suffixe d'un compte bancaire ou d'une adresse crypto de test) choisissent le résultat. Un transfert qui échoue porte l'une de ces trois raisons :
| Suffixe | Résultat | failure_reason |
|---|---|---|
000000 | succeeded | |
000001 | Refusé aussitôt par un 422, error_code: insufficient_funds ; aucun transfert n'est créé. | |
000002 | failed | failure |
000003 | failed | network_error |
000004 | failed | invalid_recipient |
Un numéro qui n'est pas un numéro de test est refusé par un 422 qui liste ceux à utiliser.
Remboursements. Le moyen de paiement du paiement remboursé décide : un paiement Mobile Money
réglé avec un numéro finissant par 000000 est remboursé, un numéro finissant par 000001 à
000004 ou par 000009 échoue ; la carte 4000 0000 0000 5423 est débitée mais ses remboursements
échouent toujours ; les paiements par banque et crypto sont toujours remboursés. Seul un paiement
sans moyen de paiement rattaché n'a pas d'issue fixée. Le failure_reason
d'un remboursement sandbox échoué est tiré de la même liste qu'en live, dans
Remboursements.
Paiements. Les six derniers chiffres du numéro de test décident du statut : 000002 se termine
en failed, 000003 en expired, 000004 en cancelled. 000001 est refusé aussitôt par un
402 avec error_code: insufficient_funds, comme un refus en live, et le paiement reste pending.
Un paiement sandbox qui échoue en donne la raison avec une paire fixe, choisie par le numéro ou la
carte de test utilisé :
| Scénario | failure_reason | failure_message |
|---|---|---|
Échec (…000002, une carte refusée) | declined | The operator declined the payment. |
| L'émetteur refuse après 3D Secure | declined | The issuer declined the card after authentication. |
| Le payeur échoue au 3D Secure | authentication_failed | The payer did not pass 3D Secure authentication. |
Un échec n'est pas toujours la fin
Un no_provider ou une erreur réseau laisse votre solde intact : la réservation est libérée quand
le transfert échoue. Vérifiez le solde plutôt que de le supposer, puis créez un
nouveau transfert avec une nouvelle clé d'idempotence.