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

# Rediriger le client vers la page de paiement

> Comment ouvrir le lien de paiement LigdiCash : même onglet, nouvel onglet, popup ou WebView native. Contraintes et patterns recommandés.

Après la création de la facture, le champ `response_text` de la réponse contient l'URL de la page de paiement LigdiCash. C'est cette URL que vous devez ouvrir pour que le client puisse choisir son opérateur et finaliser le paiement. Le mode d'ouverture dépend de votre contexte : application web, mobile natif ou hybride.

## L'iframe est bloqué

<Warning>
  LigdiCash bloque le chargement de sa page de paiement dans un `<iframe>`. N'intégrez jamais l'URL dans une iframe — la page ne s'affichera pas.

  Ouvrez toujours le lien dans un contexte de navigation réel : même onglet, nouvel onglet, popup ou WebView native.
</Warning>

Ce blocage est appliqué côté serveur via les en-têtes `X-Frame-Options` et `Content-Security-Policy`. Il ne s'agit pas d'un bug — c'est une mesure de sécurité délibérée contre le clickjacking.

## Les 4 modes d'ouverture

| Mode           | Contexte recommandé                                | Implémentation                          |
| -------------- | -------------------------------------------------- | --------------------------------------- |
| Même onglet    | Web — parcours linéaire sans état à conserver      | `window.location.href = url`            |
| Nouvel onglet  | Web — l'utilisateur garde la page courante ouverte | `window.open(url, '_blank')`            |
| Popup          | Web — UX modale, retour automatique sur la page    | Pattern anti-bloqueur (voir ci-dessous) |
| WebView native | iOS / Android — application mobile native          | Détecter les URLs de retour pour fermer |

## Pattern popup (web)

Le déclenchement d'un `window.open()` après une requête réseau est systématiquement bloqué par les navigateurs modernes : il n'est pas considéré comme une action directe de l'utilisateur.

**La solution en deux étapes :**

1. Ouvrir `about:blank` **de manière synchrone** au clic, avant tout `await`
2. Naviguer vers l'URL de paiement **après** avoir reçu la réponse API

```javascript JavaScript theme={null}
async function lancerPaiement(commande) {
  // Étape 1 — ouvrir le popup immédiatement au clic (synchrone)
  const popup = window.open("about:blank", "paiement-ligdicash", "width=500,height=700");

  try {
    // Étape 2 — créer la facture
    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_API_TOKEN}`,
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify(commande),
      }
    );

    const data = await response.json();

    if (data.response_code === "00") {
      // Étape 3 — naviguer dans le popup déjà ouvert
      popup.location.href = data.response_text;
    } else {
      popup.close();
      console.error("Échec création facture :", data.response_text);
    }
  } catch (err) {
    popup.close();
    throw err;
  }
}
```

<Tip>
  Ce pattern fonctionne sur tous les navigateurs modernes (Chrome, Firefox, Safari, Edge). La clé est que `window.open()` soit appelé dans le même fil d'exécution synchrone que le gestionnaire de clic.
</Tip>

## Pattern WebView (mobile natif)

Sur iOS et Android, ouvrez l'URL de paiement dans une **WebView native** plutôt que dans le navigateur système. Cela vous permet de détecter la fin du flux et de fermer la WebView automatiquement.

**Principe :** interceptez chaque navigation dans la WebView et comparez l'URL avec votre `return_url` et `cancel_url`.

<CodeGroup>
  ```jsx React Native theme={null}
  import { WebView } from "react-native-webview";

  export function PaiementWebView({ paymentUrl, onSuccess, onCancel }) {
    const handleNavigationChange = (navState) => {
      const { url } = navState;

      if (url.startsWith("https://monapp.com/paiement/succes")) {
        onSuccess(); // toujours confirmer côté serveur ensuite
      } else if (url.startsWith("https://monapp.com/paiement/annule")) {
        onCancel();
      }
    };

    return (
      <WebView
        source={{ uri: paymentUrl }}
        onNavigationStateChange={handleNavigationChange}
      />
    );
  }
  ```

  ```dart Flutter theme={null}
  import 'package:webview_flutter/webview_flutter.dart';

  class PaiementWebView extends StatefulWidget {
    final String paymentUrl;
    final VoidCallback onSuccess;
    final VoidCallback onCancel;

    const PaiementWebView({
      required this.paymentUrl,
      required this.onSuccess,
      required this.onCancel,
      super.key,
    });

    @override
    State<PaiementWebView> createState() => _PaiementWebViewState();
  }

  class _PaiementWebViewState extends State<PaiementWebView> {
    late final WebViewController _controller;

    @override
    void initState() {
      super.initState();
      _controller = WebViewController()
        ..setNavigationDelegate(NavigationDelegate(
          onNavigationRequest: (request) {
            final url = request.url;

            if (url.startsWith("https://monapp.com/paiement/succes")) {
              widget.onSuccess(); // toujours confirmer côté serveur ensuite
              return NavigationDecision.prevent;
            } else if (url.startsWith("https://monapp.com/paiement/annule")) {
              widget.onCancel();
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ))
        ..loadRequest(Uri.parse(widget.paymentUrl));
    }

    @override
    Widget build(BuildContext context) {
      return WebViewWidget(controller: _controller);
    }
  }
  ```

  ```swift iOS (Swift) theme={null}
  func webView(_ webView: WKWebView,
               decidePolicyFor action: WKNavigationAction,
               decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

      let url = action.request.url?.absoluteString ?? ""

      if url.hasPrefix("https://monapp.com/paiement/succes") {
          decisionHandler(.cancel)
          dismissWebView()
          verifierStatutPaiement() // toujours confirmer côté serveur
      } else if url.hasPrefix("https://monapp.com/paiement/annule") {
          decisionHandler(.cancel)
          dismissWebView()
      } else {
          decisionHandler(.allow)
      }
  }
  ```

  ```kotlin Android (Kotlin) theme={null}
  webView.webViewClient = object : WebViewClient() {
      override fun shouldOverrideUrlLoading(
          view: WebView,
          request: WebResourceRequest
      ): Boolean {
          val url = request.url.toString()

          return when {
              url.startsWith("https://monapp.com/paiement/succes") -> {
                  dismissWebView()
                  verifierStatutPaiement() // toujours confirmer côté serveur
                  true
              }
              url.startsWith("https://monapp.com/paiement/annule") -> {
                  dismissWebView()
                  true
              }
              else -> false
          }
      }
  }
  ```
</CodeGroup>

<Note>
  Pour une implémentation complète incluant React Native et Flutter, consultez [Intégration mobile](/api-paiement/payin-redirect/integration-mobile).
</Note>

## Détecter le retour utilisateur

Quand LigdiCash redirige le client vers votre `return_url` ou `cancel_url`, cela indique que le flux de paiement côté LigdiCash est terminé. Ce n'est pas une preuve de succès du paiement.

<Warning>
  Ne considérez jamais un paiement comme validé sur la seule base de la redirection vers `return_url`. Un client peut modifier l'URL manuellement ou la redirection peut survenir suite à un timeout.

  Vérifiez systématiquement le statut via l'endpoint [confirm](/api-paiement/payin-redirect/verifier-statut) ou attendez la notification du [callback](/api-paiement/callback/introduction).
</Warning>

**Comportement recommandé à la réception de `return_url` :**

1. Afficher un écran intermédiaire « Vérification du paiement en cours… »
2. Appeler votre backend, qui appelle `confirm` avec le token stocké à la création
3. Afficher le résultat définitif (succès / échec / en attente)

## Pages associées

* [Créer une facture](/api-paiement/payin-redirect/creer-facture) — obtenir l'URL de paiement
* [Vérifier le statut](/api-paiement/payin-redirect/verifier-statut) — confirmer le paiement après redirection
* [Pièges courants](/api-paiement/payin-redirect/pieges-courants) — iframe, customer vide et autres erreurs fréquentes
* [Intégration mobile](/api-paiement/payin-redirect/integration-mobile) — React Native, Flutter, iOS, Android
