Aller au contenu

Checkout Mobile Money

Ce qui se passe entre l'invite et la confirmation, et comment adapter votre intégration.

Une machine décide d'un paiement par carte en environ deux secondes. Un paiement Mobile Money dépend d'une personne qui tient un téléphone. Elle doit le déverrouiller, lire une invite et saisir un code PIN. Toute la particularité de cette intégration vient de ce constat.

Ce guide traite de l'attente, des échecs et des nouvelles tentatives. Pour les appels d'API eux-mêmes, le Démarrage rapide Paiements est plus court et les explique correctement.

Déroulement réel

Quatre acteurs interviennent entre votre requête et l'argent. Seul le premier vous appartient.

Les trois étapes intermédiaires peuvent prendre de dix secondes à plusieurs minutes. Vous n'en contrôlez aucune. Votre interface doit le présenter clairement au lieu de rester bloquée sur un indicateur de chargement.

Le numéro de téléphone détermine l'opérateur

Vous n'indiquez pas à Wajub quel opérateur doit effectuer le débit. Vous envoyez le numéro du client au format E.164. En production, Wajub identifie l'opérateur grâce à la base de données de libphonenumber. La portabilité et les nouvelles plages de numéros sont donc prises en charge pour vous.

curl https://api.wajub.com/payments \
  -H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
  -H "Idempotency-Key: order-9041" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "currency": "XAF",
    "customer": {
      "name": "Amina Nkosi",
      "phone": "+237670000000",
      "email": "amina@example.com"
    },
    "description": "Order #9041",
    "reference": "order-9041",
    "callback": "https://shop.example.com/orders/9041/return"
  }'

La réponse contient authorization_url. Redirigez le client vers cette URL. Wajub affiche le choix de l'opérateur, les instructions de l'invite et l'écran de résultat en anglais ou en français selon votre paramètre régional.

Les montants possèdent un plafond plus bas que prévu

Mobile Money limite un débit à 500 000 XAF, et au même montant en XOF. L'API accepte toutefois les paiements jusqu'à 2 000 000 XAF. Ces deux plafonds diffèrent. La page hébergée gère cet écart en encaissant un paiement important en plusieurs tranches successives au lieu de le refuser.

Pendant ce processus, le paiement possède le statut partial, que votre code recevra réellement.

MontantExpérience du client
Jusqu'à 500 000 XAFUne invite, un code PIN
Au-dessus de 500 000 XAFPlusieurs invites successives, avec le statut partial entre les tranches
Au-dessus de 2 000 000 XAFErreur 422 à la création, avec le plafond dans errors.amount

Considérez partial comme un paiement toujours en cours. Ce statut ne correspond ni à une réussite ni à un échec. Seul succeeded doit déclencher la livraison d'une commande.

Votre page pendant la confirmation

Le client quitte votre site le temps de trouver son téléphone. À son retour, le paiement possède très souvent encore le statut processing.

Ne présentez pas ce statut comme une erreur et n'interrogez pas l'API en boucle serrée. Effectuez une seule requête au chargement de la page, affichez honnêtement l'état et laissez le webhook gérer la suite. La route de retour est détaillée dans Accepter un paiement, guide complet. Elle doit gérer les trois cas suivants.

Statut au retourMessage à afficher
succeededConfirmation et prochaine étape
pending, processing, partialL'invite a été envoyée, la confirmation fonctionne encore et vous enverrez un e-mail
failed, expired, cancelledAucun débit n'a été effectué, avec un bouton pour réessayer

La page d'attente est souvent mal conçue. Elle doit expliquer la réalité : l'invite a été envoyée, sa validation sur le téléphone fonctionne encore et la commande se mettra à jour automatiquement. Envoyez ensuite la confirmation par e-mail à la réception du webhook afin que personne ne doive rester sur cette page.

Les causes d'échec et le message à afficher

Un échec provient généralement d'une personne ou du réseau, pas d'un bug. Le paiement contient une valeur failure_reason que vous pouvez enregistrer. Cette raison désigne un problème sur lequel une personne peut agir.

Problème rencontréMessage à afficher au client
Solde insuffisant dans le portefeuilleRechargez votre portefeuille et réessayez, la commande est toujours disponible
Invite refuséeAucun débit n'a été effectué, réessayez lorsque vous êtes prêt
Délai de l'invite dépasséL'opérateur n'a pas reçu de réponse à temps, réessayez
Numéro incorrect ou non pris en chargeVérifiez le numéro au format international complet
Opérateur indisponibleProposez un autre opérateur au lieu d'une nouvelle tentative avec le même

`failure_reason` est un diagnostic, pas une énumération

Ses valeurs proviennent du prestataire qui a traité la tentative. De nouvelles valeurs peuvent donc apparaître sans préavis. Basez les branches de votre code sur status. Utilisez failure_reason dans les logs et pour choisir une phrase compréhensible dans une table qui prévoit une valeur par défaut. Consultez Cycle de vie d'un paiement pour en savoir plus.

Une nouvelle tentative crée un nouveau paiement

Vous ne pouvez pas renvoyer une invite pour un paiement existant. Un paiement échoué ou expiré est terminé. Réessayer signifie en créer un autre pour la même commande.

Cette règle entraîne une conséquence importante. Le nouveau paiement exige une nouvelle clé d'idempotence. La réutilisation de l'ancienne clé renvoie l'ancien paiement échoué au lieu d'ouvrir une nouvelle tentative.

curl https://api.wajub.com/payments \
  -H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
  -H "Idempotency-Key: order-9041-attempt-2" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "currency": "XAF",
    "customer": { "phone": "+237670000000" },
    "reference": "order-9041",
    "callback": "https://shop.example.com/orders/9041/return"
  }'

La commande pointe désormais vers le paiement le plus récent. Conservez les anciens identifiants quelque part si vous souhaitez disposer d'un historique complet. Wajub ne relie pas les différentes tentatives d'une même commande.

Prise en charge des frais

Par défaut, les frais sont déduits du montant que vous recevez. Définissez bearer sur customer pour les ajouter au montant. La page hébergée affiche alors clairement le montant, les frais et le total réellement débité.

Facturer les frais au client
{
"amount": 15000,
"currency": "XAF",
"customer": {
"phone": "+237670000000"
},
"bearer": "customer"
}

Le client confirme le total sur son téléphone. Vous devez donc faire ce choix avant l'envoi de l'invite. Il ne peut pas être modifié ensuite.

Tester chaque cas sans opérateur

Dans la sandbox, les six derniers chiffres du numéro déterminent le résultat. Le préfixe sélectionne le pays et l'opérateur, tandis que le suffixe détermine le résultat. Tous les codes PIN sont acceptés.

SuffixeRésultatWebhook reçu
000000Le client confirmepayment.succeeded
000001Solde insuffisant dans le portefeuillepayment.failed
000002L'opérateur refusepayment.failed
000003L'opérateur ne répond paspayment.failed
000004Le client refuse l'invitepayment.failed
000009Le paiement réussit, mais tous les remboursements échouentpayment.succeeded

Pour MTN au Cameroun, le préfixe +23767 suivi de 000001 donne +237670000001. Ce numéro reproduit systématiquement un échec pour fonds insuffisants. Tous les préfixes de pays et d'opérateurs figurent dans Scénarios de test.

Retenez le numéro qui se termine par 000009. C'est le seul moyen de tester la gestion d'un échec de remboursement, autrement impossible à déclencher volontairement.

Observez le routage pendant son exécution

Ouvrez Konsole › Event Stream avant de déclencher un paiement de test. La sélection du prestataire, la réponse de l'opérateur et le résultat final apparaissent au fur et à mesure. Une fois le paiement terminé, Routing Log présente les mêmes informations.

Que pensez-vous de ce contenu ?