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

# Exemples par framework

> Implémentations complètes du callback LigdiCash en PHP, Laravel, Node.js Express et Python.

Ces exemples montrent un handler de callback complet : déduplication, re-vérification et traitement métier. Adaptez les détails (nom de table, logique métier) à votre application.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    <?php

    $rawBody = file_get_contents('php://input');
    $contentType = $_SERVER['CONTENT_TYPE'] ?? '';

    // Supporter JSON et form-urlencoded
    if (str_contains($contentType, 'application/json')) {
        $payload = json_decode($rawBody, true);
    } else {
        parse_str($rawBody, $payload);
        // custom_data arrive en JSON stringifié en form-urlencoded
        if (isset($payload['custom_data']) && is_string($payload['custom_data'])) {
            $payload['custom_data'] = json_decode($payload['custom_data'], true) ?? $payload['custom_data'];
        }
    }

    $transactionId = null;
    foreach ($payload['custom_data'] ?? [] as $entry) {
        if (($entry['keyof_customdata'] ?? null) === 'transaction_id') {
            $transactionId = $entry['valueof_customdata'] ?? null;
            break;
        }
    }
    if (!$transactionId) {
        http_response_code(400);
        exit;
    }

    // Déduplication
    try {
        $pdo->exec("INSERT INTO processed_callbacks (transaction_id) VALUES ('$transactionId')");
    } catch (PDOException $e) {
        // Déjà traité — doublon LigdiCash
        http_response_code(200);
        exit;
    }

    // Retrouver le token stocké à la création
    $stmt = $pdo->prepare('SELECT token FROM transactions WHERE transaction_id = ?');
    $stmt->execute([$transactionId]);
    $row = $stmt->fetch();

    if (!$row) {
        http_response_code(404);
        exit;
    }

    // Re-vérification
    $params = http_build_query(['invoiceToken' => $row['token']]);
    $ch = curl_init("https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?$params");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Apikey: ' . $_ENV['LIGDICASH_API_KEY'],
            'Authorization: Bearer ' . $_ENV['LIGDICASH_AUTH_TOKEN'],
        ],
    ]);
    $result = json_decode(curl_exec($ch), true);
    curl_close($ch);

    if (($result['status'] ?? '') === 'completed') {
        // Traitement métier
        $pdo->exec("UPDATE orders SET status = 'paid' WHERE transaction_id = '$transactionId'");
    }

    http_response_code(200);
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Support\Facades\Http;

    class LigdicashCallbackController extends Controller
    {
        public function handle(Request $request)
        {
            $transactionId = collect($request->input('custom_data', []))
                ->firstWhere('keyof_customdata', 'transaction_id')['valueof_customdata'] ?? null;

            if (!$transactionId) {
                return response()->noContent(400);
            }

            // Déduplication atomique
            $inserted = DB::table('processed_callbacks')->insertOrIgnore([
                'transaction_id' => $transactionId,
                'created_at' => now(),
            ]);

            if (!$inserted) {
                return response()->noContent(200);
            }

            // Retrouver le token stocké
            $transaction = DB::table('transactions')
                ->where('transaction_id', $transactionId)
                ->first();

            if (!$transaction) {
                return response()->noContent(404);
            }

            // Re-vérification
            $result = Http::withHeaders([
                'Apikey' => config('ligdicash.api_key'),
                'Authorization' => 'Bearer ' . config('ligdicash.auth_token'),
            ])->get('https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm', [
                'invoiceToken' => $transaction->token,
            ])->json();

            if (($result['status'] ?? '') === 'completed') {
                DB::table('orders')
                    ->where('transaction_id', $transactionId)
                    ->update(['status' => 'paid']);
            }

            return response()->noContent(200);
        }
    }
    ```
  </Tab>

  <Tab title="Node.js / Express">
    ```javascript theme={null}
    import express from 'express';

    const app = express();
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));

    app.post('/callback', async (req, res) => {
      const payload = req.body;
      const customData = Array.isArray(payload.custom_data) ? payload.custom_data : [];
      const entry = customData.find((item) => item.keyof_customdata === 'transaction_id');
      const transactionId = entry?.valueof_customdata;

      if (!transactionId) {
        return res.sendStatus(400);
      }

      // Déduplication atomique
      try {
        await db.query(
          'INSERT INTO processed_callbacks (transaction_id) VALUES ($1)',
          [transactionId]
        );
      } catch {
        // Contrainte d'unicité — doublon LigdiCash
        return res.sendStatus(200);
      }

      // Retrouver le token stocké
      const row = await db.query(
        'SELECT token FROM transactions WHERE transaction_id = $1',
        [transactionId]
      ).then(r => r.rows[0]);

      if (!row) return res.sendStatus(404);

      // Re-vérification
      const params = new URLSearchParams({ invoiceToken: row.token });
      const verify = await fetch(
        `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?${params}`,
        {
          headers: {
            Apikey: process.env.LIGDICASH_API_KEY,
            Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
          },
        }
      );
      const result = await verify.json();

      if (result.status === 'completed') {
        await db.query(
          "UPDATE orders SET status = 'paid' WHERE transaction_id = $1",
          [transactionId]
        );
      }

      res.sendStatus(200);
    });
    ```
  </Tab>

  <Tab title="Python / Django">
    ```python theme={null}
    import json
    import os
    import requests
    from django.db import IntegrityError
    from django.http import HttpResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_POST


    @csrf_exempt
    @require_POST
    def ligdicash_callback(request):
        content_type = request.content_type or ""

        if "application/json" in content_type:
            payload = json.loads(request.body)
        else:
            payload = request.POST.dict()
            # custom_data arrive en JSON stringifié en form-urlencoded
            if isinstance(payload.get("custom_data"), str):
                try:
                    payload["custom_data"] = json.loads(payload["custom_data"])
                except (json.JSONDecodeError, TypeError):
                    pass

        custom_data = payload.get("custom_data") or []
        entry = next(
            (e for e in custom_data if e.get("keyof_customdata") == "transaction_id"),
            None,
        )
        transaction_id = entry.get("valueof_customdata") if entry else None
        if not transaction_id:
            return HttpResponse(status=400)

        # Déduplication atomique
        try:
            ProcessedCallback.objects.create(transaction_id=transaction_id)
        except IntegrityError:
            return HttpResponse(status=200)

        # Retrouver le token stocké
        try:
            transaction = Transaction.objects.get(transaction_id=transaction_id)
        except Transaction.DoesNotExist:
            return HttpResponse(status=404)

        # Re-vérification
        result = requests.get(
            "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm",
            params={"invoiceToken": transaction.token},
            headers={
                "Apikey": os.environ["LIGDICASH_API_KEY"],
                "Authorization": f"Bearer {os.environ['LIGDICASH_AUTH_TOKEN']}",
            },
        ).json()

        if result.get("status") == "completed":
            Order.objects.filter(transaction_id=transaction_id).update(status="paid")

        return HttpResponse(status=200)
    ```
  </Tab>
</Tabs>

<Note>
  Les exemples ci-dessus utilisent l'endpoint de confirmation payin redirect. Pour un payout, remplacez l'endpoint par `GET /pay/v01/withdrawal/confirm/?withdrawalToken={token}`. Voir [Vérifier le statut d'un payout](/api-paiement/payout/verifier-statut).
</Note>

## Pages associées

* [Sécurisation du callback](/api-paiement/callback/securisation)
* [Idempotence et déduplication](/api-paiement/callback/idempotence)
* [Parser custom\_data](/api-paiement/callback/parser-custom-data)
