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

  1. 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.
  2. La confirmation client — le client est redirigé vers une page de paiement ou reçoit une sollicitation USSD sur son téléphone.
  3. Le webhook — l'opérateur ou l'agrégateur notifie votre serveur du statut final, avec une signature.
  4. 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.
  5. 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.

StatutSignificationAction côté boutique
pendingPaiement créé, confirmation client en coursAfficher « en attente », ne pas expédier, relancer la vérification
processingL'opérateur traite l'opérationAttendre, informer le client, ne pas recréer un paiement
succeededFonds confirmésMarquer payé, expédier, envoyer le reçu
failedÉchec définitifProposer un autre opérateur, conserver la commande
refundedRemboursement 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.

Un dernier conseil. Testez avec de vrais téléphones et de vraies cartes SIM des quatre opérateurs, sur les réseaux que vos clients utilisent. Les émulateurs ne reproduisent ni les délais USSD ni les coupures.