Rect native: getting OR_BIBED_15 error for Google Pay in webview

We are using react-nattive-webview to display Google Pay button. We are using instructions: Embedded checkout - Payment API reference . When the button is clicked, OR_BIBED_15 error is raised.

import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useRef, useState } from "react";
import { Linking, StyleSheet, View } from "react-native";
import { Text } from "react-native-paper";
import WebView from "react-native-webview";
import i18n from "src/config/language";
import { Store, useStore } from "src/store/store";
import Screen from "../atoms/Screen";

interface PaymentWebViewParams {
  paymentUrl?: string;
  amount?: number;
  onPaymentSuccess?: () => void;
  onPaymentError?: () => void;
}

const Options = {
  options: {
    methods: ["card"],
    methods_disabled: [],
    card_icons: ["mastercard", "visa"],
    fields: false,
    full_screen: false,
    button: true,
    hide_title: true,
    hide_link: true,
    email: false,
    theme: {
      type: "light",
      preset: "reset",
    },
  },
  params: {
    merchant_id: 1549901,
    required_rectoken: "y",
    currency: "GEL",
    amount: 500,
  },
  css_variable: {
    main: "#7d8ff8",
    card_bg: "#353535",
    card_shadow: "#9ADBE8",
  },
};

const html = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Payment</title>
  <link rel="stylesheet" href="https://pay.fondy.eu/latest/checkout-vue/checkout.css">
  <style>
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }
    
    #checkout-container {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 16px;
      box-sizing: border-box;
    }
  </style>
</head>
<body>
  <div id="checkout-container"></div>
  
  <script src="https://pay.flitt.com/latest/checkout-vue/checkout.js"></script>
  <script>
    // Payment configuration
  
    const Options = ${JSON.stringify(Options)};

    // Initialize checkout when DOM is ready
    document.addEventListener('DOMContentLoaded', function() {
      if (typeof checkout === 'function') {
        checkout("#checkout-container", Options);
      } else {
        console.error('Checkout function not available');
      }
    });

    // Handle payment success/error callbacks
    window.addEventListener('message', function(event) {
      if (event.data.type === 'payment_success') {
        window.ReactNativeWebView && window.ReactNativeWebView.postMessage(JSON.stringify({
          type: 'payment_success',
          data: event.data
        }));
      } else if (event.data.type === 'payment_error') {
        window.ReactNativeWebView && window.ReactNativeWebView.postMessage(JSON.stringify({
          type: 'payment_error',
          data: event.data
        }));
      }
    });
  </script>
</body>
</html>
`;

export default function PaymentWebView({
  navigation,
  route,
}: NativeStackScreenProps<any, "PaymentWebView">) {
  const { amount, onPaymentSuccess, onPaymentError } =
    route.params as PaymentWebViewParams & { amount?: number };
  const { setError, setMessage } = useStore((state: Store) => state);
  const webViewRef = useRef(null);
  const [loading, setLoading] = useState(true);

  // Generate dynamic HTML with the payment amount
  const generateHTML = () => {
    return html.replace("amount: 500", `amount: ${amount || 500}`);
  };

  const handleLoadStart = () => {
    setLoading(true);
  };

  const handleLoadEnd = () => {
    setLoading(false);
  };

  const handleError = (syntheticEvent: any) => {
    const { nativeEvent } = syntheticEvent;
    console.warn("WebView error: ", nativeEvent);
    setError(i18n.t("purchaseError"));
    navigation.goBack();
  };

  return (
    <Screen style={styles.container}>
      <View style={styles.webviewContainer}>
        <WebView
          ref={webViewRef}
          // source={{ uri: "https://codepen.io/flitt/pen/BaggpVw" }}
          source={{ html: generateHTML(), baseUrl: "https://google.com" }}
          style={styles.webview}
          onLoadStart={handleLoadStart}
          onLoadEnd={handleLoadEnd}
          onError={handleError}
          // onMessage={handleMessage}
          javaScriptEnabled={true}
          domStorageEnabled={true}
          startInLoadingState={true}
          scalesPageToFit={true}
          sharedCookiesEnabled
          thirdPartyCookiesEnabled
          allowsBackForwardNavigationGestures={true}
          injectedJavaScript={`(function() {
            const open = window.open;
            window.open = function(url) {
              window.ReactNativeWebView.postMessage(JSON.stringify({ t:'OPEN', url }));
              return null;
            };
            document.addEventListener('click', function(e){
              const a = e.target.closest('a[target="_blank"]');
              if (a && a.href) { e.preventDefault();
                window.ReactNativeWebView.postMessage(JSON.stringify({ t:'OPEN', url:a.href }));
              }
            }, true);
          })();
          true;`}
          setSupportMultipleWindows
          onCreateWindow={(e) => {
            Linking.openURL(e.nativeEvent.targetUrl);
            return false;
          }}
          onMessage={(e) => {
            try {
              const m = JSON.parse(e.nativeEvent.data);
              if (m.t === "OPEN" && m.url) Linking.openURL(m.url);
            } catch {}
          }}
        />
      </View>

      {loading && (
        <View style={styles.loadingContainer}>
          <Text style={styles.loadingText}>{i18n.t("loading")}</Text>
        </View>
      )}
    </Screen>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5E5",
    backgroundColor: "#FFFFFF",
  },
  headerText: {
    fontSize: 18,
    fontWeight: "600",
    color: "#20232B",
    textAlign: "center",
  },
  webviewContainer: {
    flex: 1,
  },
  webview: {
    flex: 1,
  },
  loadingContainer: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "rgba(255, 255, 255, 0.8)",
  },
  loadingText: {
    fontSize: 16,
    color: "#636B74",
  },
});

Please check:

  1. You are using at least react-nattive-webview of version v13.15.0 (in this version support for enabling the Payment Request API was added).
    Also please read this cases:

    Release v13.15.0 · react-native-webview/react-native-webview · GitHub

    Using Android WebView  |  Google Pay API for Android  |  Google for Developers

    Google Pay Not Working · Issue #2278 · react-native-webview/react-native-webview · GitHub

  2. Add flags to your webview:

paymentRequestEnabled         // <-- NEW in 13.15.0 (default false)         
javaScriptEnabled
domStorageEnabled
setSupportMultipleWindows
javaScriptCanOpenWindowsAutomatically
onOpenWindow={(e) => setPopupUrl(e.nativeEvent?.targetUrl ?? null)}
onCreateWindow={(e) => setPopupUrl(e.nativeEvent?.targetUrl ?? null)}
  1. Please use this example as a demo

GooglePayWebChromeClient.kt

package com.flitt

import android.app.Activity
import android.app.AlertDialog
import android.os.Message
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebChromeClient
import com.reactnativecommunity.webview.RNCWebChromeClient
import com.reactnativecommunity.webview.RNCWebView

class GooglePayWebChromeClient(
    private val activity: Activity,
    webView: RNCWebView
) : RNCWebChromeClient(webView) {

    override fun onCreateWindow(
        view: WebView,
        isDialog: Boolean,
        isUserGesture: Boolean,
        resultMsg: Message
    ): Boolean {
        val newWebView = WebView(activity).apply {
            settings.apply {
                javaScriptEnabled = true
                setSupportMultipleWindows(true)
                domStorageEnabled = true
                javaScriptCanOpenWindowsAutomatically = true
                // Add these for better compatibility
                loadWithOverviewMode = true
                useWideViewPort = true
                databaseEnabled = true
            }
        }

        val dialog = AlertDialog.Builder(activity)
            .setView(newWebView)
            .setCancelable(true)
            .create()

        newWebView.webViewClient = WebViewClient()

        newWebView.webChromeClient = object : WebChromeClient() {
            override fun onCloseWindow(window: WebView) {
                dialog.dismiss()
            }
        }

        val transport = resultMsg.obj as WebView.WebViewTransport
        transport.webView = newWebView
        resultMsg.sendToTarget()

        dialog.show()
        return true
    }
}

emdededCheckout.tsx

import React, { useRef } from "react";
import { View, StyleSheet, Dimensions } from 'react-native';
import { WebView } from "react-native-webview";

const { height, width } = Dimensions.get("window");

const FlittCheckout = () => {
  const mainWebViewRef = useRef(null);

  const htmlContent = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width">
      <title>Flitt Checkout</title>
      <link rel="stylesheet" href="https://pay.flitt.com/latest/checkout-vue/checkout.css">
      <script src="https://pay.flitt.com/latest/checkout-vue/checkout.js"></script>
    </head>
    <body>
      <div id="checkout-container"></div>
      <script>
        const Options = {
          options: {
            methods: ['card','wallets'],
            methods_disabled: [],
            card_icons: ['mastercard', 'visa', 'maestro'],
            wallet_methods_enabled: ["google", "apple"],
            fields: false,
            active_tab: "card",
            full_screen: false,
            button: true,
            hide_title: true,
            hide_link: true,    
            email: false,
            theme: { type: "light", preset: "reset" }
          },
          params: {
            token: 'e92290ab1ba4ec95a4862530b3ac78d11e956d5e'
          }
        };
        checkout("#checkout-container", Options);
      </script>
    </body>
    </html>
  `;

  return (
    <View style={styles.container}>
      <WebView
        ref={mainWebViewRef}
        originWhitelist={["*"]}
        source={{ html: htmlContent, baseUrl: "https://pay.flitt.com" }}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        setSupportMultipleWindows={true}
        javaScriptCanOpenWindowsAutomatically={true}
        style={styles.webview}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#fff" },
  webview: { flex: 1, width, height },
});

export default FlittCheckout;

CustomWebViewPackage.kt

package com.flitt

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class CustomWebViewPackage : ReactPackage {
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return listOf(CustomWebViewManager(reactContext))
    }

    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return emptyList()
    }
}

CustomWebViewManager.kt

package com.flitt

import android.os.Handler
import android.os.Looper
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativecommunity.webview.RNCWebViewManager
import com.reactnativecommunity.webview.RNCWebViewWrapper

class CustomWebViewManager(private val appContext: ReactApplicationContext) : RNCWebViewManager() {

    override fun getName() = "RNCWebView"

    override fun createViewInstance(reactContext: ThemedReactContext): RNCWebViewWrapper {
        val wrapper = super.createViewInstance(reactContext)

        Handler(Looper.getMainLooper()).post {
            reactContext.currentActivity?.let { activity ->
                wrapper.webView?.let { webView ->
                    wrapper.webView?.webChromeClient = GooglePayWebChromeClient(activity, webView)
                    android.util.Log.d("CustomWebViewManager", "Set GooglePayWebChromeClient")
                }
            }
        }

        return wrapper
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:networkSecurityConfig="@xml/network_security_config"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
      android:usesCleartextTraffic="true"
      android:supportsRtl="true">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
    </application>
</manifest>

MainApplication.kt

package com.flitt

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import org.devio.rn.splashscreen.SplashScreenReactPackage
//import com.flitt.CustomWebViewPackage  // Add this line

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              // Packages that cannot be autolinked yet can be added manually here, for example:
              // add(MyReactNativePackage())
                 add(CustomWebViewPackage())
                 add(SplashScreenReactPackage())
            }

        override fun getJSMainModuleName(): String = "index"

        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      }

  override val reactHost: ReactHost
    get() = getDefaultReactHost(applicationContext, reactNativeHost)

  override fun onCreate() {
    super.onCreate()
    loadReactNative(this)
  }
}