Aller au contenu

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.

statusSignificationWebhookQue faire
pendingLe paiement existe. Aucun opérateur n'a encore été contacté.payment.createdRedirigez le client, ou faites l'appel de débit. Rien à livrer.
processingEnvoyé à l'opérateur. Le client reçoit la demande de confirmation sur son téléphone.payment.processingAttendez. Ne faites pas de nouvelle tentative et ne créez pas de second paiement.
partialCertaines é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.
succeededFonds capturés.payment.succeededLivrez la commande.
failedLa tentative n'a pas abouti.payment.failedLisez failure_reason, puis décidez si une nouvelle tentative a du sens.
cancelledAnnulé 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.cancelledProposez de lancer un nouveau paiement.
expiredLe délai de paiement s'est écoulé avant que le client agisse.payment.expiredCré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.

DepuisPeut devenir
pendingprocessing · partial · succeeded · failed · cancelled · expired
processingpending · partial · succeeded · failed · cancelled · expired
partialprocessing · succeeded · failed
failed · cancelled · expiredsucceeded 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.

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 :

Minimum5 minutes
Par défaut1 440 minutes (24 heures)
Maximum43 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é.

DELETEhttps://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_codeQuandQue faire
payment_in_progressUn débit est en cours : le payeur le valide sur son téléphone, ou paie sur la page du prestataireAttendez son issue, par webhook ou GET /payments/{id}. Un paiement revenu à pending peut être annulé
payment_partly_paidDes échéances en ont été payéesClô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

GEThttps://api.wajub.com/payments
Paramètre de requêteValeurs acceptées
statuspending, processing, partial, succeeded, failed, cancelled, expired
referenceExactement la reference envoyée à la création. Elle n'est pas unique : chaque paiement qui la porte est renvoyé
searchTexte libre sur l'id du paiement, votre reference et la description
date_from, date_toDates, comparées à la date de création
amount_min, amount_maxMontants dans l'unité principale de la devise
per_pageDe 1 à 100, 25 par défaut
cursorPasse à 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=50

Le 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.

Que pensez-vous de ce contenu ?