Passer au contenu principal

Documentation Index

Fetch the complete documentation index at: https://developers.ligdicash.com/llms.txt

Use this file to discover all available pages before exploring further.

Cette page décrit les principes d’architecture à respecter pour une intégration LigdiCash robuste et sécurisée. Elle s’adresse aux développeurs qui conçoivent l’infrastructure autour de l’API.

Principe fondamental : tout passe par le backend

Vos identifiants LigdiCash (Apikey et Auth Token) ne doivent jamais apparaître dans votre frontend, votre application mobile ou votre code source public. Toutes les requêtes vers l’API LigdiCash partent de votre serveur.
Client / App mobile
       |

Votre backend (proxy)   ←→   API LigdiCash
       |

Votre base de données
Un Apikey ou Auth Token exposé côté client permet à n’importe qui de créer des factures ou déclencher des payouts en votre nom. Stockez ces secrets dans des variables d’environnement serveur uniquement.

Structure de la table des transactions

Votre base de données doit conserver assez d’informations pour tracer chaque transaction, traiter le callback de manière idempotente et effectuer un polling de fallback.
CREATE TABLE transactions_paiement (
    id              VARCHAR(64) PRIMARY KEY,   -- votre transaction_id interne
    commande_id     VARCHAR(64) NOT NULL,
    montant         INTEGER NOT NULL,           -- en XOF, entier
    statut          VARCHAR(20) NOT NULL DEFAULT 'pending',
                                               -- pending | completed | notcompleted
    ligdicash_token TEXT,                       -- token retourné à la création
    ligdicash_ref   VARCHAR(64),               -- operator_id retourné dans le callback
    callback_recu_at TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX ON transactions_paiement (commande_id);
CREATE INDEX ON transactions_paiement (statut, created_at);
Le champ ligdicash_token doit être stocké dès la création de la facture. C’est lui que vous utiliserez pour appeler l’endpoint confirm, aussi bien dans le callback que dans le polling de fallback. Ne comptez pas sur le token reçu dans le callback — c’est un token différent.

Endpoints à exposer côté backend

Votre backend joue le rôle de proxy entre votre frontend et l’API LigdiCash. Exposez au minimum ces trois routes :
RouteMéthodeRôle
/api/paiement/initierPOSTCrée la facture LigdiCash, stocke le token, retourne l’URL de paiement
/api/paiement/:id/statutGETRetourne le statut de la transaction (avec polling LigdiCash si encore pending)
/api/ligdicash/callbackPOSTReçoit les notifications LigdiCash, re-vérifie, met à jour la base

Route d’initiation

Node.js (Express)
app.post("/api/paiement/initier", async (req, res) => {
  const { commandeId, montant, description, clientEmail } = req.body;

  // Générer un transaction_id unique
  const transactionId = `txn_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;

  // Créer la transaction en base
  await db.transactionsPaiement.insert({
    id: transactionId,
    commande_id: commandeId,
    montant,
    statut: "pending",
  });

  // Appeler LigdiCash
  const ligdicash = await fetch(
    "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create",
    {
      method: "POST",
      headers: {
        Apikey: process.env.LIGDICASH_API_KEY,
        Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        commande: {
          invoice: {
            items: [],
            total_amount: montant,
            devise: "XOF",
            description,
            customer: "",
            customer_email: clientEmail ?? "",
            external_id: "",
            otp: "",
          },
          store: {
            name: process.env.STORE_NAME,
            website_url: process.env.STORE_URL,
          },
          actions: {
            cancel_url: `${process.env.FRONTEND_URL}/paiement/annule?txn=${transactionId}`,
            return_url: `${process.env.FRONTEND_URL}/paiement/succes?txn=${transactionId}`,
            callback_url: `${process.env.BACKEND_URL}/api/ligdicash/callback`,
          },
          custom_data: { transaction_id: transactionId },
        },
      }),
    }
  ).then((r) => r.json());

  if (ligdicash.response_code !== "00") {
    await db.transactionsPaiement.update(transactionId, { statut: "notcompleted" });
    return res.status(502).json({ error: "ligdicash_error", detail: ligdicash.description });
  }

  // Stocker le token
  await db.transactionsPaiement.update(transactionId, {
    ligdicash_token: ligdicash.token,
  });

  res.json({
    transaction_id: transactionId,
    pay_url: ligdicash.response_text,
  });
});

Route de callback

Node.js (Express)
app.post("/api/ligdicash/callback", express.json(), async (req, res) => {
  // Répondre 200 immédiatement
  res.sendStatus(200);

  // Extraire le transaction_id
  const customData = req.body.custom_data ?? [];
  const entry = Array.isArray(customData)
    ? customData.find((e) => e.keyof_customdata === "transaction_id")
    : null;

  if (!entry?.valueof_customdata) return;

  const transactionId = entry.valueof_customdata;
  const txn = await db.transactionsPaiement.findById(transactionId);

  // Idempotence : ignorer si déjà traité
  if (!txn || txn.statut !== "pending") return;

  // Re-vérifier via confirm
  const confirm = await fetch(
    `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${txn.ligdicash_token}`,
    {
      headers: {
        Apikey: process.env.LIGDICASH_API_KEY,
        Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
        Accept: "application/json",
      },
    }
  ).then((r) => r.json());

  await db.transactionsPaiement.update(transactionId, {
    statut: confirm.status ?? "pending",
    callback_recu_at: new Date(),
  });

  if (confirm.status === "completed") {
    await commandes.confirmer(txn.commande_id);
  }
});

Fallback polling

LigdiCash n’impose pas de politique de retry sur les callbacks. Si votre endpoint était indisponible ou si un callback a été manqué, une transaction peut rester pending indéfiniment. Mettez en place un polling périodique côté backend.
Fallback polling (cron)
// Exécuté toutes les 5 minutes par un cron job
async function pollTransactionsPending() {
  // Transactions pending depuis plus de 2 minutes et moins de 24h
  const txns = await db.transactionsPaiement.findAll({
    statut: "pending",
    created_at: { gte: new Date(Date.now() - 24 * 3600 * 1000) },
    callback_recu_at: null,
  });

  for (const txn of txns) {
    if (!txn.ligdicash_token) continue;

    const confirm = await fetch(
      `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${txn.ligdicash_token}`,
      {
        headers: {
          Apikey: process.env.LIGDICASH_API_KEY,
          Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
          Accept: "application/json",
        },
      }
    ).then((r) => r.json());

    if (confirm.status && confirm.status !== "pending") {
      await db.transactionsPaiement.update(txn.id, { statut: confirm.status });

      if (confirm.status === "completed") {
        await commandes.confirmer(txn.commande_id);
      }
    }
  }
}
LigdiCash ne passe jamais automatiquement une transaction en notcompleted. Une transaction reste pending indéfiniment si le client n’a pas finalisé le paiement. Après 24h, vous pouvez considérer la transaction expirée et la clôturer vous-même.

Logging et audit

Conservez une trace de chaque interaction avec LigdiCash pour faciliter le diagnostic en cas de litige.
CREATE TABLE logs_paiement (
    id              BIGSERIAL PRIMARY KEY,
    transaction_id  VARCHAR(64),
    direction       VARCHAR(10) NOT NULL,  -- 'outgoing' | 'incoming'
    type            VARCHAR(30) NOT NULL,  -- 'create' | 'confirm' | 'callback' | 'poll'
    payload         JSONB,
    response        JSONB,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Loggez aussi bien les requêtes sortantes vers LigdiCash que les callbacks entrants. En cas de litige sur une transaction, vous pourrez présenter un historique complet avec horodatage.

Variables d’environnement

.env
# Clés LigdiCash — NE JAMAIS les exposer côté client
LIGDICASH_API_KEY=votre_apikey
LIGDICASH_AUTH_TOKEN=votre_auth_token

# URLs
STORE_NAME=Ma Boutique
STORE_URL=https://maboutique.com
FRONTEND_URL=https://maboutique.com
BACKEND_URL=https://api.maboutique.com

Schéma récapitulatif

Pages associées