Skip to main content

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.

On mobile, opening the payment URL in the system browser pulls the user out of the app and makes the return tricky to handle. A native WebView keeps the user inside the app, lets you automatically detect the end of the payment flow, and triggers verification without any extra action from the user.

Native WebView vs external browser

ApproachAutomatic returnUXRecommended
Native WebViewYes — URL-based detectionSmooth, stays in the app
SFSafariViewController / Chrome Custom TabsPartial — deep link requiredBriefly leaves the appAcceptable
System browserNoLeaves the app
SFSafariViewController (iOS) and Chrome Custom Tabs (Android) do not let you intercept redirects to your return URLs. They are not suitable for this use case without setting up a deep link.

React Native

Dependency: react-native-webview
npm install react-native-webview
# iOS only
npx pod-install
React Native
import { useState } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
import { WebView } from "react-native-webview";

export function PaymentWebView({ paymentUrl, onSuccess, onCancel, onError }) {
  const [loading, setLoading] = useState(true);

  const handleNavigationChange = (navState) => {
    const { url } = navState;

    if (url.startsWith("https://myapp.com/payment/success")) {
      onSuccess();
    } else if (url.startsWith("https://myapp.com/payment/cancel")) {
      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%" },
});
Replace https://myapp.com/payment/success and https://myapp.com/payment/cancel with the exact return_url and cancel_url passed at invoice creation.

Flutter

Dependency: webview_flutter
pubspec.yaml
dependencies:
  webview_flutter: ^4.0.0
Flutter
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

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

  @override
  State<PaymentWebView> createState() => _PaymentWebViewState();
}

class _PaymentWebViewState extends State<PaymentWebView> {
  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://myapp.com/payment/success")) {
            widget.onSuccess();
            return NavigationDecision.prevent;
          } else if (url.startsWith("https://myapp.com/payment/cancel")) {
            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)

iOS (Swift)
import UIKit
import WebKit

class PaymentViewController: 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://myapp.com/payment/success") {
            decisionHandler(.cancel)
            dismiss(animated: true) { self.onSuccess?() }
        } else if url.hasPrefix("https://myapp.com/payment/cancel") {
            decisionHandler(.cancel)
            dismiss(animated: true) { self.onCancel?() }
        } else {
            decisionHandler(.allow)
        }
    }
}
Presenting the ViewController:
iOS (Swift)
let vc = PaymentViewController()
vc.paymentUrl = URL(string: data.response_text)!
vc.onSuccess = {
    // dismiss is handled automatically, trigger verification
    self.verifyPayment()
}
vc.onCancel = {
    self.showCancellationMessage()
}
present(vc, animated: true)

Android (Kotlin / WebView)

Android (Kotlin)
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class PaymentActivity : 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://myapp.com/payment/success") -> {
                            setResult(RESULT_OK)
                            finish()
                            true
                        }
                        url.startsWith("https://myapp.com/payment/cancel") -> {
                            setResult(RESULT_CANCELED)
                            finish()
                            true
                        }
                        else -> false
                    }
                }
            }
            loadUrl(paymentUrl)
        }

        setContentView(webView)
    }
}
Starting the Activity and retrieving the result:
Android (Kotlin)
val launcher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    when (result.resultCode) {
        Activity.RESULT_OK -> verifyPayment()
        Activity.RESULT_CANCELED -> showCancellationMessage()
    }
}

val intent = Intent(this, PaymentActivity::class.java).apply {
    putExtra("payment_url", data.response_text)
}
launcher.launch(intent)
Do not forget to declare PaymentActivity in your AndroidManifest.xml:
AndroidManifest.xml
<activity android:name=".PaymentActivity" />

After return detection

The flow is identical across platforms:
1

Close the WebView

Pop or dismiss the WebView component to go back to the previous screen.
2

Show a verification screen

Display a loading indicator — “Verifying payment…” — while your backend is queried.
3

Call your backend

Your backend calls the confirm endpoint with the token stored at invoice creation.
4

Display the result

Based on the returned status: confirm the order (completed), inform the user (notcompleted), or invite them to wait (pending).
Detecting return_url is a UI signal, not proof of payment. A user can navigate manually to that URL. Always confirm the payment server-side before fulfilling the order.