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.
Sur mobile, ouvrir l’URL de paiement dans le navigateur système fait quitter l’application et rend le retour utilisateur difficile à gérer. La WebView native maintient l’utilisateur dans l’app, permet de détecter automatiquement la fin du flux de paiement et de déclencher la vérification sans action supplémentaire de l’utilisateur.
WebView native vs navigateur externe
| Approche | Retour automatique | UX | Recommandé |
|---|
| WebView native | Oui — détection par URL | Fluide, reste dans l’app | ✅ |
| SFSafariViewController / Chrome Custom Tabs | Partiel — deep link requis | Sort brièvement de l’app | Acceptable |
| Navigateur système | Non | Quitte l’app | ✗ |
SFSafariViewController (iOS) et Chrome Custom Tabs (Android) ne permettent pas d’intercepter les redirections vers vos URLs de retour. Ils ne conviennent pas pour ce cas d’usage sans mettre en place un deep link.
React Native
Dépendance : react-native-webview
npm install react-native-webview
# iOS uniquement
npx pod-install
import { useState } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
import { WebView } from "react-native-webview";
export function PaiementWebView({ paymentUrl, onSuccess, onCancel, onError }) {
const [loading, setLoading] = useState(true);
const handleNavigationChange = (navState) => {
const { url } = navState;
if (url.startsWith("https://monapp.com/paiement/succes")) {
onSuccess();
} else if (url.startsWith("https://monapp.com/paiement/annule")) {
onCancel();
}
};
return (
<View style={styles.container}>
<WebView
source={{ uri: paymentUrl }}
onNavigationStateChange={handleNavigationChange}
onLoadStart={() => setLoading(true)}
onLoadEnd={() => setLoading(false)}
onError={() => onError?.()}
javaScriptEnabled
/>
{loading && (
<ActivityIndicator style={styles.loader} size="large" color="#0066cc" />
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
loader: { position: "absolute", alignSelf: "center", top: "50%" },
});
Remplacez https://monapp.com/paiement/succes et https://monapp.com/paiement/annule par vos return_url et cancel_url exactes passées à la création de la facture.
Flutter
Dépendance : webview_flutter
dependencies:
webview_flutter: ^4.0.0
import 'package:flutter/material.dart';
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;
bool _loading = true;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (_) => setState(() => _loading = true),
onPageFinished: (_) => setState(() => _loading = false),
onNavigationRequest: (request) {
final url = request.url;
if (url.startsWith("https://monapp.com/paiement/succes")) {
widget.onSuccess();
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 Stack(
children: [
WebViewWidget(controller: _controller),
if (_loading)
const Center(child: CircularProgressIndicator()),
],
);
}
}
iOS (Swift / WKWebView)
import UIKit
import WebKit
class PaiementViewController: UIViewController, WKNavigationDelegate {
private var webView: WKWebView!
var paymentUrl: URL!
var onSuccess: (() -> Void)?
var onCancel: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: view.bounds)
webView.navigationDelegate = self
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
webView.load(URLRequest(url: paymentUrl))
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
let url = navigationAction.request.url?.absoluteString ?? ""
if url.hasPrefix("https://monapp.com/paiement/succes") {
decisionHandler(.cancel)
dismiss(animated: true) { self.onSuccess?() }
} else if url.hasPrefix("https://monapp.com/paiement/annule") {
decisionHandler(.cancel)
dismiss(animated: true) { self.onCancel?() }
} else {
decisionHandler(.allow)
}
}
}
Présenter le ViewController :
let vc = PaiementViewController()
vc.paymentUrl = URL(string: data.response_text)!
vc.onSuccess = {
// fermeture effectuée automatiquement, lancer la vérification
self.verifierPaiement()
}
vc.onCancel = {
self.afficherMessageAnnulation()
}
present(vc, animated: true)
Android (Kotlin / WebView)
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
class PaiementActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val paymentUrl = intent.getStringExtra("payment_url") ?: return finish()
val webView = WebView(this).apply {
settings.javaScriptEnabled = true
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") -> {
setResult(RESULT_OK)
finish()
true
}
url.startsWith("https://monapp.com/paiement/annule") -> {
setResult(RESULT_CANCELED)
finish()
true
}
else -> false
}
}
}
loadUrl(paymentUrl)
}
setContentView(webView)
}
}
Démarrer l’Activity et récupérer le résultat :
val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> verifierPaiement()
Activity.RESULT_CANCELED -> afficherMessageAnnulation()
}
}
val intent = Intent(this, PaiementActivity::class.java).apply {
putExtra("payment_url", data.response_text)
}
launcher.launch(intent)
N’oubliez pas de déclarer PaiementActivity dans votre AndroidManifest.xml :
<activity android:name=".PaiementActivity" />
Après la détection du retour
Le flux est identique quelle que soit la plateforme :
Fermer la WebView
Dépiler ou dismisser le composant WebView pour revenir à l’écran précédent.
Afficher un écran de vérification
Montrez un indicateur de chargement — « Vérification du paiement en cours… » — le temps d’interroger votre backend.
Appeler votre backend
Votre backend appelle l’endpoint confirm avec le token stocké à la création de la facture.
Afficher le résultat
Selon le status retourné : confirmez la commande (completed), informez l’utilisateur (notcompleted) ou invitez-le à patienter (pending).
La détection de return_url est un signal d’interface, pas une preuve de paiement. Un utilisateur peut naviguer manuellement vers cette URL. Confirmez toujours le paiement côté serveur avant d’honorer la commande.
Pages associées