Démarrer
Mode test
Chaque compte MivaaPay dispose d'un bac à sable complet. Vous y créez des paiements, recevez de vrais webhooks signés et exercez toute votre intégration — sans qu'un centime ne bouge.
Deux jeux de clés
Le mode n'est pas un paramètre de la requête : il est porté par la clé. Votre tableau de bord vous en donne deux, reconnaissables au préfixe :
| Clé | Effet |
|---|---|
sk_test_… |
Bac à sable. Aucun opérateur n'est contacté, aucun argent ne circule. |
sk_… |
Production. Le client est réellement débité. |
Basculer de l'un à l'autre ne demande donc aucun changement de code : une variable d'environnement suffit. C'est le point important — ce que vous avez validé en test est exactement ce qui partira en production.
curl -X POST https://api.mivaapay.com/api/v1/payments \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{ "amount": 5000, "operator": "mtn", "phone_number": "22997000000" }'
// MIVAAPAY_SECRET_KEY=sk_test_… en recette, sk_… en production.
// Le code, lui, est identique dans les deux environnements.
$payment = Http::withToken(env('MIVAAPAY_SECRET_KEY'))
->post('https://api.mivaapay.com/api/v1/payments', $payload)
->json('data');
// MIVAAPAY_SECRET_KEY=sk_test_… en recette, sk_… en production.
const res = await fetch('https://api.mivaapay.com/api/v1/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MIVAAPAY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
Les clés de test fonctionnent même tant que votre KYC est en cours. Vous pouvez donc intégrer et valider pendant l'instruction du dossier, et n'attendre l'activation que pour le passage en production.
Déclencher un scénario
En Mobile Money, l'issue est décidée par les quatre derniers chiffres du numéro payeur. C'est déterministe : le même numéro donne toujours le même résultat, ce qui rend vos tests automatisés reproductibles.
| Numéro se terminant par | Ce qui se passe | Statut final |
|---|---|---|
0000 |
Le client valide aussitôt. | succeeded |
0001 |
L'opérateur refuse dès l'initiation : POST /payments répond 402 payment_failed. |
failed |
0002 |
Le client ne valide jamais. Utile pour tester vos délais d'attente. | pending indéfiniment |
0003 |
Le client refuse sur son téléphone après l'envoi de la demande. | failed |
| tout autre numéro | Cas nominal : en attente, puis validé au bout de 10 secondes. | succeeded |
22997000000 # succès immédiat
22997000001 # refus de l'opérateur
22997000002 # jamais validé
22997000003 # refus du client
22991234567 # succès après 10 secondes (le cas le plus réaliste)
pending
Même pour le scénario à succès immédiat. C'est volontaire : en production, l'état n'est
jamais connu au moment de la réponse. Si votre code livrait la commande sur la réponse à
POST /payments, le mode test ne vous laisserait pas croire que ça marche.
L'issue arrive par webhook, ou par GET /payments/{reference}.
Paiement par carte
Un paiement carte en mode test renvoie une payment_url comme en production, mais
elle mène à une page hébergée par MivaaPay où vous choisissez l'issue : deux boutons,
« paiement réussi » ou « refus ». Le client est ensuite redirigé vers votre
return_url, avec reference et status en paramètres.
Cela permet d'exercer le parcours de redirection en entier, y compris le cas que l'on oublie le plus souvent : fermer l'onglet sans payer.
Remboursements de test
En production, un remboursement est une demande qui passe par un contrôle back-office. En
mode test, il est résolu immédiatement : POST /payments/{reference}/refund
rend un remboursement déjà succeeded, et vous recevez dans la foulée les webhooks
refund.succeeded puis payment.refunded. Vous testez donc la séquence
complète sans attendre une validation humaine.
Webhooks
Les webhooks du bac à sable partent sur le même endpoint que les vrais, avec la même
signature HMAC — c'est ce qui vous permet de valider votre vérification de signature avant la
production. Ils se distinguent par le champ livemode :
{
"id": "5c8b8a1e-1f0e-4c0a-9a3e-6f8c2b1d4e77",
"event": "payment.succeeded",
"livemode": false,
"created_at": "2026-08-05T10:25:02+00:00",
"data": { "reference": "…", "status": "succeeded", "amount": 5000 }
}
Chaque environnement a son propre endpoint et son propre secret.
PUT /webhooks/endpoint
configure celui du mode de la clé employée : avec une clé de test, vous ne pouvez ni
détourner ni faire taire vos notifications de production. Vous avez donc deux URL à renseigner,
une par clé, et deux secrets à stocker.
Si vous préférez malgré tout un endpoint unique, testez livemode à la réception —
c'est ce qui évite qu'une commande fictive parte en préparation.
Ce qui est cloisonné
Les deux environnements ne se voient pas, et les paiements de test ne touchent à rien de réel :
| Garantie | Détail |
|---|---|
| Listes séparées | Une clé de test ne voit que des ressources de test, une clé live que des ressources live. Une référence de l'autre environnement renvoie not_found. |
| Aucun encaissement | Un paiement de test ne crédite jamais votre solde réel. |
| Aucun reversement | Les paiements de test ne comptent dans aucun seuil et ne peuvent pas être reversés. |
| Idempotence par environnement | Un external_id utilisé en test reste disponible en live. Vous pouvez rejouer vos commandes de recette en production. |
Solde en mode test
GET /balance avec une clé de test ne renvoie pas votre vrai solde —
ce serait exposer une donnée financière réelle dans un environnement de test. Il renvoie la
somme de vos encaissements de test, ce qui vous laisse un chiffre cohérent à voir bouger.
Ce qui n'existe pas en test
POST /payment-links est refusé avec une clé de test. Un lien mène à une page
que le client ouvre sans clé API : rien, à son ouverture, ne permettrait de savoir
qu'elle doit simuler plutôt qu'encaisser. Testez vos encaissements avec
POST /payments, puis créez vos liens avec votre clé live.
Passer en production
Avant de remplacer sk_test_… par votre clé live, vérifiez que :
- votre compte marchand est actif (KYC validé) ;
- votre endpoint webhook de production est configuré et vérifie la signature ;
- votre code ne livre jamais sur la réponse à
POST /payments, mais sursucceeded; - votre traitement de webhook est idempotent (voir Rejeux et idempotence) ;
- vous envoyez un
external_idsur chaque paiement, pour ne jamais débiter deux fois ; - vous avez testé les quatre scénarios, pas seulement le cas passant.
Faites-le avec un petit montant, sur un numéro qui vous appartient. Le bac à sable simule nos règles, pas celles de l'opérateur : plafonds du compte du client, réseau, délais de validation réels ne s'observent qu'en production.