Mobile App Integration
Special attention is required if Payment capture page is embedded inside a mobile app
For iOS
Comparison of iOS WebView
| Capability | WKWebView | SFSafariViewController |
|---|---|---|
| Embed inside your own UI | Yes | No (modal browser) |
Receive postMessage from Payment capture page in native code | Yes | No — use successUrl / cancelUrl redirects |
| Apple Pay | Yes (iOS 16+) | Yes (all supported iOS versions) |
| Google Pay | Yes | Limited / not supported officially |
| Initial setup effort | Moderate | Minimal |
SFSafariViewController
The simplest integration. iOS provides the full browser environment, so Apple Pay, 3DS and external scheme redirects all work without extra setup. successUrl and cancelUrl is mandatory to redirects users back to the app.
https://hosted-global-sandbox.ezypay.com/paymentmethod/embed?token=eyJhbGciOi...&countryCode=AU&successUrl=https%3A%2F%2Fexample.com%2Fsuccess&cancelUrl=https%3A%2F%2Fexample.com%2Fcancel
import SafariServices
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = false
config.barCollapsingEnabled = false
let vc = SFSafariViewController(url: pcpURL, configuration: config)
vc.dismissButtonStyle = .done
present(vc, animated: true)When Payment capture page finishes or is cancelled, it redirects to the successUrl / cancelUrl configured. Use a custom URL scheme or universal link to return control to your app, and handle it in your AppDelegate / SceneDelegate.
WKWebView
Use this integration when Payment capture page needs to be rendered inside a native screen. Apple Pay within WKWebView requires iOS 16 or later.
Required Setup
-
Append
GOOGLE_PAY_SUPPORTEDto the WKWebView user agent so that Google Pay browser compatibility check works. -
Set both
WKNavigationDelegateandWKUIDelegate. These delegates must:- Forward non-HTTP(S) navigations (
tel:,mailto:, wallet schemes, bank deep links) toUIApplication.shared.open(_:). - Handle window.open() with a hybrid rule:
- Google Pay popups (and about:blank bootstrap popups) stay inside a WKWebView overlay.
- Other popup URLs (for example many 3DS flows) open in SFSafariViewController.
- Forward non-HTTP(S) navigations (
-
Install a JavaScript bridge to receive the payment method token via
window.postMessage(...). Bridge behavior (particularly with Apple Pay) can vary by iOS version and device
Minimal WKWebView Setup
import WebKit
final class PaymentWebViewCoordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
private weak var mainPCPWebView: WKWebView?
}
let coordinator = PaymentWebViewCoordinator()
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
let webView = WKWebView(frame: .zero, configuration: config)
let currentUserAgent = webView.value(forKey: "userAgent") as? String ?? ""
coordinator.mainPCPWebView = webView // keep a reference for popup close handling
webView.customUserAgent = currentUserAgent + " GOOGLE_PAY_SUPPORTED"
webView.navigationDelegate = coordinator
webView.uiDelegate = coordinator
webView.load(URLRequest(url: pcpURL))Forwarding Non-Web Schemes
func webView(_ webView: WKWebView,
decidePolicyFor action: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = action.request.url,
let scheme = url.scheme?.lowercased(),
scheme != "https", scheme != "http", scheme != "about", !scheme.isEmpty {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}Handling window.open (3DS Popups)
window.open (3DS Popups)When Payment capture page opens a popup, use WKWebView overlay for Google Pay and about:blank
bootstrap popups, and use SFSafariViewController for other popup URLs.
func webView(_ webView: WKWebView,
createWebViewWith configuration: WKWebViewConfiguration,
for action: WKNavigationAction,
windowFeatures: WKWindowFeatures) -> WKWebView? {
guard action.targetFrame == nil else { return nil }
if shouldUseOverlayPopup(action.request.url) {
let popup = WKWebView(frame: webView.frame, configuration: configuration)
popup.translatesAutoresizingMaskIntoConstraints = false
popup.uiDelegate = webView.uiDelegate
webView.addSubview(popup)
NSLayoutConstraint.activate([
popup.leadingAnchor.constraint(equalTo: webView.leadingAnchor),
popup.topAnchor.constraint(equalTo: webView.topAnchor),
popup.trailingAnchor.constraint(equalTo: webView.trailingAnchor),
popup.bottomAnchor.constraint(equalTo: webView.bottomAnchor),
])
return popup
}
guard let url = action.request.url else { return nil }
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let root = scene.windows.first?.rootViewController {
root.present(SFSafariViewController(url: url), animated: true)
}
return nil
}
func webViewDidClose(_ webView: WKWebView) {
// Remove popup overlays only — keep the main PCP embed page.
guard webView !== mainPCPWebView else { return }
webView.removeFromSuperview()
}
private func shouldUseOverlayPopup(_ url: URL?) -> Bool {
guard let url else { return true } // nil popup URL
if url.scheme?.lowercased() == "about" { return true } // about:blank
let host = url.host?.lowercased() ?? ""
return host == "pay.google.com"
|| host.hasSuffix(".pay.google.com")
|| host == "payments.google.com"
|| host.hasSuffix(".payments.google.com")
|| host == "accounts.google.com"
}Closing overlay popups
Overlay popups (Google Pay and about:blank bootstrap popups) are closed by implementing webViewDidClose. When the popup calls window.close(), the popup is removed from the view hierarchy and the main Payment capture page WKWebView is left in place.
Popups opened in SFSafariViewController do not need webViewDidClose — the user dismisses Safari with the Done button.
Receiving messages from PCP
After a successful payment method capture, PCP delivers the result to the host app via window.postMessage(...). A JavaScript bridge must be installed in the WKWebView so the native app can receive these messages.
Apple Pay Specific
-
Apple Pay merchant registration and domain verification are handled by Ezypay
No custom Apple Pay Merchant ID orcom.apple.developer.in-app-paymentsentitlement is required -
Payment capture page must be served from a verified domain.
-
Device requirements:
- A card is provisioned in Apple Wallet
- The user is signed in to iCloud
-
Apple Pay is supported in
WKWebViewfrom iOS 16 onward
UseSFSafariViewControllerfor earlier versions
Troubleshooting Apple Pay
- Use a new access token when loading the page.
- If Google Pay button does not appears ensures Mobile Safari user agent is correctly set
- After tapping the Apple Pay button and authorising in the sheet, tap Create. The native
WKScriptMessageHandlerreceives a message with"type": "success"and"purpose": "PAYMENT_METHOD_TOKEN". If it doesn't arrive, confirm the bridge is being installed beforewindow.postMessage({actionType: "create"})is sent.
For Android
Comparison of Android WebView
| Capability | WebView | Custom Tabs |
|---|---|---|
| Embed inside your own UI | Yes | No (modal browser) |
Receive postMessage from Payment capture page in native code | Yes | No — use successUrl / cancelUrl redirects |
| Apple Pay | No | No |
| Google Pay | Yes | Yes |
| Initial setup effort | Moderate | Minimal |
WebView
-
System requirements
- Google Play services version 25.18.30 or higher
- Android System WebView (Chrome WebView) version 137 or higher
-
Apply required changes to the Android application. Follow Google’s official documentation:
https://developers.google.com/pay/api/android/guides/recipes/using-android-webview-
Update build dependency
https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#add-build-dependency -
Update
AndroidManifest.xml
https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#add-android-manifest-queries-tags -
Enable JavaScript and Payment Request API in the WebView
https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#enable-payment-request-api
-
-
For Google Pay production usage:
- Register the application in the Google Pay & Wallet Console
- Follow the official documentation for publishing and approval:
https://developers.google.com/pay/api/android/guides/test-and-deploy/publish-your-integration#integrate-android-app.
Troubleshooting WebView
If opening the Google Pay sheet produces errors:
-
OR_BIBED_15
Indicates that required setup steps for enabling Google Pay in WebView may be incomplete. Verify that device requirements and WebView configuration steps have been completed. -
OR_BIBED_11
Occurs only in the Google Pay production environment. Verify that the application is registered and approved in the Google Pay & Wallet Console. -
Application not visible in Google Pay & Wallet Console
Ensure that the logged-in Google account has access to the application in the Google Play Console.
Custom Tabs
Apply the following changes in the Android application (Kotlin example). successUrl and cancelUrl is mandatory to redirects users back to the app.
https://hosted-global-sandbox.ezypay.com/paymentmethod/embed?token=eyJhbGciOi...&countryCode=AU&successUrl=https%3A%2F%2Fexample.com%2Fsuccess&cancelUrl=https%3A%2F%2Fexample.com%2Fcancel
1. Update build dependency
build.gradle.kts
dependencies {
implementation("androidx.browser:browser:<latest>")
}libs.versions.toml
[versions]
browser = "1.8.0"
[libraries]
androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" }2. Update AndroidManifest.xml
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required because PCP is opened through HTTPS -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Recommended for browser intent handling -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application android:theme="@style/Theme.YourApp">
<activity android:name=".MainActivity" android:exported="true">
<!-- Launcher entry point -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Required for deep link return (success/cancel URLs) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Must match successUrl/cancelUrl -->
<data android:scheme="androidmobileapp" android:host="checkout" />
</intent-filter>
</activity>
</application>
</manifest>3. Launch Payment capture page URL with Custom Tabs
import android.content.Context
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
fun openPcpInCustomTab(context: Context, pcpUrl: String) {
val uri = Uri.parse(pcpUrl)
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
customTabsIntent.launchUrl(context, uri)
}