Paiements
Statut pending bloqué, échec sans motif, double débit ou erreur 404 sur un identifiant valide.
Sept symptômes couvrent presque toutes les questions liées aux paiements. Repérez le vôtre et lisez la cause avant la solution. Trois d'entre eux proviennent de la même incompréhension.
Le paiement reste pending sans évolution
Un paiement Mobile Money attend une personne. Wajub a interrogé l'opérateur, qui a envoyé une demande
sur un téléphone. Tant que personne ne saisit son code PIN, aucun résultat n'est disponible.
pending n'est pas un état bloqué, mais un état d'attente.
Effectuez les vérifications suivantes dans cet ordre.
| Vérification | Méthode |
|---|---|
| Le payeur a-t-il ouvert la page ? | authorization_url reste valable jusqu'à la fin du paiement et peut être rouvert : traitez-le comme un secret |
| La demande est-elle arrivée ? | L'opérateur l'envoie, pas Wajub. La page hébergée affiche le code USSD en solution de repli |
| Le paiement est-il simplement trop récent ? | En période de pointe, les opérateurs peuvent mettre une minute à envoyer la demande |
Le paiement ne reste pas indéfiniment pending. Il expire automatiquement et passe à expired.
curl https://api.wajub.com/payments \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XAF",
"email": "[email protected]",
"expires": { "in": 30 }
}'Le champ imbriqué est expires.in et sa valeur s'exprime en minutes, de 5 à 43200, soit 30 jours.
La valeur par défaut est 1440. Écrire expires_in au premier niveau ne produit aucun effet. La
valeur est ignorée et le délai reste fixé à 24 heures.
Le paiement a échoué sans motif visible
Un paiement ne porte aujourd'hui aucun code d'échec : failure_reason est absent du paiement et
du webhook payment.failed. C'est status qui distingue les cas.
curl https://api.wajub.com/payments/trx_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
| jq '.transaction | { status, channel, amount }'status | Ce qui s'est passé |
|---|---|
failed | L'opérateur a refusé le débit |
expired | Le payeur n'a pas confirmé à temps |
cancelled | Vous avez appelé DELETE /payments/{id}, ou le payeur a cliqué sur Annuler sur la page hébergée |
Quand le refus arrive au moment du débit, le code se trouve dans la réponse à
POST /payments/{id} : un 402 dont error_code le nomme, insufficient_funds par exemple, et le
paiement revient à pending, prêt à être débité de nouveau. Pour les termes exacts de l'opérateur
après coup, ouvrez le paiement dans Konsole : sa
chronologie montre chaque tentative et la réponse brute du prestataire.
Motifs d'échec présente les codes que portent les transferts et les
remboursements.
Mon appel a expiré et j'ignore si le paiement existe
Cette situation est dangereuse, car les deux résultats sont possibles et un seul permet d'agir sans risque. Par défaut, le SDK attend 30 secondes par tentative et relance jusqu'à trois fois avec la même clé : l'erreur de connexion arrive donc au bout d'environ deux minutes, et elle n'indique pas ce que le serveur a effectué.
Ne créez pas un second paiement pour le vérifier. Renvoyez la même requête avec la même
Idempotency-Key. Wajub renvoie le paiement initial s'il existe ou le crée dans le cas contraire.
const payment = await wajub.payments.create(params, {
idempotencyKey: `ORDER-${order.id}`,
});Sans cette clé, la nouvelle tentative provoque un second débit. Idempotence explique comment construire une clé qui survit au redémarrage de votre processus.
Si la nouvelle tentative arrive pendant que la première requête est encore en cours, l'API répond
409 avec errors.idempotency_key : « A request with this Idempotency-Key is still being
processed. » Rien n'a échoué : la réponse de la première requête arrive. Les SDK serveur attendent
puis redemandent avec la même clé, après 2 puis 4 secondes (puis 8 en Node.js), et renvoient le
paiement d'origine (à partir de Node.js et C# 2.0.0, PHP 2.0.1, Ruby 1.3.0 et 1.2.1 pour les autres). En HTTP brut,
attendez quelques secondes et renvoyez la requête. Tout autre 409 est un vrai refus.
Le client a été débité deux fois
Ce problème provient presque toujours de l'une de deux causes.
La première est une nouvelle tentative sans clé d'idempotence, présentée ci-dessus. La seconde vient
de l'idée que reference évite les doublons. Ce n'est pas le cas. reference est une chaîne libre
sans règle d'unicité. Envoyer deux fois la même valeur crée deux paiements qui débitent tous les deux.
Si le problème s'est déjà produit, remboursez le doublon au lieu de l'annuler. Un paiement
succeeded ne peut plus être annulé, seulement remboursé. Consultez Remboursements.
GET /payments/{something} renvoie 404 pour mon identifiant
Il existe trois causes, de la plus fréquente à la plus rare.
Vous avez transmis votre propre référence. L'endpoint accepte uniquement l'uid Wajub. Un
paiement créé avec "reference": "ORDER-4172" n'est pas accessible depuis
/payments/ORDER-4172 et répond avec 404 Payment Not Found. Enregistrez le champ id de la
réponse de création avec votre commande, puis utilisez-le pour la recherche.
id permet de retrouver le paiement. reference vous appartient et sert à vos propres recherches.
Vous utilisez le mauvais environnement. Une clé de la sandbox ne voit aucun paiement live, et
inversement. Les deux ne partagent aucune donnée. La réponse est donc 404, pas une erreur
d'autorisation. Le préfixe test_ de l'identifiant indique l'environnement concerné.
Le paiement appartient à une autre équipe. Les identifiants ne passent pas d'une équipe à
l'autre. La réponse reste volontairement 404 plutôt que 403.
Le montant est refusé avec une réponse 422
Trois règles s'appliquent. Les utilisateurs provenant d'une autre plateforme rencontrent souvent la première.
Les montants utilisent les unités principales. 5000 signifie cinq mille francs, pas cinquante.
Aucun centime ne doit être converti. XAF et XOF ne possèdent aucune sous-unité.
Chaque devise possède des limites. Le serveur les applique et la réponse 422 indique le champ
concerné. Lisez errors.amount au lieu de deviner. Pour XAF et XOF, le minimum est faible et le
maximum assez élevé. Vous les rencontrerez généralement après avoir envoyé une valeur dans la
mauvaise unité.
Le Mobile Money possède son propre plafond, et un débit unique au-delà est refusé. Au-dessus de
500 000 XAF ou XOF, ou de l'équivalent dans une autre devise, la charge répond 422, en live comme en
sandbox. Réglez-le en tranches avec POST /payments/{id}/splits : le
paiement reste dans partial tant qu'une partie seulement est encaissée, et ne passe à succeeded
qu'après la dernière tranche ou quand vous le clôturez. Considérez partial comme « pas encore
payé », jamais comme un signal de livraison.
Tout fonctionne dans la sandbox, mais rien en live
Deux causes sont possibles.
Vos clés live peuvent exister alors que votre compte n'est pas encore activé. Les paiements live exigent la validation du KYC. Passer en live contient la checklist.
Vous utilisez peut-être aussi des numéros de test de la sandbox auprès de vrais opérateurs. Dans la
sandbox, les six derniers chiffres du numéro déterminent le résultat. En live, ils identifient
une personne. +237670000002 provoque un échec dans la sandbox, mais appartient à quelqu'un en
production.
| Suffixe de la sandbox | Résultat |
|---|---|
000000 | Réussite |
000001 | La charge répond 422 pour fonds insuffisants, le paiement reste pending |
000002 | Échec |
000003 | Délai expiré |
000004 | Annulé par le payeur |
000009 | Paiement réussi, mais tous ses remboursements échouent |
Pages associées
- Motifs d'échecTous les codes de motif et les cas où une nouvelle tentative peut réussir.
- IdempotenceLa clé qui sécurise une nouvelle tentative après l'expiration d'un délai.
- Mode testLes numéros de test, les résultats simulés et les limites de la sandbox.
- Accepter un paiement, guide completLe cycle de vie de la commande dans lequel apparaissent ces symptômes.