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 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 | Uniquement tant que status vaut failed. |
| Remboursement | failure_reason, failure_message | Uniquement tant que status vaut failed. |
| Paiement | aucun aujourd'hui | Voir 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_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 | Un membre de l'équipe Wajub l'a refusé lors d'une revue manuelle. | Contactez le support. Ne relancez pas. |
Remboursements
failure_reason | Ce qui s'est passé | Que faire |
|---|---|---|
ops_failed | Un 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 :
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 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é.
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',
'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_reason | failure_message |
|---|---|
invalid_recipient | The recipient account could not be found or is inactive. |
network_error | A network error occurred. Please retry later. |
failure | The payout provider could not complete the transfer. |
provider_error | The payout provider returned an error. Please retry later. |
daily_limit_exceeded | Daily transfer limit exceeded for this channel. |
unknown | An 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_reason | failure_message |
|---|---|
insufficient_funds | The refund could not be completed due to insufficient funds on the provider. |
refund_rejected_by_issuer | The refund was rejected by the card issuer. |
transaction_already_reversed | The original transaction has already been reversed or refunded. |
network_error | A network error occurred while processing the refund. |
provider_error | The payment provider returned an error. Please retry later. |
unknown | An 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.