Cycle de vie d'un paiement
Chaque état d'un paiement, le webhook qui l'annonce, et ce qu'il faut faire dans chacun.
Chaque état qu'un paiement peut prendre, le webhook qui l'annonce, et ce que fait votre intégration
dans chacun. Deux d'entre eux signifient « attendez » : pending, avant tout contact avec un
opérateur, et processing, pendant que le client reçoit la demande de confirmation, où une nouvelle
tentative 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.
succeeded ne bouge plus jamais, sauf par les remboursements : un paiement remboursé se lit
refunded ou partially_refunded.
Un paiement préparé, avant son activation
Un paiement créé avec deferred: true, pour des
champs de paiement affichés avant que la commande existe,
est seulement préparé jusqu'à ce que votre serveur l'active avec POST /payments/{id}/activate.
D'ici là, il n'envoie aucun webhook payment.* et n'apparaît ni dans GET /payments ni dans le
Dashboard. Lu par son id, il est pending, puis expired ou cancelled, et il ne peut pas être
débité. L'activation envoie payment.created, puis le paiement suit les états ci-dessus, avec le
même id et la même expiration. Paiements préparés résume les
différences.
Les transitions possibles
Wajub ne fait évoluer un paiement que selon les chemins ci-dessous.
| Depuis | Peut devenir |
|---|---|
pending | processing · partial · succeeded · failed · cancelled · expired |
processing | pending · partial · succeeded · failed · cancelled · expired |
partial | processing · succeeded · failed |
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. Quand une
confirmation tardive ne peut pas être créditée, par exemple parce qu'une autre tentative avait déjà
encaissé le paiement, le payeur est remboursé 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.
Trois 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'à travers ses remboursements
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. 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 et refund.failed, qui arrive aussi,
avec cancelled_by_wajub, pour un remboursement que Wajub a arrêté avant de l'envoyer. 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.
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.
payment.expired peut arriver un peu après expires_at, et le paiement peut encore afficher
pending entre les deux. 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. Un
paiement dont le débit est encore en cours quand expires_at passe, le payeur le validant sur son
téléphone par exemple, n'expire pas à ce moment : il attend ce débit. Payé, il est succeeded ;
refusé sur-le-champ, il revient à pending et peut expirer ; refusé plus tard, il est failed, sauf
le code du payeur refusé plus tard par l'opérateur, qui le ramène à pending, où il peut aussi
expirer. Un paiement dont une échéance est déjà payée n'expire jamais : il reste ouvert jusqu'à être
soldé ou clôturé.
Ce que vous recevez quand un paiement échoue
Un paiement échoué porte un failure_reason, et failure_message le dit en une phrase. Voir
Motifs d'échec.
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 que personne n'est en train de payer 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. Un paiement qui ne peut pas être annulé maintenant répond 409, avec sa raison dans
error_code :
error_code | Quand | Que faire |
|---|---|---|
payment_in_progress | Un débit est en cours : le payeur le valide sur son téléphone, ou paie sur la page du prestataire | Attendez son issue, par webhook ou GET /payments/{id}. Un paiement revenu à pending peut être annulé |
payment_partly_paid | Des échéances en ont été payées | Clôturez-le avec POST /payments/{id}/close, en gardant ou en remboursant ce qui a été encaissé |
Tout autre paiement, déjà payé, échoué, annulé ou expiré, 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 : ils listent les paiements remboursés en
totalité ou en partie, que succeeded laisse alors de côté.
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é : 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.