Tester en sandbox
En sandbox, il n’y a pas de vrai téléphone ni de vrai opérateur : MafPay simule Wave et Orange Money, avec exactement le même parcours qu’en production.
Votre serveur ──1. initiate──► MafPay (statut PROCESSING, lien + QR code) Le client ──2. ouvre le lien / scanne le QR──► page de paiement de test MafPay Le client ──3. clique sur « Payer »──► MafPay (statut SUCCESS) MafPay ──4. notification signée──► votre serveur (transaction.success)Avant de commencer : les identifiants de test
Configurez le moyen de paiement dans l’onglet Sandbox avec les valeurs de test (liste complète) : comme l’opérateur, MafPay refuse l’initiation si l’identifiant est incorrect (« Identifiants sandbox invalides »).
Méthode principale : la page de paiement de test
Après l’initiation, ouvrez le lien de deep_links (ou scannez le qr_code avec un téléphone) : vous arrivez sur une page MafPay qui affiche le marchand, le montant déjà chargé et le moyen de paiement.
| Vous cliquez sur | Résultat | Notification reçue par votre serveur |
|---|---|---|
| Payer | le paiement passe à SUCCESS |
transaction.success |
| Annuler | le paiement passe à CANCELLED |
transaction.cancelled |
| (rien, le délai passe) | à l’ouverture de la page, le paiement passe à EXPIRED |
transaction.expired |
Une fois le paiement terminé, la page affiche le résultat et un bouton « Retourner sur le site » qui renvoie le client vers votre success_url (ou error_url en cas d’échec ou d’annulation), comme le ferait l’opérateur.
- Recharger la page ou cliquer deux fois ne change rien : un paiement terminé le reste, et une seule notification est envoyée.
- Cette page n’existe qu’en sandbox : un paiement live n’est jamais joignable par ce lien.
- Rien de sensible n’y est affiché (ni vos clés, ni vos
metadata).
Méthode avancée : jouer l’opérateur avec curl
Pour automatiser vos tests (scripts, intégration continue) ou simuler un cas particulier, vous pouvez envoyer vous-même à MafPay le message que l’opérateur aurait envoyé. C’est ce que fait, en coulisses, le bouton Payer.
Comment simuler
/api/v1/{endpoint_key}/webhooks/{provider}/{provider}vautwaveouorange-money.- Envoyez vos clés sandbox (
X-Api-Key/X-Api-Secret) : c’est ce qui indique à MafPay que vous simulez. - Vous devez indiquer la
referencedu paiement (MAFPAY-XXXXXXXX) dans un champ précis selon l’opérateur, et le montant exact du paiement.
Wave
La référence se place dans data.client_reference.
Paiement réussi
curl -X POST "$MAFPAY_URL/api/v1/$ENDPOINT_KEY/webhooks/wave/" \ -H "X-Api-Key: $MAFPAY_PK" -H "X-Api-Secret: $MAFPAY_SK" \ -H "Content-Type: application/json" \ -d '{ "type": "checkout.session.completed", "data": { "id": "cos-sandbox-1", "client_reference": "MAFPAY-W7VOB6J6", "payment_status": "succeeded", "amount": "5000", "currency": "XOF" } }'const response = await fetch( `${process.env.MAFPAY_URL}/api/v1/${process.env.MAFPAY_ENDPOINT_KEY}/webhooks/wave/`, { method: "POST", headers: { "X-Api-Key": process.env.MAFPAY_PK, "X-Api-Secret": process.env.MAFPAY_SK, "Content-Type": "application/json", }, body: JSON.stringify({ type: "checkout.session.completed", data: { id: "cos-sandbox-1", client_reference: "MAFPAY-W7VOB6J6", payment_status: "succeeded", amount: "5000", currency: "XOF" }, }), });console.log(await response.json());import osimport requests
response = requests.post( f"{os.environ['MAFPAY_URL']}/api/v1/{os.environ['MAFPAY_ENDPOINT_KEY']}/webhooks/wave/", headers={"X-Api-Key": os.environ["MAFPAY_PK"], "X-Api-Secret": os.environ["MAFPAY_SK"]}, json={ "type": "checkout.session.completed", "data": {"id": "cos-sandbox-1", "client_reference": "MAFPAY-W7VOB6J6", "payment_status": "succeeded", "amount": "5000", "currency": "XOF"}, }, timeout=15,)print(response.json())Réponse :
{ "reference": "MAFPAY-W7VOB6J6", "previous_status": "PROCESSING", "new_status": "SUCCESS" }Autres scénarios Wave
| Vous voulez simuler | type |
Autre champ à changer |
|---|---|---|
| Succès | checkout.session.completed |
data.payment_status: "succeeded" |
| Échec | checkout.session.completed |
data.payment_status: "failed" |
| Annulation | checkout.session.completed |
data.payment_status: "cancelled" |
| Échec avec motif | checkout.session.payment_failed |
ajouter data.last_payment_error: { "code": "insufficient_funds", "message": "Solde insuffisant" } |
| Expiration | checkout.session.expired |
— |
Orange Money
La référence se place dans metadata.mafpay_reference, et le montant dans amount.value.
curl -X POST "$MAFPAY_URL/api/v1/$ENDPOINT_KEY/webhooks/orange-money/" \ -H "X-Api-Key: $MAFPAY_PK" -H "X-Api-Secret: $MAFPAY_SK" \ -H "Content-Type: application/json" \ -d '{ "amount": { "value": 2500, "unit": "XOF" }, "transactionId": "MP260924.1029.C58502", "status": "SUCCESS", "metadata": { "mafpay_reference": "MAFPAY-0X0GXXRR" } }'const response = await fetch( `${process.env.MAFPAY_URL}/api/v1/${process.env.MAFPAY_ENDPOINT_KEY}/webhooks/orange-money/`, { method: "POST", headers: { "X-Api-Key": process.env.MAFPAY_PK, "X-Api-Secret": process.env.MAFPAY_SK, "Content-Type": "application/json", }, body: JSON.stringify({ amount: { value: 2500, unit: "XOF" }, transactionId: "MP260924.1029.C58502", status: "SUCCESS", metadata: { mafpay_reference: "MAFPAY-0X0GXXRR" }, }), });console.log(await response.json());import osimport requests
response = requests.post( f"{os.environ['MAFPAY_URL']}/api/v1/{os.environ['MAFPAY_ENDPOINT_KEY']}/webhooks/orange-money/", headers={"X-Api-Key": os.environ["MAFPAY_PK"], "X-Api-Secret": os.environ["MAFPAY_SK"]}, json={ "amount": {"value": 2500, "unit": "XOF"}, "transactionId": "MP260924.1029.C58502", "status": "SUCCESS", "metadata": {"mafpay_reference": "MAFPAY-0X0GXXRR"}, }, timeout=15,)print(response.json())| Vous voulez simuler | status |
|---|---|
| Succès | SUCCESS |
| Échec | FAILED |
| Annulation | CANCELLED |
| Expiration | EXPIRED |
Un autre statut ne change rien.
Ce que MafPay contrôle
Rien n’est modifié tant que tous ces contrôles ne sont pas passés :
| Problème | Code | Message / effet |
|---|---|---|
| Clés fausses | 401 |
« Clés API invalides. » |
| Clés live utilisées pour simuler | 403 |
refusé : on ne simule qu’en sandbox |
| Corps illisible ou champ manquant | 400 |
le message indique quoi corriger |
| Référence inconnue (ou d’une autre application) | 404 |
« Transaction introuvable. » |
| Montant différent de celui du paiement | 400 |
« Le montant annoncé ne correspond pas à celui de la transaction. » |
Les doublons et les contradictions sont ignorés
Comme en vraie vie, un webhook en double, tardif, ou qui contredit un statut définitif (un « échec » après un « succès ») ne change rien :
{ "reference": "MAFPAY-0X0GXXRR", "previous_status": "FAILED", "new_status": "FAILED", "message": "Statut inchangé (transition non applicable)." }Aucune notification n’est renvoyée à votre serveur dans ce cas.
Scénarios de test à jouer avant la production
- Succès (bouton Payer) : la commande passe « payée », le stock est décrémenté, l’email de confirmation part.
- Échec : la commande reste « en attente de paiement », le client peut réessayer.
- Annulation (bouton Annuler) et expiration : idem, sans livraison.
- Notification en double : envoyez deux fois le même webhook : votre serveur ne doit livrer qu’une fois.
- Signature invalide : appelez votre propre
webhook_urlavec une fausse signature : votre serveur doit répondre401. - Montant décimal avec Orange Money : vérifiez que vous gérez l’erreur
400.