> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpaycheckout.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile App Integration

> Learn how to embed your webpage inside a mobile WebView and properly handle deep links across different frameworks.

If you're embedding our hosted webpage inside a mobile app using a **WebView**, you must ensure that **external deep links are properly handled** by opening them in their respective apps.

This guide walks you through the setup for major mobile frameworks:

***

## <Icon icon="compass" iconType="solid" /> Framework-Specific Setup

<Tabs>
  <Tab title="Flutter (Android & iOS)">
    ### <Icon icon="tools" iconType="solid" /> Dependencies

    ```bash theme={null}
    flutter pub add webview_flutter url_launcher
    ```

    ### <Icon icon="file-code" iconType="solid" /> Code Sample

    ```dart theme={null}
    if (checkoutUrl != null) {
      controller = WebViewController()
        ..setJavaScriptMode(JavaScriptMode.unrestricted)
        ..setNavigationDelegate(
          NavigationDelegate(
            onNavigationRequest: (NavigationRequest request) async {
              if (request.url.startsWith("myapp://payment-callback")) {
                _handleCallbackUrl(request.url);
                return NavigationDecision.prevent;
              }
              Uri url = Uri.parse(request.url);
              if (url.scheme != "http" && url.scheme != "https") {
                await launchUrl(url, mode: LaunchMode.externalApplication);
                return NavigationDecision.prevent;
              }
              return NavigationDecision.navigate;
            },
          ),
        )
        ..loadRequest(Uri.parse(checkoutUrl!));
    } else {
      throw Exception("Failed to get checkout URL");
    }
    ```
  </Tab>

  <Tab title="Android Native (Java/Kotlin)">
    ### <Icon icon="file-code" iconType="solid" /> WebViewClient Code

    ```kotlin theme={null}
    webView.webViewClient = object : WebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
            return if (url != null && (url.startsWith("http") || url.startsWith("https"))) {
                false
            } else {
                try {
                    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                    context.startActivity(intent)
                    true
                } catch (e: Exception) {
                    true
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS Native (Swift)">
    ### <Icon icon="file-code" iconType="solid" /> WKWebView Delegate Example

    ```swift theme={null}
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let url = navigationAction.request.url, !url.absoluteString.starts(with: "http") {
            if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
                decisionHandler(.cancel)
                return
            }
        }
        decisionHandler(.allow)
    }
    ```
  </Tab>

  <Tab title="React Native">
    ### <Icon icon="tools" iconType="solid" /> Install Dependencies

    ```bash theme={null}
    npm install react-native-webview
    ```

    ### <Icon icon="file-code" iconType="solid" /> WebView Setup

    ```tsx theme={null}
    import { Linking } from 'react-native';
    import { WebView } from 'react-native-webview';

    <WebView
      source={{ uri: 'https://yourdomain.com' }}
      onShouldStartLoadWithRequest={(request) => {
        const url = request.url;
        if (!url.startsWith('http')) {
          Linking.openURL(url).catch(err => console.warn('Failed to open URL:', err));
          return false;
        }
        return true;
      }}
    />
    ```
  </Tab>
</Tabs>

***

## <Icon icon="keyboard" iconType="solid" /> WebView Keyboard Handling

When embedding the checkout page inside a mobile WebView, the WebView often shrinks when the keyboard opens, which results in a poor user experience. Input fields or the Pay button may get hidden, forcing the user to scroll in awkward ways.

To ensure a smooth experience, configure the WebView so that the keyboard pans over the WebView instead of shrinking it. This allows the WebView to remain in place, keeps input fields properly focused, and ensures that critical actions like completing payment stay visible.

For Example in **Flutter**, you can configure the Scaffold to prevent the WebView from shrinking.

```dart theme={null}
Scaffold(
  resizeToAvoidBottomInset: false, // Prevents WebView from shrinking
  body: WebViewWidget(controller: controller),
)
```

***

## <Icon icon="credit-card" iconType="solid" /> Google Pay Integration

### <Icon icon="chrome" iconType="brands" /> Chrome Custom Tab (CCT) Integration

Chrome Custom Tabs provide a seamless way to display web content within your Android app while maintaining the native look and feel. **For Google Pay integration on Android, we strongly recommend using CCT instead of WebView** for better performance, security, and native user experience.

<Tabs>
  <Tab title="Flutter (Android & iOS)">
    #### <Icon icon="tools" iconType="solid" /> Dependencies

    ```bash theme={null}
    flutter pub add flutter_inappwebview url_launcher
    ```

    #### <Icon icon="file-code" iconType="solid" /> Code Sample

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

    // Launch payment URL in Chrome Custom Tab (Android) or Safari (iOS)
    Future<void> launchPaymentUrl(String paymentUrl) async {
      final Uri uri = Uri.parse(paymentUrl);
      
      if (await canLaunchUrl(uri)) {
        await launchUrl(
          uri,
          mode: LaunchMode.externalApplication,
          webViewConfiguration: const WebViewConfiguration(
            enableJavaScript: true,
            enableDomStorage: true,
          ),
        );
      } else {
        throw Exception('Could not launch payment URL');
      }
    }
    ```
  </Tab>

  <Tab title="Android Native (Java/Kotlin)">
    #### <Icon icon="tools" iconType="solid" /> Dependencies

    ```kotlin theme={null}
    // build.gradle
    implementation 'androidx.browser:browser:1.7.0'
    ```

    #### <Icon icon="file-code" iconType="solid" /> Code Sample

    ```kotlin theme={null}
    import androidx.browser.customtabs.CustomTabsIntent
    import androidx.browser.customtabs.CustomTabColorSchemeParams
    import androidx.core.content.ContextCompat

    val customTabsIntent = CustomTabsIntent.Builder()
        .setColorSchemeParams(
            CustomTabColorSchemeParams.Builder()
                .setToolbarColor(ContextCompat.getColor(this, R.color.primary))
                .build()
        )
        .setShowTitle(true)
        .setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left)
        .setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right)
        .build()

    // Launch the payment URL in CCT
    customTabsIntent.launchUrl(this, Uri.parse(paymentUrl))
    ```
  </Tab>

  <Tab title="iOS Native (Swift)">
    #### <Icon icon="file-code" iconType="solid" /> Code Sample

    ```swift theme={null}
    import SafariServices

    func launchPaymentURL(_ urlString: String) {
        guard let url = URL(string: urlString) else { return }
        
        let safariVC = SFSafariViewController(url: url)
        safariVC.preferredBarTintColor = UIColor.systemBlue
        safariVC.preferredControlTintColor = UIColor.white
        
        present(safariVC, animated: true)
    }
    ```
  </Tab>

  <Tab title="React Native">
    #### <Icon icon="tools" iconType="solid" /> Install Dependencies

    ```bash theme={null}
    npm install react-native-inappbrowser-reborn
    ```

    #### <Icon icon="file-code" iconType="solid" /> Code Sample

    ```tsx theme={null}
    import { InAppBrowser } from 'react-native-inappbrowser-reborn';

    const launchPaymentURL = async (url: string) => {
      try {
        const result = await InAppBrowser.open(url, {
          // iOS options
          dismissButtonStyle: 'cancel',
          preferredBarTintColor: '#453AA4',
          preferredControlTintColor: 'white',
          readerMode: false,
          animated: true,
          modalPresentationStyle: 'fullScreen',
          modalTransitionStyle: 'coverVertical',
          modalEnabled: true,
          enableBarCollapsing: false,
          // Android options
          showTitle: true,
          toolbarColor: '#453AA4',
          secondaryToolbarColor: 'black',
          navigationBarColor: 'black',
          navigationBarDividerColor: 'white',
          enableUrlBarHiding: true,
          enableDefaultShare: true,
          forceCloseOnRedirection: false,
          animations: {
            startEnter: 'slide_in_right',
            startExit: 'slide_out_left',
            endEnter: 'slide_in_left',
            endExit: 'slide_out_right',
          },
        });
      } catch (error) {
        console.error('Error launching payment URL:', error);
      }
    };
    ```
  </Tab>
</Tabs>

### <Icon icon="globe" iconType="solid" /> WebView Integration with Google Pay

**Note**: While CCT is the recommended approach for Google Pay on Android, if you must use WebView instead, refer to the [Google Pay Android WebView guide](https://developers.google.com/pay/api/android/guides/recipes/using-android-webview) for complete integration details.

***

## Android & iOS Requirements

### Android

If your app wants to check if other apps are installed or can handle specific intents (like deep links or custom schemes) before launching them, you need to declare those intents inside the `<queries>` tag in your app's `AndroidManifest.xml`.

This is mandatory starting Android 11 (API 30) for privacy reasons.

Without this, calls like `PackageManager.queryIntentActivities()` or launching those intents may not work as expected.

### iOS

If your app wants to check whether other apps are installed that support certain custom URL schemes before opening them (using `canOpenURL`), you must declare those schemes inside the `LSApplicationQueriesSchemes` array in your `Info.plist`.

Without this, `canOpenURL` will always return false for those schemes (except for some system schemes).

This is required since iOS 9 for privacy reasons.

### Summary

| Platform | Requirement                                                              |
| -------- | ------------------------------------------------------------------------ |
| Android  | Declare intent filters inside `<queries>` tag in `AndroidManifest.xml`   |
| iOS      | Declare URL schemes inside `LSApplicationQueriesSchemes` in `Info.plist` |

### Example

#### Android (AndroidManifest.xml)

```xml theme={null}
<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="cashme" />
    </intent>
</queries>
```

#### iOS (Info.plist)

```xml theme={null}
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>cashme</string>
</array>
```

***

<Warning> If deep links are not handled as described, your app may not open apps like Cash App or klarna from within the WebView.</Warning>
