Guide technique
Intégrer le paiement mobile money à un site e-commerce en RDC
Un paiement mobile money n'est pas un paiement par carte. Il est asynchrone, dépend d'un réseau télécom et peut rester « en attente » pendant que le client range son téléphone. Voici comment construire un tunnel qui ne perd ni commande ni argent.
Sur un site e-commerce congolais, le paiement arrive souvent après une conversation WhatsApp. Le client a choisi son produit, il connaît le prix, et il doit maintenant confirmer un débit sur son téléphone. Si votre site affiche « paiement échoué » alors que le client a payé, vous perdez la commande et sa confiance.
La différence avec un paiement par carte tient en un mot : asynchrone. Le paiement mobile money se joue entre votre serveur, l'opérateur et le téléphone du client. Votre code doit assumer que la réponse finale arrivera plus tard, parfois jamais, et que le réseau peut se tromper.
Les cinq briques d'une intégration fiable
- La création du paiement — votre serveur appelle l'API avec un montant, une devise, un opérateur et votre référence de commande.
- La confirmation client — le client est redirigé vers une page de paiement ou reçoit une sollicitation USSD sur son téléphone.
- Le webhook — l'opérateur ou l'agrégateur notifie votre serveur du statut final, avec une signature.
- La vérification serveur — avant d'expédier, vous vérifiez le statut auprès de l'API, même si le webhook semble clair.
- La réconciliation — un traitement quotidien compare vos commandes, vos paiements et vos règlements.
Le flux, pas à pas
1. Créer un paiement côté serveur
Ne créez jamais un paiement depuis le navigateur avec votre clé secrète. Le montant doit être calculé côté serveur à partir du panier, jamais envoyé par le client tel quel.
app.post("/api/checkout", async (req, res) => {
const cart = await getCart(req.session.cartId);
const total = cart.lines.reduce((sum, l) => sum + l.price * l.qty, 0);
const payment = await alma.payments.create({
amount: total, // entier, en CDF
currency: "CDF",
operator: req.body.operator, // mpesa | orange | airtel | afrimoney
reference: `CMD-${cart.id}`, // votre référence, unique
customer: { phone: req.body.phone },
return_url: "https://boutique.cd/commande/retour"
});
await savePaymentIntent(cart.id, payment.id, "pending");
res.json({ checkout_url: payment.checkout_url });
});
Trois règles : le montant est un entier, la référence est unique et stable, et le statut initial est « en attente » — jamais « payé ».
2. Ne pas confondre retour client et confirmation
Après le paiement, le client revient sur votre site. Ce retour prouve seulement qu'il a terminé le parcours. Il ne prouve pas que l'argent est arrivé. Affichez une page « nous vérifions votre paiement » et laissez le webhook faire le travail.
3. Recevoir et vérifier le webhook
Un webhook est une requête HTTP envoyée par le prestataire. Elle peut être rejouée, retardée ou usurpée. Votre endpoint doit donc vérifier la signature, traiter l'événement de façon idempotente et répondre rapidement.
app.post("/webhooks/alma", alma.webhooks.verify(), async (req, res) => {
const event = req.event;
// 1. Retrouver la commande par notre référence
const order = await findOrderByReference(event.data.reference);
if (!order) return res.sendStatus(202);
// 2. Idempotence : ignorer un événement déjà traité
if (await alreadyProcessed(event.id)) return res.sendStatus(200);
// 3. Vérifier le statut auprès de l'API, pas seulement dans l'événement
const payment = await alma.payments.verify(event.data.payment_id);
if (payment.status !== "succeeded") return res.sendStatus(200);
// 4. Marquer payé, puis expédier
await markPaid(order.id, payment.id, payment.amount);
await enqueueFulfillment(order.id);
await markProcessed(event.id);
res.sendStatus(200);
});
Le webhook doit répondre en moins de quelques secondes. Si votre traitement est long (facture, e-mail, ERP), mettez-le en file d'attente et répondez immédiatement.
Gérer le statut « en attente »
C'est le cas que les intégrations ratent le plus souvent. Le client a confirmé, mais l'opérateur n'a pas encore répondu. Votre site ne doit ni annuler la commande, ni la marquer payée.
| Statut | Signification | Action côté boutique |
|---|---|---|
| pending | Paiement créé, confirmation client en cours | Afficher « en attente », ne pas expédier, relancer la vérification |
| processing | L'opérateur traite l'opération | Attendre, informer le client, ne pas recréer un paiement |
| succeeded | Fonds confirmés | Marquer payé, expédier, envoyer le reçu |
| failed | Échec définitif | Proposer un autre opérateur, conserver la commande |
| refunded | Remboursement effectué | Mettre à jour la comptabilité et le stock |
Prévoyez une tâche de vérification qui interroge l'API pour les paiements restés en attente après un délai raisonnable. Si le statut ne change pas, montrez au client un message clair avec un contact — pas une erreur générique.
Idempotence : le détail qui évite les doubles débits
- Votre référence de commande doit être unique et ne jamais être réutilisée pour un autre panier.
- Avant de créer un paiement, vérifiez qu'un paiement actif n'existe pas déjà pour cette référence.
- Dans le webhook, enregistrez l'identifiant de l'événement traité. Un rejeu doit être sans effet.
- Si le client clique deux fois sur « payer », renvoyez le même paiement plutôt que d'en créer un second.
Réconciliation : la vérité est dans le règlement
Un paiement réussi dans votre base doit correspondre à un règlement sur votre compte. Trois contrôles quotidiens suffisent :
- Commandes payées sans paiement — souvent un webhook manqué ou un statut mal interprété.
- Paiements réussis sans commande — souvent une référence mal transmise ou un doublon.
- Écarts de montant — commission, arrondi ou remboursement partiel mal enregistré.
Exportez les transactions et les règlements, puis rapprochez-les par référence. Si votre volume grandit, faites-le via API plutôt que par fichier.
Sécurité : la checklist minimale
- Clé secrète stockée côté serveur, jamais dans le code front-end.
- Webhooks signés et signature vérifiée avant tout traitement.
- Montant recalculé côté serveur, jamais accepté du client.
- HTTPS obligatoire, y compris sur les URL de retour.
- Journalisation des événements, des rejeux et des changements de statut.
- Accès au tableau de bord limité par rôle et authentification forte.
- Plan de secours si l'opérateur est indisponible : message clair, autre canal, remboursement.
Checklist de mise en production
- Paiement test réussi sur les quatre réseaux
- Cas d'échec et d'annulation testés
- Webhook rejoué volontairement sans double traitement
- Statut « en attente » géré et visible pour le client
- Remboursement testé de bout en bout
- Export ou API de réconciliation branché
- Alerte en cas de pic d'échecs ou de webhooks en erreur
Une intégration mobile money solide n'est pas celle qui code le plus vite, mais celle qui sait quoi faire quand le réseau ne répond pas. La page développeurs AlmaPay détaille les endpoints et l'accompagnement technique ; vous pouvez aussi demander un accès de test.