Aceptar pagos en un WebView de una app móvil
Tu app es nativa (iOS, Android, Flutter, React Native) y quieres cobrar con la página de pago alojada. Tu backend crea el pago y tu app muestra su redirectUrl. Esta página explica cómo mostrar esa página dentro de la app, cómo devolver al cliente a la app y qué cambia en un WebView.
Recomendado: abre el checkout en el navegador integrado
Abre la redirectUrl del pago en el navegador integrado del sistema, no en un WebView embebido:
- iOS:
SFSafariViewController. Apple Pay funciona en él igual que en Safari. - Android: Custom Tabs. Todas las funciones web del navegador están disponibles, incluidos los métodos de pago.
- Flutter:
url_launcherconLaunchMode.inAppBrowserView, que abre uno de los dos anteriores. - React Native (Expo):
WebBrowser.openBrowserAsync, que abre uno de los dos anteriores.
No tienes que configurar nada para 3D Secure, Bizum, PayPal, Apple Pay ni Google Pay. El checkout funciona igual que en el navegador del móvil.
- iOS (Swift)
- Android (Kotlin)
- Flutter
- React Native (Expo)
import SafariServices
let safari = SFSafariViewController(url: redirectUrl)
safari.delegate = self
present(safari, animated: true)
// SFSafariViewControllerDelegate
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
// Pide a tu backend el estado del pago.
}
// implementation("androidx.browser:browser:<version>")
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
// Cuando tu activity vuelva al primer plano (onResume),
// pide a tu backend el estado del pago.
await launchUrl(Uri.parse(redirectUrl), mode: LaunchMode.inAppBrowserView);
// Cuando la app vuelva al primer plano, pide a tu backend el estado del pago.
import * as WebBrowser from 'expo-web-browser';
await WebBrowser.openBrowserAsync(redirectUrl);
// La promesa se resuelve cuando el cliente cierra el navegador.
// Pide a tu backend el estado del pago.
Devuelve al cliente a tu app
Cuando el pago termina, MONEI envía al cliente a la completeUrl del pago. Un pago fallido, cancelado o caducado va a la failUrl, si la configuras. MONEI añade a la URL el id y el status del pago. Puedes devolver al cliente de dos formas:
- Una página de retorno con un botón. Configura
completeUrlyfailUrlcon una página de tu web. La página muestra el resultado y un botón que abre tu app con un universal link (iOS), un app link (Android) o un esquema propio. Usa un botón, no una redirección automática: un navegador abre otra app de forma fiable solo cuando el cliente toca un enlace. - Una sesión de autenticación que se cierra sola. Configura
completeUrlyfailUrlcon una URL con el esquema propio de tu app, por ejemplomyapp://checkout/complete. MONEI acepta URLs con esquema propio y redirige a ellas como a cualquier otra URL. Abre el checkout con una API que espere ese esquema. Cuando llega la redirección, la sesión se cierra y entrega la URL a tu app.
- iOS (Swift)
- Android (Kotlin)
- Flutter
- React Native (Expo)
import AuthenticationServices
let session = ASWebAuthenticationSession(url: redirectUrl, callbackURLScheme: "myapp") { callbackURL, error in
// callbackURL es tu completeUrl o failUrl con el id y el estado del pago,
// o nil si el cliente cerró la ventana.
// Pide a tu backend el estado del pago.
}
session.presentationContextProvider = self
// Sin esto, iOS pide al cliente que permita a la app "iniciar sesión" con el sitio de pago.
session.prefersEphemeralWebBrowserSession = true
session.start()
<activity android:name=".CheckoutReturnActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="checkout" />
</intent-filter>
</activity>
Abre el checkout en Custom Tabs como arriba. CheckoutReturnActivity recibe la URL de retorno en intent.data. Pide ahí a tu backend el estado del pago.
Prueba este flujo en un dispositivo. Chrome puede bloquear el cambio automático a una app cuando el cliente no acaba de tocar nada, por ejemplo después de confirmar un Bizum. En ese caso, usa una página de retorno con un botón.
// flutter_web_auth_2
final result = await FlutterWebAuth2.authenticate(
url: redirectUrl,
callbackUrlScheme: 'myapp',
);
// result es tu completeUrl o failUrl con el id y el estado del pago.
// Pide a tu backend el estado del pago.
En Android, flutter_web_auth_2 también necesita su activity de retorno en AndroidManifest.xml. Consulta la configuración del plugin.
import * as WebBrowser from 'expo-web-browser';
const result = await WebBrowser.openAuthSessionAsync(redirectUrl, 'myapp://checkout', {
preferEphemeralSession: true
});
if (result.type === 'success') {
// result.url es tu completeUrl o failUrl con el id y el estado del pago.
}
// Pide a tu backend el estado del pago.
Obtén el resultado de tu backend, no de la URL de retorno. El status de la URL le dice a tu app qué pantalla mostrar, pero no prueba que el pago se haya completado. Tu backend conoce el estado por el webhook a tu callbackUrl o por Get Payment.
Para la mejor experiencia con monederos en una app nativa, también puedes usar los SDK nativos: Apple Pay en apps iOS y Google Pay en apps Android.
Si usas un WebView embebido
Un WebView embebido (WKWebView, WebView de Android, webview_flutter, react-native-webview) no es un navegador completo. Carga en él la redirectUrl. El checkout funciona, con estas diferencias.
Detecta el resultado
La página de pago alojada no envía ningún mensaje a tu app. Cuando el pago termina, navega el WebView a tu completeUrl o failUrl. Intercepta esa navegación, cierra el WebView y pide a tu backend el estado del pago. No necesitas JavaScript en la página, así que Apple Pay sigue funcionando.
- iOS (Swift)
- Android (Kotlin)
- Flutter
- React Native
// WKNavigationDelegate
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url,
url.absoluteString.hasPrefix(returnUrlPrefix) {
decisionHandler(.cancel)
// Cierra el WebView y pide a tu backend el estado del pago.
return
}
decisionHandler(.allow)
}
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
if (request.url.toString().startsWith(RETURN_URL_PREFIX)) {
// Cierra el WebView y pide a tu backend el estado del pago.
return true
}
return false
}
}
controller.setNavigationDelegate(NavigationDelegate(
onNavigationRequest: (request) {
if (request.url.startsWith(returnUrlPrefix)) {
// Cierra el WebView y pide a tu backend el estado del pago.
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
));
<WebView
source={{uri: redirectUrl}}
onShouldStartLoadWithRequest={(request) => {
if (request.url.startsWith(RETURN_URL_PREFIX)) {
// Cierra el WebView y pide a tu backend el estado del pago.
return false;
}
return true;
}}
/>
returnUrlPrefix es el inicio de tu completeUrl y tu failUrl. Puede ser una página de tu web o una URL con esquema propio.
3D Secure
Cuando la tarjeta necesita 3D Secure, la página de pago alojada lleva al cliente a la página de verificación de su banco en el mismo WebView. Cuando el cliente termina, el banco lo devuelve a la página de pago. No se abre nada en una ventana nueva.
- Permite JavaScript en el WebView.
- Permite la navegación a otros dominios HTTPS. Tu gestor de navegación solo debe detener tu URL de retorno, porque la página del banco está en el dominio del banco.
- No ocultes ni recargues el WebView mientras se verifica la tarjeta. Si la página se oculta durante el primer paso de verificación, por ejemplo porque el cliente cambia de app, el intento de pago se cancela y el cliente debe volver a intentarlo.
Apple Pay
WKWebViewadmite Apple Pay. Apple lo desactiva en las páginas donde tu app inyecta JavaScript (WKUserScript,evaluateJavaScript). Si tu app inyecta scripts en la página del checkout, el cliente no ve el botón de Apple Pay.- MONEI muestra el botón de Apple Pay solo cuando el navegador indica que Apple Pay está disponible (
ApplePaySession.canMakePayments()). Si el WebView no lo admite, el botón no aparece y los demás métodos siguen disponibles.
Google Pay
- iOS: Google Pay no está disponible en los WebView de iOS. MONEI no muestra el botón.
- Android: el
WebViewde Android admite Google Pay mediante la Payment Request API. Debes activarla:
// implementation("androidx.webkit:webkit:1.14.0") o posterior
if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) {
WebSettingsCompat.setPaymentRequestEnabled(webView.settings, true)
}
<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>
El dispositivo del cliente necesita Google Play services 25.18.30 o posterior y Android System WebView 137 o posterior. Consulta la guía de Android WebView de Google. En Flutter, webview_flutter_android ofrece el mismo ajuste (setPaymentRequestEnabled) y necesita las mismas entradas <queries>.
Bizum
El cliente confirma el pago con Bizum en su aplicación bancaria. La página del checkout le pide que complete el pago en su aplicación bancaria y consulta el estado del pago hasta que cambia. No abre la aplicación bancaria por el cliente.
Mantén el WebView abierto y no lo recargues mientras el cliente está en su aplicación bancaria. Cuando vuelva a tu app, la página muestra el resultado.
Click to Pay
Click to Pay de Visa no se ofrece en los WebView embebidos de iOS. Click to Pay de Mastercard sí se ofrece.
PayPal y ventanas nuevas
El botón de PayPal usa el SDK de JavaScript de PayPal, que puede abrir una ventana nueva. Un WebView no abre ventanas nuevas salvo que tu app las gestione:
- iOS: implementa
webView(_:createWebViewWith:for:windowFeatures:)en tuWKUIDelegate. - Android: llama a
settings.setSupportMultipleWindows(true)e implementaWebChromeClient.onCreateWindow. - Flutter:
webview_fluttercarga la ventana nueva en el mismo WebView. Si PayPal no se completa, usa el navegador integrado.
Enlaces a otras apps
Un WebView no abre enlaces con un esquema que no sea HTTP (por ejemplo intent:// o el esquema de una app bancaria). Si una página de pago envía al cliente a otra app, intercepta el enlace y ábrelo con el sistema:
- iOS:
webView(_:decidePolicyFor:decisionHandler:)en tuWKNavigationDelegate. - Android:
WebViewClient.shouldOverrideUrlLoading. UsaIntent.parseUri(url, Intent.URI_INTENT_SCHEME)para los enlacesintent://. - Flutter:
NavigationDelegate.onNavigationRequest, devolviendoNavigationDecision.preventdespués de abrir el enlace. - React Native:
onShouldStartLoadWithRequest, devolviendofalsedespués de abrir el enlace conLinking.openURL.
Preguntas frecuentes
¿Puedo usar la página de pago alojada en un WebView embebido?
Sí. Carga la redirectUrl en el WebView, intercepta la navegación a tu URL de retorno y revisa las demás diferencias del WebView embebido. El navegador integrado requiere menos trabajo y admite más métodos de pago.
¿La página de pago envía un mensaje a mi app?
No. Cuando cargas la página de pago alojada directamente en un WebView, no envía ningún postMessage a tu app. Navega a tu completeUrl o failUrl. Intercepta esa navegación.
¿Puede completeUrl usar un esquema propio como myapp://?
Sí. completeUrl, failUrl y cancelUrl aceptan URLs con esquema propio, y MONEI redirige a ellas añadiendo el id y el status del pago. Úsalas con una sesión de autenticación o con un WebView que intercepte la navegación.
¿Por qué no aparece el botón de Apple Pay o Google Pay en mi app?
MONEI muestra un botón de monedero solo cuando el navegador indica que el monedero está disponible. En un WebView, revisa las condiciones de Apple Pay y Google Pay, o abre el checkout en el navegador integrado.