Acepta pagos
El SDK tiene estas llamadas: isSupported, prepare, acceptPayment, presentEducation, isAccountLinked, linkAccount, readerState y readerStates. La API es @MainActor. Llámala desde el actor principal, por ejemplo desde una vista de SwiftUI.
Ejemplo
import MoneiTapToPay
// Una vez al iniciarse, y otra vez después de cada renovación del token. Sirve un token guardado que no
// ha caducado.
func startTapToPay() async {
guard TapToPay.isSupported else { return } // Oculta Tap to Pay en el iPhone en tu interfaz.
do {
let token = try await myServer.fetchPosToken() // Solo el campo "token".
try await TapToPay.prepare(token: token)
} catch {
// Consulta "Gestiona errores y resultados desconocidos".
}
}
// Cuando el cliente paga.
func pay(amountInCents: Int, orderId: String) async {
do {
let result = try await TapToPay.acceptPayment(
amount: amountInCents,
orderId: orderId,
callbackUrl: URL(string: "https://example.com/monei/webhook"))
switch result.status {
case .approved: showApproved(result)
case .declined: showDeclined(result)
@unknown default: showPending(orderId)
}
} catch TapToPayError.outcomeUnknown(let orderId) {
// No vuelvas a intentarlo. Busca este orderId en MONEI.
showPending(orderId)
} catch {
// Consulta "Gestiona errores y resultados desconocidos".
}
}
Comprueba la compatibilidad del dispositivo
TapToPay.isSupported es true cuando el dispositivo es compatible con Tap to Pay en el iPhone: un iPhone XS o posterior con iOS 18.5 o posterior. Cuando es false, oculta Tap to Pay en el iPhone en tu interfaz. En un dispositivo no compatible, todas las llamadas lanzan notSupported.
Prepara el lector
Llama a TapToPay.prepare(token:) una vez al iniciarse la app, y otra vez después de cada renovación del token.
prepareguarda el token y prepara el lector en segundo plano, para que el primer pago empiece más rápido.- El token se queda solo en memoria. Llama otra vez a
preparedespués de cada inicio. Sirve un token guardado que no ha caducado: así la configuración del lector empieza inmediatamente. prepareno muestra los términos de Apple.preparepide permiso de ubicación si el usuario todavía no ha respondido, y espera hasta que el usuario responde. No hay límite de tiempo para la respuesta. Si el usuario no permite el acceso a la ubicación,preparelanzalocationDenied. SiInfo.plistno tieneNSLocationWhenInUseUsageDescription,preparelanzasetupIncompleteinmediatamente (locationDeniedantes del SDK 0.5.0).- En el primer inicio, muestra tu pantalla previa sobre la ubicación antes de
prepare. Apple pide que expliques el permiso durante el registro, mucho antes del cobro. - El SDK necesita una ubicación para preparar el lector. El primer
prepareoacceptPaymentdespués del inicio espera hasta 15 segundos para obtenerla. Si el dispositivo no obtiene una ubicación en este tiempo, la llamada lanzapaymentFailed(code: .locationTimeout).
Cada vez que la app vuelve del segundo plano, el SDK prepara otra vez el lector. No necesitas llamar a prepare para esto. Una alerta del sistema o el Centro de control no provocan una nueva preparación, porque iOS mantiene la sesión del lector.
Cobra un pago
Llama a TapToPay.acceptPayment(amount:orderId:callbackUrl:) cuando el cliente paga.
| Parámetro | Tipo | Descripción |
|---|---|---|
amount | Int | El importe en céntimos de euro, mayor que 0. Por ejemplo, 1050 es 10,50 EUR. |
orderId | String | Tu propia referencia del pedido. No puede estar vacía. Usa un orderId nuevo para cada pedido. MONEI lo guarda con el pago, y tú lo usas para conciliar. |
callbackUrl | URL? | La URL a la que MONEI envía el webhook firmado con el resultado del pago. Defínela siempre. Consulta Confirma el pago con el webhook. |
Qué ocurre durante un pago:
- Si la cuenta todavía no aceptó los términos de Tap to Pay en el iPhone de Apple,
acceptPaymentlos muestra primero. El usuario debe aceptarlos. Apple pide que ofrezcas los términos también antes del cobro. Consulta Acepta los términos de Apple antes del cobro. - Apple muestra la pantalla de pago. El cliente acerca la tarjeta.
- MONEI procesa el pago.
acceptPaymentdevuelve unPaymentResulto lanza unTapToPayError.
Solo puede ejecutarse un pago a la vez. Durante un pago, acceptPayment lanza busy, así que desactiva tu botón de pago.
Si acceptPayment lanza outcomeUnknown, la tarjeta puede estar cobrada. No vuelvas a intentarlo, y no cobres otra vez con un orderId nuevo. Consulta Resultado desconocido.
Lee el resultado
acceptPayment devuelve un PaymentResult:
| Propiedad | Tipo | Descripción |
|---|---|---|
paymentId | String | El ID del pago en MONEI. |
status | Status | .approved o .declined. |
cardBrand | String? | El nombre de la red de la tarjeta en minúsculas, por ejemplo visa o amex. unknown para una red que el SDK no conoce. nil si la red no está disponible. |
last4 | String? | Los últimos 4 dígitos de la tarjeta, o nil. |
orderId | String | El orderId del pago. |
amount | Int | El importe en céntimos. |
currency | String | La divisa como código ISO 4217, por ejemplo EUR. |
statusCode | String? | El código de estado de MONEI, por ejemplo E000, o nil. |
statusMessage | String? | El texto del código de estado de MONEI, o nil. Un resultado rechazado también tiene el motivo, por ejemplo fondos insuficientes. |
authorizationCode | String? | El código de autorización de un resultado aprobado, o nil. Un resultado rechazado no tiene código de autorización. |
cardType | String? | credit, debit o prepaid, o nil. |
cardCountry | String? | El país de la tarjeta como código ISO 3166-1 alfa-2, por ejemplo ES, o nil. |
Solo status indica si el pago está aprobado. Usa los otros campos para mostrar el resultado en tu interfaz. El webhook firmado sigue siendo el único resultado de pago fiable.
Un resultado con el estado .declined no es un error. La tarjeta se leyó y el emisor rechazó el pago. El pago está en MONEI con su paymentId.
Muestra el motivo del rechazo
Un resultado rechazado tiene el motivo en statusMessage. Muéstralo al cliente. No necesitas una llamada a MONEI para obtenerlo. statusMessage puede ser nil. En ese caso, muestra un texto general.
func showDeclined(_ result: PaymentResult) {
// statusMessage puede ser nil. En ese caso, muestra un texto general.
let reason = result.statusMessage ?? "La tarjeta se ha rechazado."
showMessage("Pago rechazado: \(reason)")
}
Confirma el pago con el webhook
Pasa siempre callbackUrl. MONEI envía a esta URL un webhook firmado con el resultado del pago. El webhook firmado es el único resultado de pago fiable. Usa el resultado de la app solo para la interfaz.
- Verifica la firma en tu servidor antes de completar el pedido. Consulta Verificar la firma.
- La
callbackUrldebe usarhttps, tener 2048 caracteres o menos, y su host no puede ser una dirección IP privada. El SDK no comprueba la URL. MONEI la comprueba después de leer la tarjeta. Si la URL no es válida, MONEI no crea el pago, yacceptPaymentpuede lanzaroutcomeUnknown. Asegúrate de que la URL es válida antes de aceptar pagos. - Para saber cuándo envía MONEI este webhook, consulta Callback del pago.
Muestra las pantallas educativas de Apple
Llama a TapToPay.presentEducation(from:) para mostrar las pantallas de Apple que enseñan cómo acercar una tarjeta. Muéstralas después de que el usuario acepte los términos de Apple, y desde tus ajustes o tu menú de ayuda.
try await TapToPay.presentEducation(from: viewController)
Acepta los términos de Apple antes del cobro
Desde el SDK 0.4.0. Apple pide que el comercio pueda aceptar los términos de Tap to Pay en el iPhone antes del primer cliente, por ejemplo al final de tu proceso de alta y desde tus ajustes. Usa isAccountLinked() para mostrar el estado y linkAccount() para mostrar los términos.
// Ajustes: lee el estado de Apple cada vez. No lo guardes.
func refreshTapToPayStatus() async {
enabled = (try? await TapToPay.isAccountLinked()) ?? false
}
// El botón que activa Tap to Pay en el iPhone. Muéstralo solo a un usuario que puede aceptar los
// términos en nombre del negocio. A los demás usuarios, muéstrales un mensaje para que contacten
// con un administrador.
func enableTapToPay(from viewController: UIViewController) async {
do {
try await TapToPay.linkAccount() // Muestra los términos de Apple si todavía no se aceptaron.
enabled = true
try await TapToPay.presentEducation(from: viewController)
} catch TapToPayError.termsDeclined {
// El usuario no aceptó los términos. Mantén el botón.
} catch {
// Consulta "Gestiona errores y resultados desconocidos".
}
}
- Llama primero a
prepare. Las dos llamadas usan el token guardado. isAccountLinked()nunca muestra los términos. También devuelvefalsecuando la comprobación falla. Esto es seguro:linkAccount()no muestra los términos a una cuenta que ya los aceptó.- Después de que el usuario acepte los términos,
linkAccount()prepara el lector. Esto puede tardar unos segundos. Muestra el estado del lector hasta que termine. - Mientras se ejecuta cualquiera de las dos llamadas,
acceptPaymentlanzabusy. No las inicies cuando el usuario vaya a cobrar. Si el usuario toca el botón de Tap to Pay en el iPhone mientras se ejecuta una comprobación, espera a que termine y después llama aacceptPayment. No desactives visualmente el botón: Apple no lo permite. - Mantén el botón de Tap to Pay en el iPhone en el cobro. Si los términos no se aceptaron,
acceptPaymentlos muestra. - Solo un usuario que puede aceptar los términos en nombre del negocio debe ver los términos de Apple.
acceptPaymentno puede comprobarlo. Para un usuario que no puede aceptarlos, llama aisAccountLinked()cuando se abra la pantalla de cobro. Si devuelvefalse, el botón de Tap to Pay en el iPhone muestra un mensaje para que contacte con un administrador y no llama aacceptPayment. - Para el texto del botón, usa los textos aprobados por Apple en sus directrices de marketing de Tap to Pay en el iPhone.
Muestra el estado del lector
Desde el SDK 0.4.0. Apple pide un indicador de progreso mientras el dispositivo configura Tap to Pay en el iPhone, y una vista de inicialización cuando el usuario inicia un pago antes de que el lector esté listo. Usa TapToPay.readerStates().
struct TapToPayStateView: View {
@State private var state = TapToPay.readerState
var body: some View {
Group {
switch state {
case .ready: Text("Listo")
case .preparing(let progress?):
ProgressView("Preparando", value: Double(progress), total: 100)
case .preparing: ProgressView("Preparando")
case .notReady: Text("No está listo")
@unknown default: EmptyView()
}
}
.task { for await next in TapToPay.readerStates() { state = next } }
}
}
| Estado | Significado |
|---|---|
notReady | No se ejecuta ninguna configuración y el lector no está listo: todavía no se llamó a prepare, la cuenta no aceptó los términos de Apple, la última configuración falló, la sesión se debe configurar otra vez (por ejemplo después de que la app estuvo en segundo plano) o MONEI bloqueó esta versión del SDK. La siguiente llamada que necesita el lector lo configura o lanza un error. |
preparing(progress:) | Se está ejecutando una configuración del lector. progress va de 0 a 100 mientras el dispositivo descarga su configuración. Muestra entonces un indicador con valor. Cuando progress es nil, muestra un indicador sin valor. |
ready | El lector está listo. acceptPayment abre la pantalla de Apple de inmediato. |
- Cada llamada recibe su propio flujo. Un flujo termina cuando se cancela su tarea o cuando se libera el flujo, como con un
.taskde SwiftUI. Unbreakdel bucle no termina por sí solo un flujo que todavía guardas. - En el cobro, llama a
acceptPaymentcuando el usuario toque el botón de Tap to Pay en el iPhone. Mientras el estado no seaready, muestra una vista de inicialización, por ejemplo "Tap to Pay en el iPhone estará listo en breve". La pantalla de Apple se abre cuando el lector está listo. - Después de la lectura de la tarjeta, la pantalla de Apple se cierra. Muestra una vista de procesamiento hasta que
acceptPaymenttermine.
Sentencias switch
El SDK usa library evolution, y sus enums públicos no están congelados. Un switch sobre PaymentResult.Status, TapToPayError o TapToPayErrorCode necesita un caso @unknown default. Sin él, tu app no compila.
Referencia de la API
| Símbolo | Descripción |
|---|---|
TapToPay.isSupported: Bool | true cuando el dispositivo es compatible con Tap to Pay en el iPhone. |
TapToPay.prepare(token: String) async throws | Guarda el token y prepara el lector en segundo plano. No muestra los términos de Apple. Pide permiso de ubicación si el usuario todavía no ha respondido. Llámalo una vez al iniciarse la app y después de cada renovación del token. |
TapToPay.acceptPayment(amount: Int, orderId: String, callbackUrl: URL?) async throws -> PaymentResult | Cobra un pago con tarjeta. amount está en céntimos de euro y debe ser mayor que 0. orderId no puede estar vacío. |
TapToPay.presentEducation(from: UIViewController) async throws | Muestra las pantallas de Apple que enseñan cómo acercar una tarjeta. |
TapToPay.isAccountLinked() async throws -> Bool | Desde 0.4.0. true cuando la cuenta del token guardado aceptó los términos de Apple en este dispositivo. Nunca muestra los términos. Consulta Acepta los términos de Apple antes del cobro. |
TapToPay.linkAccount() async throws | Desde 0.4.0. Muestra los términos de Apple si la cuenta todavía no los aceptó, y después prepara el lector. Llámala solo desde una acción del usuario. |
TapToPay.readerState: ReaderState | Desde 0.4.0. El estado actual del lector: notReady, preparing(progress:) o ready. Consulta Muestra el estado del lector. |
TapToPay.readerStates() -> AsyncStream<ReaderState> | Desde 0.4.0. El estado actual del lector y después cada cambio. Cada llamada recibe su propio flujo. |
PaymentResult | paymentId, status, cardBrand, last4, orderId, amount, currency, statusCode, statusMessage, authorizationCode, cardType y cardCountry. Consulta Lee el resultado. |
TapToPayError | El error que lanzan todas las llamadas. Consulta Gestiona errores y resultados desconocidos. |
Preguntas frecuentes
¿Por qué tarda más el primer pago?
En el primer inicio, el SDK pide permiso de ubicación y espera la respuesta del usuario. Después necesita una ubicación antes de poder preparar el lector, y espera hasta 15 segundos para obtenerla. En el primer pago en un dispositivo, Apple también puede mostrar sus términos de Tap to Pay en el iPhone. Llama a prepare al iniciarse la app, para que el lector esté listo antes de que el cliente pague, y ofrece los términos antes del cobro con linkAccount().
¿Debo llamar otra vez a prepare cuando la app vuelve del segundo plano?
No. El SDK prepara otra vez el lector cada vez que la app vuelve del segundo plano. Llama otra vez a prepare solo después de un inicio y después de una renovación del token.
¿Cada pedido necesita un orderId nuevo?
Sí. Usa un orderId nuevo y no vacío para cada pedido. MONEI lo guarda con el pago, y tú lo usas para conciliar. Después de outcomeUnknown, no cobres otra vez con un orderId nuevo hasta que conozcas el resultado del primer pago. Consulta Resultado desconocido.