> ## 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.

# Architecture recommandée

> Comment structurer votre backend pour intégrer LigdiCash en toute sécurité : proxy, base de données, gestion du callback et fallback polling.

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
```

<Warning>
  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.
</Warning>

## 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.

```sql theme={null}
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);
```

<Note>
  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.
</Note>

## 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

```javascript Node.js (Express) theme={null}
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

```javascript Node.js (Express) theme={null}
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.

```javascript Fallback polling (cron) theme={null}
// 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);
      }
    }
  }
}
```

<Note>
  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.
</Note>

## Logging et audit

Conservez une trace de chaque interaction avec LigdiCash pour faciliter le diagnostic en cas de litige.

```sql theme={null}
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()
);
```

<Tip>
  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.
</Tip>

## Variables d'environnement

```bash .env theme={null}
# 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

```mermaid theme={null}
flowchart LR
    CLIENT["Client"]
    FE["Frontend / App mobile"]
    PROXY["Votre backend"]
    DB[("Base de données")]
    CRON["Cron polling"]
    LC["API LigdiCash"]
    PAY["Page de paiement\nLigdiCash"]

    CLIENT -->|"1. initie"| FE
    FE -->|"2. POST /initier"| PROXY
    PROXY -->|"3. INSERT pending"| DB
    PROXY -->|"4. POST /create"| LC
    LC -->|"5. token + pay_url"| PROXY
    PROXY -->|"6. stocke token"| DB
    PROXY -->|"7. pay_url"| FE
    FE -->|"8. redirige"| PAY
    CLIENT -->|"9. paie"| PAY
    PAY -->|"10. POST /callback"| PROXY
    PROXY -->|"11. GET /confirm"| LC
    LC -->|"12. status"| PROXY
    PROXY -->|"13. UPDATE statut"| DB
    CRON -.->|"poll pending"| DB
    CRON -.->|"GET /confirm"| LC
```

## Pages associées

* [Sécurisation du callback](/api-paiement/callback/securisation) — pourquoi re-vérifier toujours
* [Idempotence du callback](/api-paiement/callback/idempotence) — gérer le double envoi LigdiCash
* [Pattern transaction\_id](/concepts/transaction-id-pattern) — identifier les transactions de manière fiable
* [Checklist de mise en production](/guides/checklist-production) — vérifications avant le go-live
