Skip to main content

Accept payments in a mobile app WebView

Your app is native (iOS, Android, Flutter, React Native) and you want to take payments with the Hosted Payment Page. Your backend creates the payment, and your app shows its redirectUrl. This page tells you how to show that page inside the app, how to bring the customer back, and what changes in a WebView.

Recommended: open the checkout in the in-app browser​

Open the redirectUrl of the payment in the system in-app browser, not in an embedded WebView:

  • iOS: SFSafariViewController. Apple Pay works in it as it does in Safari.
  • Android: Custom Tabs. Every web platform feature of the browser is available in it, payment methods included.
  • Flutter: url_launcher with LaunchMode.inAppBrowserView, which opens one of the two above.
  • React Native (Expo): WebBrowser.openBrowserAsync, which opens one of the two above.

You do not configure anything for 3D Secure, Bizum, PayPal, Apple Pay or Google Pay. The checkout behaves as it does in the mobile browser.

import SafariServices

let safari = SFSafariViewController(url: redirectUrl)
safari.delegate = self
present(safari, animated: true)

// SFSafariViewControllerDelegate
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
// Ask your backend for the payment status.
}

Bring the customer back to your app​

When the payment ends, MONEI sends the customer to the completeUrl of the payment. A failed, canceled or expired payment goes to the failUrl instead, if you set one. MONEI adds the payment id and status to the URL. You can bring the customer back in two ways:

  • A return page with a button. Set completeUrl and failUrl to a page on your site. The page shows the result and a button that opens your app with a universal link (iOS), an app link (Android) or a custom scheme. Use a button, not an automatic redirect: a browser opens another app reliably only when the customer taps a link.
  • An authentication session that closes itself. Set completeUrl and failUrl to a URL with your app's custom scheme, for example myapp://checkout/complete. MONEI accepts custom-scheme URLs and redirects to them like any other URL. Open the checkout with an API that waits for that scheme. When the redirect happens, the session closes and gives your app the URL.
import AuthenticationServices

let session = ASWebAuthenticationSession(url: redirectUrl, callbackURLScheme: "myapp") { callbackURL, error in
// callbackURL is your completeUrl or failUrl with the payment id and status,
// or nil if the customer closed the sheet.
// Ask your backend for the payment status.
}
session.presentationContextProvider = self
// Without this, iOS asks the customer to allow the app to "sign in" with the payment site.
session.prefersEphemeralWebBrowserSession = true
session.start()

Get the result from your backend, not from the return URL. The status in the URL tells your app which screen to show, but it does not prove the payment succeeded. Your backend knows the status from the webhook to your callbackUrl or from Get Payment.

For the best wallet experience in a native app, you can also use the native SDKs: Apple Pay for iOS apps and Google Pay for Android apps.

If you use an embedded WebView​

An embedded WebView (WKWebView, Android WebView, webview_flutter, react-native-webview) is not a full browser. Load the redirectUrl in it. The checkout works, with these differences.

Detect the result​

The Hosted Payment Page sends no message to your app. When the payment ends, it navigates the WebView to your completeUrl or failUrl. Catch that navigation, close the WebView, and ask your backend for the payment status. This needs no JavaScript in the page, so Apple Pay keeps working.

// WKNavigationDelegate
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url,
url.absoluteString.hasPrefix(returnUrlPrefix) {
decisionHandler(.cancel)
// Close the WebView and ask your backend for the payment status.
return
}
decisionHandler(.allow)
}

returnUrlPrefix is the start of your completeUrl and failUrl. It can be a page on your site or a custom-scheme URL.

3D Secure​

When the card needs 3D Secure, the Hosted Payment Page takes the customer to their bank's verification page in the same WebView. When the customer finishes, the bank sends them back to the payment page. Nothing opens in a new window.

  • Allow JavaScript in the WebView.
  • Allow navigation to other HTTPS domains. Your navigation handler must only stop your return URL, because the bank's page is on the bank's domain.
  • Do not hide or reload the WebView while the card is verified. If the page is hidden during the first verification step, for example because the customer switches apps, the payment attempt is canceled and the customer must try again.

Apple Pay​

  • WKWebView supports Apple Pay. Apple disables it on pages where your app injects JavaScript (WKUserScript, evaluateJavaScript). If your app injects scripts into the checkout page, the customer gets no Apple Pay button.
  • MONEI shows the Apple Pay button only when the browser reports that Apple Pay is available (ApplePaySession.canMakePayments()). When the WebView does not support it, the button is not shown and the other methods stay available.

Google Pay​

  • iOS: Google Pay is not available in iOS WebViews. MONEI does not show the button there.
  • Android: Android WebView supports Google Pay through the Payment Request API. You must turn it on:
// implementation("androidx.webkit:webkit:1.14.0") or later
if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) {
WebSettingsCompat.setPaymentRequestEnabled(webView.settings, true)
}
AndroidManifest.xml
<queries>
<intent><action android:name="org.chromium.intent.action.PAY" /></intent>
<intent><action android:name="org.chromium.intent.action.IS_READY_TO_PAY" /></intent>
<intent><action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" /></intent>
</queries>

The customer's device needs Google Play services 25.18.30 or later and Android System WebView 137 or later. See Google's Android WebView guide. In Flutter, webview_flutter_android exposes the same setting (setPaymentRequestEnabled) and needs the same <queries> entries.

Bizum​

The customer confirms a Bizum payment in their banking app. The checkout page shows "complete the payment in your banking app" and checks the payment status until it changes. It does not open the banking app for the customer.

Keep the WebView open and do not reload it while the customer is in the banking app. When they come back to your app, the page shows the result.

Click to Pay​

Visa Click to Pay is not offered inside iOS embedded WebViews. Mastercard Click to Pay is still offered.

PayPal and new windows​

The PayPal button runs PayPal's own JavaScript SDK, which can open a new window. A WebView opens no new windows unless your app handles them:

  • iOS: implement webView(_:createWebViewWith:for:windowFeatures:) in your WKUIDelegate.
  • Android: call settings.setSupportMultipleWindows(true) and implement WebChromeClient.onCreateWindow.
  • Flutter: webview_flutter loads a new window in the same WebView. If PayPal does not complete, use the in-app browser.

A WebView does not open links with a non-HTTP scheme (for example intent:// or a bank app scheme). If a payment page sends the customer to another app, catch the link and open it with the system:

  • iOS: webView(_:decidePolicyFor:decisionHandler:) in your WKNavigationDelegate.
  • Android: WebViewClient.shouldOverrideUrlLoading. Use Intent.parseUri(url, Intent.URI_INTENT_SCHEME) for intent:// links.
  • Flutter: NavigationDelegate.onNavigationRequest, returning NavigationDecision.prevent after you launch the link.
  • React Native: onShouldStartLoadWithRequest, returning false after you open the link with Linking.openURL.

Common questions​

Can I use the Hosted Payment Page in an embedded WebView?​

Yes. Load the redirectUrl in the WebView, catch the navigation to your return URL, and read the other embedded WebView differences. The in-app browser needs less work and supports more payment methods.

Does the payment page send a message to my app?​

No. When you load the Hosted Payment Page directly in a WebView, it sends no postMessage to your app. It navigates to your completeUrl or failUrl. Catch that navigation instead.

Can completeUrl be a custom scheme like myapp://?​

Yes. completeUrl, failUrl and cancelUrl accept custom-scheme URLs, and MONEI redirects to them with the payment id and status added. Use them with an authentication session or a WebView that catches the navigation.

Why is the Apple Pay or Google Pay button missing in my app?​

MONEI shows a wallet button only when the browser reports that the wallet is available. In a WebView, check the Apple Pay and Google Pay conditions above, or open the checkout in the in-app browser.