Integration with mobile application
General Recommendation
⚠️ Using WebView for the payment process is not recommended. While it is technically possible to integrate the checkout inside a
WebView, it is not the preferred option.
It is recommended to use real system browsers, as they use the native engine, share cookies with the device browser, and correctly maintain user context during redirects (3DS, authentications, wallets).
Why not WebView?
- Restrictions on user actions (autoplay, popups, wallets).
- Limitations on cross-domain redirects.
- Inconsistent behavior across operating system versions.
- Greater maintenance surface area.
Integration with Expo / React Native
Once you have obtained a payment session from your backend service, you can start the payment process in the mobile application. Unlike native Android, in Expo you have three options for opening the processUrl, each with different levels of compatibility and user experience.
In-app browser (recommended)
Use expo-web-browser to open Chrome Custom Tabs (Android) or SFSafariViewController (iOS) without leaving the app. Supports 3DS and shares system cookies.
npx expo install expo-web-browser
import * as WebBrowser from 'expo-web-browser'
const result = await WebBrowser.openBrowserAsync(processUrl)
External browser
Opens the URL in the device's default browser. Use deep links with your returnUrl and cancelUrl so the app is notified when the process is complete.
import { Linking } from 'react-native'
await Linking.openURL(processUrl)
WebView (not recommended)
npx expo install react-native-webview
import { WebView } from 'react-native-webview'
;<WebView
source={{ uri: processUrl }}
javaScriptEnabled={true} // Required to run checkout scripts.
domStorageEnabled={true} // Maintains session state across redirects.
thirdPartyCookiesEnabled={true} // Required for 3DS and other authentication redirects.
mediaPlaybackRequiresUserAction={false} // Only required if your checkout needs to play video. Allows video autoplay without user interaction. On iOS, also requires the `muted` attribute on the video element.
allowsInlineMediaPlayback={true} // Use together with `mediaPlaybackRequiresUserAction={false}`.
/>
Integration example
Before integrating into your app, you can validate the checkout behavior using the following tools. All of them allow you to enter the session URL and open it in different navigation modes (WebView, external browser, and in-app browser).
The entry point is App.js. The in-app browser logic (recommended option) is in the inapp mode, which uses WebBrowser.openBrowserAsync.
An Expo Snack is also available for quick execution without any local setup.
Android integration (Kotlin)
Once you have obtained a payment session from your backend service, you can start the payment process in the mobile application. See the general recommendation to choose the most suitable navigation mode.
If you decide to use WebView, you can load the processUrl using the loadUrl method of the WebView class. Make sure to enable both JavaScript and Cookies for the payment session to function correctly — without them, the session will not allow the process to move forward.
Configure the WebView
These settings help optimize and personalize the browsing experience within the WebView.
It is important that you can identify the Return URL and the Cancel URL to be able to close the WebView once the payment process is complete.
import android.annotation.SuppressLint
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import com.placetopay.p2pr.utilities.Constants
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun CheckoutWebView(processUrl: String, returnUrl: String, cancelUrl: String, refreshWebView: Boolean, onFinished: () -> Unit) {
AndroidView(factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
clearCache(true)
CookieManager.getInstance().setAcceptCookie(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true)
}
webChromeClient = WebChromeClient()
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?, request: WebResourceRequest?
): Boolean {
if (request?.url.toString() == returnUrl || request?.url.toString() == cancelUrl)
onFinished()
return super.shouldOverrideUrlLoading(view, request)
}
}
loadUrl(processUrl)
}
}, update = {
it.loadUrl(processUrl)
if (refreshWebView) it.reload()
})
}