Mobile App Integration

For iOS

Comparison of iOS WebView

CapabilityWKWebViewSFSafariViewController
Embed inside your own UIYesNo (modal browser)
Receive postMessage from Payment capture page in native codeYesNo — use successUrl / cancelUrl redirects
Apple PayYes (iOS 16+)Yes (all supported iOS versions)
Google PayYesLimited / not supported officially
Initial setup effortModerateMinimal

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

  1. Set a Mobile Safari user agent

    Payment capture page relies on user-agent detection to enable Google Pay and Safari-based payment APIs.

  2. Set both WKNavigationDelegate and WKUIDelegate

    These delegates must:

    • Forward non-HTTP(S) navigations (tel:, mailto:, wallet schemes, bank deep links) to UIApplication.shared.open(_:).
    • Handle window.open() by presenting the URL in SFSafariViewController. Payment capture page uses popups for 3DS challenges and some redirect flows.
  3. Avoid injecting WKUserScript or registering WKScriptMessageHandler before page load when Apple Pay is required. Doing so disables ApplePaySession for that page load. If a JavaScript bridge is required, enable it only for flows that do not use Apple Pay.

Minimal WKWebView Setup

import WebKit

let safariUA = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " +
               "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 " +
               "Mobile/15E148 Safari/604.1"

let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []

let webView = WKWebView(frame: .zero, configuration: config)
webView.customUserAgent = safariUA
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)

func webView(_ webView: WKWebView,
             createWebViewWith configuration: WKWebViewConfiguration,
             for action: WKNavigationAction,
             windowFeatures: WKWindowFeatures) -> WKWebView? {

    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
}

Apple Pay Specific

  • Apple Pay merchant registration and domain verification are handled by Ezypay
    No custom Apple Pay Merchant ID or com.apple.developer.in-app-payments entitlement 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 WKWebView from iOS 16 onward
    Use SFSafariViewController for earlier versions


Troubleshooting Apple Pay

  • The page loads without JavaScript errors If "ApplePaySession is not available" appears ,ensure no WKUserScript was injected before page load
  • 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 WKScriptMessageHandler receives a message with "type": "success" and "purpose": "PAYMENT_METHOD_TOKEN". If it doesn't arrive, confirm the bridge is being installed before window.postMessage({actionType: "create"}) is sent.

For Android

Comparison of Android WebView

CapabilityWebViewCustom Tabs
Embed inside your own UIYesNo (modal browser)
Receive postMessage from Payment capture page in native codeYesNo — use successUrl / cancelUrl redirects
Apple PayNoNo
Google PayYesYes
Initial setup effortModerateMinimal

WebView

  1. System requirements

    1. Google Play services version 25.18.30 or higher
    2. Android System WebView (Chrome WebView) version 137 or higher
  2. Apply required changes to the Android application. Follow Google’s official documentation:
    https://developers.google.com/pay/api/android/guides/recipes/using-android-webview

    1. Update build dependency
      https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#add-build-dependency

    2. Update AndroidManifest.xml
      https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#add-android-manifest-queries-tags

    3. 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

  3. For Google Pay production usage:

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

<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)
}