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 :
| Route | Méthode | Rôle |
|---|
/api/paiement/initier | POST | Crée la facture LigdiCash, stocke le token, retourne l’URL de paiement |
/api/paiement/:id/statut | GET | Retourne le statut de la transaction (avec polling LigdiCash si encore pending) |
/api/ligdicash/callback | POST | Reçoit les notifications LigdiCash, re-vérifie, met à jour la base |
Route d’initiation
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
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.
// 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
# 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