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, ou lisez le détail des échéances sur le paiement. |
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é, soit par le client sur son téléphone, soit par vous. | payment.cancelled | Proposez de lancer un nouveau paiement. |
expired | Le délai de paiement s'est écoulé avant que le client agisse. | payment.expired ou payment.failed | Créez un nouveau paiement. L'ancien ne pourra jamais aboutir. |
Quatre de ces états sont terminaux : succeeded, failed, cancelled et expired. Une fois qu'un
paiement en atteint un, plus rien ne le fait bouger, et Wajub horodate ce moment pour décider combien
de temps sa page hébergée reste accessible.
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 · cancelled · expired · failed |
processing | succeeded · partial · failed · cancelled |
partial | succeeded · processing · failed · cancelled |
Remarquez ce qui manque : rien ne revient à pending, et un paiement failed, cancelled ou
expired ne revient jamais à la vie. Quand il vous faut une autre tentative, vous créez 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 jamais le status du paiement
C'est le point qui piège le plus d'intégrations. Un paiement remboursé, en totalité ou en partie,
affiche toujours succeeded sur GET /payments/{id}, et pour toujours. L'API ne réécrit pas le
statut, et il n'existe pas d'événement payment.refunded.
Les remboursements ont à la place leur propre famille d'événements : refund.created,
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.
`refunded` est un libellé du Dashboard, pas un statut de l'API
Le Dashboard affiche Refunded et Partially refunded sur un paiement. Ces libellés sont
calculés à partir des remboursements rattachés, pour les personnes qui lisent un écran. N'écrivez
pas de code qui attend un status de paiement refunded ; il n'arrivera jamais.
expired peut vous parvenir sous deux noms
Un paiement expiré est annoncé par payment.expired quand la tâche de nettoyage qui ferme les
sessions périmées le prend en charge. Un numéro sandbox de délai dépassé aboutit au même statut
expired par un autre chemin et s'annonce comme payment.failed.
Les deux signifient la même chose et les deux sont terminaux. C'est précisément pour cela que la règle
en haut de la page est d'aiguiller sur status plutôt que sur le nom de l'événement.
partial ne concerne que les paiements fractionnés
Un paiement peut être réglé en deux à quatre échéances. Tant que certaines sont payées et d'autres
non, son statut est partial, 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 : un écouteur abonné à la seule famille payment.* ne
voit rien entre processing et le résultat final. Abonnez-vous aussi à payment.split.*, et
considérez le détail des échéances sur le paiement comme la référence de ce qui a été réglé.
Expiration
Chaque paiement porte une date limite, fixée avec expires.in à la création, en minutes :
| 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. Une tâche planifiée passe sur les sessions périmées toutes les
cinq minutes : un paiement dont la date limite vient d'être dépassée peut encore afficher pending un
moment, et payment.expired arrive au passage de cette tâche 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 la tâche de nettoyage ne touche que les paiements qui n'ont jamais été crédités.
Ce que vous recevez quand un paiement échoue
Un paiement échoué porte un failure_reason. Sa valeur vient du prestataire qui a traité la
tentative, et Wajub route entre de nombreux prestataires.
`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.
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.