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

# Guide e-commerce : panier → paiement → livraison

> Implémentez un flux de paiement complet avec LigdiCash : création de commande, paiement mobile money, réception du callback et confirmation de livraison.

Ce guide décrit le flux de bout en bout pour un site e-commerce : depuis la validation du panier côté client jusqu'à la confirmation de livraison côté serveur. Il utilise le payin avec redirection, qui est le mode d'intégration le plus simple et le plus compatible avec tous les opérateurs.

**Pré-requis :** vous avez un projet API LigdiCash avec une Apikey et un Auth Token. Voir [Créer un projet API](/concepts/projet-api).

## Vue d'ensemble du flux

```
Client           Frontend          Backend          LigdiCash
  |                 |                  |                 |
  |-- Valider --->  |                  |                 |
  |   le panier     |-- POST /pay --> |                  |
  |                 |                  |-- create -----> |
  |                 |                  | <-- token ----- |
  |                 | <-- pay_url ---- |                 |
  |-- Redirect -->  |                  |                 |
  |                 |--- Ouvre URL --> |                 |
  |  [Paiement sur la page LigdiCash]  |                 |
  |                 |                  | <-- callback -- |
  |                 |                  |-- confirm ----> |
  |                 |                  | <-- statut ---- |
  |                 |                  |-- Confirme      |
  |                 |                  |   commande      |
  | <-- Retour ---- |                  |                 |
  |   return_url    |-- GET /statut -> |                 |
  |                 | <-- résultat --- |                 |
```

<Warning>
  Ne jamais appeler l'API LigdiCash directement depuis le frontend. Votre Apikey et votre Auth Token doivent rester sur votre backend. Voir [Architecture recommandée](/guides/architecture-recommandee).
</Warning>

## Étape 1 — Stocker la commande avant le paiement

Avant de créer la facture LigdiCash, persistez la commande en base de données avec le statut `pending`. Vous aurez besoin de son identifiant pour tracer le paiement.

```sql theme={null}
INSERT INTO commandes (id, client_id, montant, statut, created_at)
VALUES ('cmd_7f3a9b', 42, 15000, 'pending', NOW());
```

<Tip>
  Utilisez un identifiant interne non-devinable (UUID ou ID préfixé) comme `transaction_id`. Vous le passerez dans `custom_data` et l'utiliserez pour identifier la commande dans le callback.
</Tip>

## Étape 2 — Créer la facture côté backend

Votre backend appelle l'endpoint de création de facture et stocke le `token` retourné.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create \
    -H "Apikey: {API_KEY}" \
    -H "Authorization: Bearer {AUTH_TOKEN}" \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
      "commande": {
        "invoice": {
          "items": [
            {
              "name": "Abonnement Premium",
              "description": "1 mois d'\''accès Premium",
              "quantity": 1,
              "unit_price": 15000,
              "total_price": 15000
            }
          ],
          "total_amount": 15000,
          "devise": "XOF",
          "description": "Commande #cmd_7f3a9b",
          "customer": "",
          "customer_firstname": "Aminata",
          "customer_lastname": "Ouédraogo",
          "customer_email": "aminata@exemple.com",
          "external_id": "",
          "otp": ""
        },
        "store": {
          "name": "Ma Boutique",
          "website_url": "https://maboutique.com"
        },
        "actions": {
          "cancel_url": "https://maboutique.com/paiement/annule",
          "return_url": "https://maboutique.com/paiement/succes",
          "callback_url": "https://maboutique.com/api/ligdicash/callback"
        },
        "custom_data": {
          "transaction_id": "cmd_7f3a9b"
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = 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: [
              {
                name: "Abonnement Premium",
                description: "1 mois d'accès Premium",
                quantity: 1,
                unit_price: 15000,
                total_price: 15000,
              },
            ],
            total_amount: 15000,
            devise: "XOF",
            description: "Commande #cmd_7f3a9b",
            customer: "",
            customer_firstname: "Aminata",
            customer_lastname: "Ouédraogo",
            customer_email: "aminata@exemple.com",
            external_id: "",
            otp: "",
          },
          store: {
            name: "Ma Boutique",
            website_url: "https://maboutique.com",
          },
          actions: {
            cancel_url: "https://maboutique.com/paiement/annule",
            return_url: "https://maboutique.com/paiement/succes",
            callback_url: "https://maboutique.com/api/ligdicash/callback",
          },
          custom_data: {
            transaction_id: "cmd_7f3a9b",
          },
        },
      }),
    }
  );

  const data = await response.json();
  // data.response_code === "00" → succès
  // data.response_text → URL de paiement
  ```

  ```php PHP theme={null}
  $client = new \GuzzleHttp\Client();

  $response = $client->post(
      'https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create',
      [
          'headers' => [
              'Apikey'        => $_ENV['LIGDICASH_API_KEY'],
              'Authorization' => 'Bearer ' . $_ENV['LIGDICASH_AUTH_TOKEN'],
              'Accept'        => 'application/json',
              'Content-Type'  => 'application/json',
          ],
          'json' => [
              'commande' => [
                  'invoice' => [
                      'items' => [
                          [
                              'name'        => 'Abonnement Premium',
                              'description' => '1 mois d\'accès Premium',
                              'quantity'    => 1,
                              'unit_price'  => 15000,
                              'total_price' => 15000,
                          ],
                      ],
                      'total_amount'      => 15000,
                      'devise'            => 'XOF',
                      'description'       => 'Commande #cmd_7f3a9b',
                      'customer'          => '',
                      'customer_firstname'=> 'Aminata',
                      'customer_lastname' => 'Ouédraogo',
                      'customer_email'    => 'aminata@exemple.com',
                      'external_id'       => '',
                      'otp'               => '',
                  ],
                  'store' => [
                      'name'        => 'Ma Boutique',
                      'website_url' => 'https://maboutique.com',
                  ],
                  'actions' => [
                      'cancel_url'   => 'https://maboutique.com/paiement/annule',
                      'return_url'   => 'https://maboutique.com/paiement/succes',
                      'callback_url' => 'https://maboutique.com/api/ligdicash/callback',
                  ],
                  'custom_data' => [
                      'transaction_id' => 'cmd_7f3a9b',
                  ],
              ],
          ],
      ]
  );

  $data = json_decode($response->getBody(), true);
  // $data['response_code'] === '00' → succès
  // $data['response_text'] → URL de paiement
  ```
</CodeGroup>

Réponse en cas de succès (`response_code: "00"`) :

```json theme={null}
{
  "response_code": "00",
  "response_text": "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=eyJ0...",
  "description": "Checkout-Invoice created with success.",
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
```

**Stockez le `token` en base de données** en l'associant à votre commande. Vous en aurez besoin pour vérifier le paiement.

```sql theme={null}
UPDATE commandes
SET ligdicash_token = 'eyJ0eXAiOiJKV1Qi...'
WHERE id = 'cmd_7f3a9b';
```

## Étape 3 — Rediriger le client vers la page de paiement

Retournez `response_text` (l'URL de paiement) à votre frontend, puis redirigez le client.

```javascript Frontend (JavaScript) theme={null}
// Appel backend → récupère l'URL de paiement
const { payUrl } = await fetch("/api/commandes/cmd_7f3a9b/payer", {
  method: "POST",
}).then((r) => r.json());

// Redirection dans le même onglet (recommandé)
window.location.href = payUrl;

// Ou dans un nouvel onglet
// window.open(payUrl, "_blank");
```

<Warning>
  Ne jamais ouvrir l'URL LigdiCash dans une `<iframe>`. LigdiCash bloque l'affichage en iframe. Utilisez une redirection dans le même onglet, un nouvel onglet, ou un popup. Voir [Pièges courants](/api-paiement/payin-redirect/pieges-courants).
</Warning>

Le client voit la page de paiement LigdiCash, sélectionne son opérateur mobile money et finalise le paiement. LigdiCash le redirige ensuite vers votre `return_url` (succès) ou `cancel_url` (annulation).

## Étape 4 — Réceptionner le callback

LigdiCash envoie une notification POST à votre `callback_url` dès que le statut de la transaction change. Votre backend doit :

1. Identifier la commande via `custom_data`
2. Re-vérifier le statut avec l'endpoint `confirm` (ne jamais faire confiance au payload seul)
3. Mettre à jour la commande en base
4. Répondre `200 OK`

```javascript Node.js (Express) theme={null}
app.post("/api/ligdicash/callback", express.json(), async (req, res) => {
  // Répondre 200 immédiatement pour éviter les retries
  res.sendStatus(200);

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

  if (!entry) return;

  const transactionId = entry.valueof_customdata;

  // Récupérer la commande et son token LigdiCash
  const commande = await db.commandes.findById(transactionId);
  if (!commande || commande.statut !== "pending") return;

  // Re-vérifier le statut côté LigdiCash
  const confirm = await fetch(
    `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${commande.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 === "completed") {
    await db.commandes.update(transactionId, { statut: "payee" });
    await livraison.declencher(transactionId);
  } else if (confirm.status === "notcompleted") {
    await db.commandes.update(transactionId, { statut: "echec" });
  }
  // Si "pending" : ne rien faire, attendre le prochain callback
});
```

<Note>
  LigdiCash envoie **deux requêtes POST** à votre callback pour chaque événement : une en `application/x-www-form-urlencoded` et une en `application/json`. Traitez-les de manière idempotente. Voir [Idempotence du callback](/api-paiement/callback/idempotence).
</Note>

## Étape 5 — Vérifier le statut côté frontend

Après la redirection vers votre `return_url`, le frontend interroge votre backend pour afficher le résultat final. La `return_url` est un signal d'interface — elle ne prouve pas que le paiement a réussi.

```javascript Frontend — page return_url theme={null}
// Extrait le transaction_id depuis l'URL (ex: /paiement/succes?cmd=cmd_7f3a9b)
const params = new URLSearchParams(window.location.search);
const commandeId = params.get("cmd");

const { statut } = await fetch(`/api/commandes/${commandeId}/statut`).then(
  (r) => r.json()
);

switch (statut) {
  case "payee":
    afficherConfirmation();
    break;
  case "pending":
    // Le callback n'est pas encore arrivé — afficher un loader et repolling
    afficherAttenteVerification();
    break;
  case "echec":
    afficherErreur();
    break;
}
```

```javascript Backend — GET /api/commandes/:id/statut theme={null}
app.get("/api/commandes/:id/statut", async (req, res) => {
  const commande = await db.commandes.findById(req.params.id);
  if (!commande) return res.status(404).json({ error: "not_found" });

  // Si toujours pending, interroger LigdiCash en temps réel
  if (commande.statut === "pending") {
    const confirm = await fetch(
      `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?token=${commande.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 === "completed") {
      await db.commandes.update(commande.id, { statut: "payee" });
      commande.statut = "payee";
    }
  }

  res.json({ statut: commande.statut });
});
```

## Récapitulatif

<Steps>
  <Step title="Stocker la commande">
    Créez la commande en base avec le statut `pending` avant tout appel LigdiCash.
  </Step>

  <Step title="Créer la facture">
    Backend → POST `/pay/v01/redirect/checkout-invoice/create`. Stockez le `token` retourné.
  </Step>

  <Step title="Rediriger le client">
    Frontend → redirection vers `response_text` (même onglet ou popup). Jamais d'iframe.
  </Step>

  <Step title="Réceptionner le callback">
    Répondre `200` immédiatement, puis re-vérifier via `confirm` avec le token stocké.
  </Step>

  <Step title="Confirmer la commande">
    Mettre à jour le statut en base et déclencher la livraison si `status === "completed"`.
  </Step>

  <Step title="Afficher le résultat">
    Frontend → interroger votre backend pour afficher le statut réel après retour sur `return_url`.
  </Step>
</Steps>

## Pages associées

* [Créer une facture](/api-paiement/payin-redirect/creer-facture) — paramètres complets de l'endpoint
* [Sécurisation du callback](/api-paiement/callback/securisation) — pattern de re-vérification détaillé
* [Parser custom\_data](/api-paiement/callback/parser-custom-data) — les 3 formes possibles
* [Architecture recommandée](/guides/architecture-recommandee) — structure backend complète
