Cycle de vie d'un paiement
Chaque état d'un paiement, le webhook qui l'annonce, et ce qu'il faut faire dans chacun.
Un paiement ne réussit ni n'échoue au moment où vous le créez. Entre ces deux issues, il passe par des états qui reflètent ce qui se passe sur le téléphone du client et chez l'opérateur Mobile Money. Votre intégration doit se comporter différemment dans chacun.
Deux de ces états signifient « attendez », et il est important de les distinguer : l'un signifie que Wajub n'a encore contacté aucun opérateur, l'autre que le client reçoit la demande de confirmation en ce moment même. Faire une nouvelle tentative pendant le second crée un deuxième débit.
Les états
Aiguillez sur `status`, jamais sur le message
Le champ message est rédigé pour des humains et peut changer sans préavis. La valeur status fait
partie du contrat de l'API, et c'est sur elle que votre code doit se baser. Aiguillez sur elle
plutôt que sur l'événement reçu, car plusieurs événements peuvent annoncer le même état.
status | Signification | Webhook | Que faire |
|---|---|---|---|
pending | Le paiement existe. Aucun opérateur n'a encore été contacté. | payment.created | Redirigez le client, ou faites l'appel de débit. Rien à livrer. |
processing | Envoyé à l'opérateur. Le client reçoit la demande de confirmation sur son téléphone. | payment.processing | Attendez. Ne faites pas de nouvelle tentative et ne créez pas de second paiement. |
partial | Certaines échéances d'un paiement fractionné sont réglées, d'autres non. | payment.split.* | Attendez. amount_paid sur le paiement indique ce qui est réglé jusqu'ici. |
succeeded | Fonds capturés. | payment.succeeded | Livrez la commande. |
failed | La tentative n'a pas abouti. | payment.failed | Lisez failure_reason, puis décidez si une nouvelle tentative a du sens. |
cancelled | Annulé par vous avec DELETE /payments/{id}, ou par le client avec le bouton Annuler de la page hébergée. Un refus sur le téléphone n'est pas une annulation. | payment.cancelled | Proposez de lancer un nouveau paiement. |
expired | Le délai de paiement s'est écoulé avant que le client agisse. | payment.expired | Créez un nouveau paiement. L'ancien ne pourra jamais aboutir. |
Quatre de ces états mettent fin à la tentative : succeeded, failed, cancelled et expired.
Wajub horodate ce moment pour décider combien de temps la page hébergée reste accessible. succeeded
ne bouge plus jamais, sauf par les remboursements : depuis la version d'API 2026-10-11, un
paiement remboursé se lit refunded ou partially_refunded.
Les transitions possibles
Wajub ne fait évoluer un paiement que selon les chemins ci-dessous. Un statut inattendu après un autre est un bug de notre côté, pas un état que vous devez gérer.
| Depuis | Peut devenir |
|---|---|
pending | processing · partial · succeeded · failed · cancelled · expired |
processing | pending · partial · succeeded · failed · cancelled · expired |
partial | processing · succeeded · failed · cancelled |
failed · cancelled · expired | succeeded uniquement |
Deux de ces chemins surprennent. processing revient à pending quand l'opérateur refuse un débit
immédiatement, avec un 402, pour que le même paiement puisse être débité de nouveau ;
payment.attempt_failed en informe votre serveur. Et un paiement
failed, cancelled ou expired peut encore devenir succeeded quand l'opérateur confirme
l'argent en retard : livrez sur payment.succeeded, quel que soit le moment où il arrive. Il le
devient aussitôt quand votre propre prestataire a encaissé. Quand c'est un rail Wajub, ou quand une
autre tentative avait déjà encaissé le paiement, Wajub examine d'abord la confirmation tardive : soit
il crédite le paiement, et payment.succeeded suit, soit il rembourse le payeur et le paiement reste
tel quel. Rien d'autre ne fait sortir de ces trois états, donc une nouvelle tentative après eux passe
par un nouveau paiement.
Quatre points qui surprennent
processing signifie que le téléphone du client sonne
C'est l'état dans lequel votre intégration passera le plus de temps, et celui qui a le plus de chances
d'être mal géré. En Mobile Money, processing signifie que l'opérateur a envoyé une demande de
confirmation sur le téléphone du client et attend qu'il saisisse son code PIN. Cela peut prendre
quelques secondes, ou le temps qu'il faut à quelqu'un pour retrouver son téléphone.
Ne retentez jamais un paiement en cours de traitement
Créer un second paiement parce que le premier « prend trop de temps » est la façon la plus courante
de débiter un client deux fois. Un paiement en processing est toujours actif. Attendez
payment.succeeded, payment.failed ou payment.expired.
Un remboursement ne modifie le status du paiement qu'à partir de la version 2026-10-11
C'est le point qui piège le plus d'intégrations, et il dépend de votre version d'API. Depuis
2026-10-11, un paiement se lit partially_refunded dès que des remboursements réussis en couvrent
une partie, et refunded quand ils le couvrent en entier. Sur une version antérieure, il affiche
toujours succeeded, en totalité ou en partie, pour toujours. Dans les deux cas, c'est une lecture
des remboursements : le paiement lui-même ne change pas, et il n'existe pas d'événement
payment.refunded. Ce que dit le statut du paiement
Les remboursements ont à la place leur propre famille d'événements : refund.created,
refund.processing (en live uniquement), refund.succeeded, refund.failed et refund.cancelled. Pour savoir quelle part d'un paiement a été
restituée, listez ses remboursements avec GET /payments/{id}/refunds et
additionnez ceux qui ont réussi.
N'attendez pas un statut que votre version n'affiche jamais
Sur un compte épinglé avant 2026-10-11, refunded et partially_refunded n'arrivent jamais :
les libellés Refunded et Partially refunded du Dashboard sont calculés à partir des
remboursements, pour les personnes qui lisent un écran. Quelle que soit votre version, comptez les
remboursements réussis pour savoir ce qui a été restitué.
expired s'annonce par payment.expired
Un paiement expiré est annoncé par payment.expired, que sa fenêtre se soit fermée ou qu'un numéro
sandbox de délai dépassé y ait mis fin. Un délai
dépassé en sandbox s'annonçait comme payment.failed ; ce n'est plus le cas. Aiguillez tout de même
sur status : c'est à cela que sert la règle en haut de la page.
partial ne concerne que les paiements fractionnés
Un paiement peut être réglé en deux à quatre échéances, en sandbox et, sur le Mobile Money, en live
(Paiements fractionnés). Tant que certaines sont payées et d'autres non, son
statut est partial, processing pendant qu'une échéance attend le payeur, et chaque échéance rend
compte d'elle-même via payment.split.processing, payment.split.succeeded et
payment.split.failed.
Il n'existe pas d'événement payment.partial, et un abonnement ne reconnaît que des noms exacts :
payment.* ne correspond à rien. Listez les trois événements payment.split par leur nom, ou
abonnez-vous à *. Le paiement ne porte pas la liste de ses échéances : amount_paid est la
référence de ce qui a été réglé, et chaque événement payment.split.* indique quelle échéance a
bougé.
Un paiement partial reste ouvert jusqu'au paiement de sa dernière échéance : rien ne l'expire ni ne
l'annule. Pour l'arrêter avant, clôturez-le avec POST /payments/{id}/close :
il devient succeeded pour ce qui a été encaissé, que vous gardez ou remboursez (outcome).
DELETE /payments/{id} refuse un paiement dont des échéances sont déjà payées.
Expiration
Chaque paiement porte une date limite, fixée avec expires.in à la création, en minutes, et
relue dans son expires_at :
| Minimum | 5 minutes |
| Par défaut | 1 440 minutes (24 heures) |
| Maximum | 43 200 minutes (30 jours) |
Le plancher est de cinq minutes, car une demande de confirmation Mobile Money a besoin de temps pour atteindre le téléphone et être confirmée. Des fenêtres plus courtes expirent avant que le client puisse payer.
L'expiration n'est pas instantanée : elle peut suivre la date limite de quelques minutes. Un
paiement dont la date limite vient d'être dépassée peut encore afficher pending un moment, et
payment.expired arrive un peu après la date limite plutôt qu'à la seconde près.
Ce délai est voulu, ce n'est pas une erreur d'arrondi. Ne calculez pas l'expiration vous-même pour
décider qu'une commande est morte : un opérateur peut encore confirmer un paiement dans les
dernières secondes de sa fenêtre, et l'expiration ne s'applique qu'aux paiements qui n'ont jamais
été crédités. Elle ne s'applique pas non plus à un paiement réglé en tranches dès que l'une d'elles
est payée : cet argent vient du payeur, et le paiement reste ouvert pour la tranche suivante. Un
paiement partial n'expire jamais : il reste ouvert jusqu'à être soldé ou annulé.
Ce que vous recevez quand un paiement échoue
Un paiement échoué porte un failure_reason, et failure_message le dit en une phrase. La valeur
est le code du prestataire qui a refusé la dernière tentative, et Wajub route entre de nombreux
prestataires, ou payment_failed quand cette tentative a échoué sans code du prestataire. Voir
Motifs d'échec.
`failure_reason` n'est pas une énumération fixe
Comme la valeur vient de l'amont, les chaînes exactes diffèrent d'un prestataire à l'autre et
peuvent changer quand un prestataire met à jour sa propre API. Traitez failure_reason comme un
diagnostic à journaliser et à montrer à votre équipe support, pas comme une clé stable sur laquelle
bâtir votre logique métier.
Aiguillez sur status pour le déroulement du code. Utilisez failure_reason pour expliquer à un
humain ce qui s'est passé, et pour décider manuellement si une catégorie d'échec mérite une nouvelle
tentative.
Annuler un paiement
Un paiement pending, processing ou partial peut être annulé depuis votre serveur, quand le
client abandonne la commande de votre côté.
https://api.wajub.com/payments/{id}Le paiement passe en cancelled, payment.cancelled est envoyé, et sa page n'accepte plus de
paiement. Tout autre statut répond 400 This payment cannot be cancelled (already completed or expired). Une confirmation que l'opérateur envoie après l'annulation fait quand même passer le
paiement en succeeded, comme décrit plus haut.
Lister les paiements par état
https://api.wajub.com/payments| Paramètre de requête | Valeurs acceptées |
|---|---|
status | pending, processing, partial, succeeded, failed, cancelled, expired |
reference | Exactement la reference envoyée à la création. Elle n'est pas unique : chaque paiement qui la porte est renvoyé |
search | Texte libre sur l'id du paiement, votre reference et la description |
date_from, date_to | Dates, comparées à la date de création |
amount_min, amount_max | Montants dans l'unité principale de la devise |
per_page | De 1 à 100, 25 par défaut |
cursor | Passe à la pagination par curseur |
status accepte aussi refunded et partially_refunded. À partir de la version 2026-10-11 de
l'API, ils listent les paiements remboursés en totalité ou en partie, que succeeded laisse alors
de côté. Sur une version antérieure, ils renvoient une liste vide.
Ce que dit le statut du paiement
Pour voir les paiements qui attendent encore un client, filtrez sur pending :
curl -G https://api.wajub.com/payments \
-H "Authorization: $WAJUB_API_KEY" \
-d status=pending \
-d per_page=50Le reçu d'un paiement réussi
Une fois le paiement réussi, il porte receipt_url : le reçu du payeur, une page hébergée sur votre
domaine de factures, dans la langue du payeur. Le lien est signé, et c'est la signature de son
paramètre s qui l'ouvre : transmettez l'URL telle quelle.
Le même document existe sous deux autres formes. Ajoutez /pdf au chemin pour un PDF, ou /data
pour le reçu en JSON, en gardant la chaîne de requête.
Pages associées
- WebhooksRecevoir ces événements de façon fiable : signatures, nouvelles tentatives, doublons.
- Mode testAmenez un paiement dans chaque état à la demande avec des numéros de test.
- RemboursementsLe cycle de vie d'un remboursement et les événements qu'il émet.
- Récupérer un paiementFaites le rapprochement côté serveur quand vous avez besoin de certitude.