Aller au contenu
Chargement des API keys…

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_reasonstringfacultatif
Un code court, lisible par une machine. C'est sur ce champ qu'il faut aiguiller votre code. Présent uniquement une fois que l'objet atteint status: "failed".
failure_messagestringfacultatif
Une phrase destinée à un humain. Affichez-la dans votre back-office, journalisez-la, mais n'aiguillez jamais votre code dessus. Reprend le code quand le prestataire n'a fourni aucun texte.

Où il apparaît

Un transfert échoué
{
"id": "trf_01JXXXXXXXXXXXXX",
"status": "failed",
"amount": 100000,
"currency": "XAF",
"reason": "Salaire septembre",
"failure_reason": "invalid_recipient",
"failure_message": "Beneficiary has no payout channel."
}

Cette même paire est livrée dans les webhooks transfer.failed et refund.failed, puisque ces payloads sont la ressource elle-même.

ObjetChamps portésPrésents quand
Transfertfailure_reason, failure_messageUniquement tant que status vaut failed.
Remboursementfailure_reason, failure_messageUniquement tant que status vaut failed.
Paiementaucun aujourd'huiVoir Paiements échoués plus bas.

Codes émis par Wajub

Ces codes sont stables et la liste est exhaustive. Ils sont décidés avant tout appel à un opérateur, ou à sa place, ce qui explique que Wajub en maîtrise l'orthographe.

Transferts

failure_reasonCe qui s'est passéQue faire
team_restrictedLes payouts sont suspendus sur votre compte.Contactez le support. Ne relancez pas.
invalid_recipientLe bénéficiaire est absent, ou n'a aucun canal de payout.Corrigez le bénéficiaire, puis créez un nouveau transfert.
no_providerAucun prestataire n'est éligible pour ce canal et ce montant pour le moment.Réessayez plus tard, ou passez par un autre canal.
provider_rejectedTous les prestataires éligibles ont refusé le payout.Lisez failure_message, corrigez, puis créez-en un nouveau.
admin_rejectedUn membre de l'équipe Wajub l'a refusé lors d'une revue manuelle.Contactez le support. Ne relancez pas.

Remboursements

failure_reasonCe qui s'est passéQue faire
ops_failedUn membre de l'équipe Wajub a clôturé un remboursement manuel comme irrécupérable.Contactez le support avant de rembourser à nouveau.

Un remboursement que le traitement automatique n'a pas pu mener à bien n'est pas abandonné : il passe en exécution manuelle et reste pending pendant que l'équipe opérations le traite. failed marque la fin de ce parcours, pas son début.

Paiements échoués

Un paiement ne porte aujourd'hui aucun code d'échec. La PaymentResource déclare failure_reason, mais rien ne l'alimente : le code et le message du prestataire sont enregistrés sur la tentative de traitement sous-jacente, pas sur le paiement, si bien que le champ est absent de tous les payloads, y compris du webhook payment.failed.

Ce que vous obtenez à la place, c'est le status, et il distingue déjà les trois cas qui changent la suite :

statusCe qui s'est passé
failedL'opérateur a refusé le débit.
expiredLe client n'a pas validé à temps et la session a expiré.
cancelledLe client, ou votre propre DELETE /payments/{id}, l'a arrêté.

Pour lire les mots mêmes du prestataire, ouvrez le paiement dans Konsole : sa chronologie montre chaque tentative, le prestataire concerné et la réponse brute de la passerelle. C'est aussi là que le Dashboard puise quand il affiche une explication en langage clair à côté d'un paiement échoué.

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 prestataire peut introduire un nouveau code sans aucune mise en production de notre côté.

const RETRY_NEVER = new Set([
'team_restricted',
'admin_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 : elle pioche dans une liste fixe. Servez-vous-en pour exercer chaque branche de votre handler avant de passer en live.

Par défaut, les transferts en sandbox réussissent 80 % du temps ; les échecs sont tirés de cette liste :

failure_reasonfailure_message
invalid_recipientThe recipient account could not be found or is inactive.
network_errorA network error occurred. Please retry later.
failureThe payout provider could not complete the transfer.
provider_errorThe payout provider returned an error. Please retry later.
daily_limit_exceededDaily transfer limit exceeded for this channel.
unknownAn unexpected error occurred while processing the transfer.

Les remboursements en sandbox réussissent 70 % du temps ; les échecs sont tirés de cette liste :

failure_reasonfailure_message
insufficient_fundsThe refund could not be completed due to insufficient funds on the provider.
refund_rejected_by_issuerThe refund was rejected by the card issuer.
transaction_already_reversedThe original transaction has already been reversed or refunded.
network_errorA network error occurred while processing the refund.
provider_errorThe payment provider returned an error. Please retry later.
unknownAn unexpected error occurred while processing the refund.

Les paiements en sandbox suivent la même règle qu'en live : ils se terminent sur un statut, sans failure_reason. Le statut obtenu dépend des six derniers chiffres du numéro de test : 000002 pour failed, 000003 pour expired, 000004 pour cancelled. 000001 est la seule exception : il est refusé de manière synchrone avec un 422 sur insufficient_funds et le paiement reste pending. La liste complète se trouve dans Scénarios de test.

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.

Que pensez-vous de ce contenu ?