Express.jsNode.js 18+ · @moncashconnect/sdk · Express 4+

Intégrer MonCash avec Express.js

Une API Express complète : créer un paiement, gérer le retour client et recevoir les confirmations par webhook signé — le tout dans un seul fichier.

Sandbox

Commencez en Sandbox

Avant d'implémenter le code ci-dessous avec votre clé live (sk_proj_…), nous recommandons de tester d'abord en mode Sandbox avec sk_test_proj_…. Le code est identique — vous ne changez que la valeur de la variable d'environnement. Lisez le Guide Sandbox avant de continuer.

Prérequis

  • Node.js 18+
  • npm ou pnpm
  • Un projet MonCashConnect avec clé secrète — créer un projet

Installation

npm install express @moncashconnect/sdk dotenv

Si vous utilisez TypeScript : npm install -D typescript @types/express @types/node

Variables d'environnement

Créez un fichier .env à la racine :

MCC_SECRET_KEY=sk_proj_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MCC_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PORT=3000
APP_URL=https://votresite.com

Serveur complet (server.js)

Voici un serveur Express fonctionnel couvrant les trois routes essentielles. Copiez-le dans server.js et adaptez la logique métier (mise à jour BDD, rendu de template, etc.).

import "dotenv/config";
import express from "express";
import { MonCashClient, constructEvent, MonCashError } from "@moncashconnect/sdk";

const app    = express();
const client = new MonCashClient(process.env.MCC_SECRET_KEY);

// ── Middlewares ───────────────────────────────────────────────────────
// JSON pour toutes les routes sauf /webhooks/moncash
app.use((req, res, next) => {
  if (req.path === "/webhooks/moncash") return next();
  express.json()(req, res, next);
});

// ── POST /pay ─────────────────────────────────────────────────────────
// Crée un paiement et redirige le client vers MonCash
app.post("/pay", async (req, res) => {
  const { orderId, amount } = req.body;

  if (!orderId || !amount) {
    return res.status(400).json({ error: "orderId et amount sont requis." });
  }

  try {
    const payment = await client.createPayment(Number(amount), String(orderId), {
      returnUrl: `${process.env.APP_URL}/pay/return`,
    });
    res.redirect(payment.paymentUrl);
  } catch (err) {
    if (err instanceof MonCashError) {
      // 409 = referenceId déjà utilisé
      return res.status(err.statusCode).json({ error: err.message });
    }
    res.status(500).json({ error: "Erreur serveur." });
  }
});

// ── GET /pay/return ───────────────────────────────────────────────────
// Page de retour après paiement — vérifier le statut côté serveur
app.get("/pay/return", async (req, res) => {
  const { ref } = req.query;
  if (!ref) return res.status(400).send("Référence manquante.");

  try {
    const tx = await client.getPaymentStatus(String(ref));
    // Remplacez par res.render("return", { tx }) si vous utilisez un moteur de template
    res.json(tx);
  } catch (err) {
    if (err instanceof MonCashError) {
      return res.status(err.statusCode).json({ error: err.message });
    }
    res.status(500).json({ error: "Impossible de vérifier le statut." });
  }
});

// ── POST /webhooks/moncash ────────────────────────────────────────────
// express.raw() est obligatoire — ne pas utiliser express.json() ici
app.post(
  "/webhooks/moncash",
  express.raw({ type: "application/json" }),
  (req, res) => {
    try {
      const event = constructEvent(
        req.body,                                  // Buffer
        req.headers["x-mcc-signature"] ?? "",
        req.headers["x-mcc-timestamp"] ?? "",
        process.env.MCC_WEBHOOK_SECRET,
      );

      if (event.event === "payment.completed") {
        // Mettez à jour votre BDD ici
        console.log("✅ Paiement confirmé :", event.reference, event.amount, "HTG");
      } else if (event.event === "payment.failed") {
        console.log("❌ Paiement échoué :", event.reference);
      }

      res.sendStatus(200); // Toujours répondre 200
    } catch (err) {
      if (err instanceof MonCashError) {
        return res.status(err.statusCode).send(err.message);
      }
      res.sendStatus(500);
    }
  }
);

// ── Start ─────────────────────────────────────────────────────────────
const port = process.env.PORT ?? 3000;
app.listen(port, () => console.log(`Serveur démarré sur le port ${port}`));

Points clés

Pourquoi séparer le middleware JSON du webhook ?

express.json() consomme le stream de la requête et reconstruit le corps — la signature HMAC ne correspond alors plus au corps brut reçu. express.raw() donne accès au Buffer original, sur lequel la signature est vérifiée.

Référence dupliquée (409)

Si vous créez plusieurs paiements pour le même orderId, l'API retourne 409 Conflict. Utilisez un identifiant unique (timestamp, UUID, numéro de tentative) ou vérifiez d'abord le statut de la commande avant de créer un nouveau paiement.

Page de retour vs webhook

La page GET /pay/return est pour l'UX — elle montre un statut immédiat au client. Le webhook est la source de vérité pour créditer la commande. Le statut peut être pending sur la page de retour si MonCash n'a pas encore finalisé la confirmation.

Ne marquez jamais une commande comme payée sur la base de la redirection vers /pay/return. Attendez toujours l'événement payment.completed du webhook.

Liste de contrôle avant mise en production

MCC_SECRET_KEY et MCC_WEBHOOK_SECRET sont dans les variables d'environnement du serveur — pas dans le code ou le dépôt Git

express.raw({ type: 'application/json' }) est utilisé sur la route webhook (pas express.json())

La route webhook répond 200 même si l'événement est inconnu (pour éviter les retries inutiles)

Votre handler webhook est idempotent — vérifiez si la commande est déjà payée avant de la créditer

APP_URL pointe vers votre domaine de production (pas localhost)

L'URL https://votresite.com/webhooks/moncash est configurée dans le tableau de bord MonCashConnect