Apple Pay con credencial guardada
Consentimiento en el sheet con recurringPaymentRequest → guardar la credencial
sin cobrar → cobrar después, con el cliente ausente y montos distintos. Fijado a la compañía de
staging: esta es la única donde existe el feature.
¿Quién firma la merchant session?
Esta perilla tiene que casar con la del descifrado: Apple cifra el token con el Payment
Processing Certificate del merchant que firmó la sesión. Si no coinciden, sale
OpenSSL::Cipher::CipherError (finding F-CIPHER).
Apple elige el gateway según el dispositivo: una cuenta Apple real con tarjeta real va al gateway
de producción; una cuenta Apple Pay Sandbox con tarjeta de prueba va al de -cert.
Verificado en vivo el 11-sep-2026:
| Firma | Gateway | Tarjeta | Sesión | MPAN |
|---|---|---|---|---|
PSP Conekta Stage | -cert | sandbox | ✓ válida | ✗ el sandbox de Apple nunca emite MPAN |
PSP Conekta Stage | producción | real | ✗ 417 | — |
Cuenta propia merchant.ANB.QA | producción | real | ✓ válida | ✗ merchant ID sin respaldo de redes |
El 417 del PSP en el gateway de producción no es falta de Mass Enablement: la company
sí está enrolada, pero para el entorno sandbox de Apple — que es lo correcto para staging.
La única captura de MPAN que existe (CRD-1979) se hizo en producción, con tarjeta real
y merchant.io.conekta.
Comprobaciones del dispositivo
Antes de nada: si algo de esto falla, el resto no puede funcionar.
Crear el customer
POST /customers — sin método de pago todavía. Devuelve el cus_ al que después se le cuelga la credencial.
Consentimiento y guardado
Abre el sheet con recurringPaymentRequest y manda el pk_payment a
POST /customers/:id/payment_sources. Conekta hace una verificación (autorización de ~$1 reversada)
para abrir la serie. No cobra el monto de abajo — ese solo se muestra en el sheet.
No está documentado cuál usa la llave del Vault (tu cert) y cuál la del
PSP. Si sale posible_cipher_error, prueba el siguiente de la lista:
es la perilla que decide con qué llave descifra Conekta.
Cobrar bajo demanda
POST /orders con payment_method {type:"apple", id:"src_…"}, sin pk_payment
y sin el cliente presente. Cada cobro puede llevar un monto distinto — ese es el punto del ejercicio.
Ver el wallet del customer
GET /customers/:id/payment_sources — qué credenciales quedaron y con qué credential_type.
Log
Esperando…Qué verificamos contra la documentación oficial (y qué no cuadra)
recurringPaymentRequest al request existente” sin mencionar la versión.
Pero recurringPaymentRequest se introdujo en la v14 de la Apple Pay JS API. Una
integración que hoy hace new ApplePaySession(3, …) —como la nuestra antes de esta página— no va a
emitir merchant token. Esta herramienta no sube la versión a todo: negocia cada camino por
separado (versionPara(), abajo) y, cuando el dispositivo no llega a v14, retira el
guardado y deja vivo el one-off en v3 — para que se vea que la separación es la respuesta correcta, no
una limitación.
“Added support for automatic reload payments, recurring payment requests, and multiple payment tokens in ApplePayModifier and ApplePayPaymentRequest.” Apple — Apple Pay on the Web Version 14 Release Notes (macOS 13 / iOS 16)
El patrón, para copiar
Así queda la negociación por camino. Es lo que corre en esta página:
// El one-off basta con v3. El recurrente EXIGE v14 (recurringPaymentRequest). // Apple: usa la versión más baja que soporte lo que necesitas. const V_ONE_OFF = 3; const V_RECURRENTE = 14; function versionPara(camino) { const quiero = camino === 'recurrente' ? V_RECURRENTE : V_ONE_OFF; if (!window.ApplePaySession) return null; if (typeof ApplePaySession.supportsVersion !== 'function') { // Navegador viejo: solo dejamos pasar one-off. return camino === 'recurrente' ? null : V_ONE_OFF; } return ApplePaySession.supportsVersion(quiero) ? quiero : null; } // Clave: si el recurrente no está disponible NO se degrada a una versión // menor. Se retira la opción de guardar. Fingir que se puede es justo lo // que produce el on_demand_eligible:false que nadie sabe diagnosticar. const vRec = versionPara('recurrente'); if (vRec === null) { ocultarBotonGuardarCredencial(); // el one-off sigue intacto } else { new ApplePaySession(vRec, { ...request, recurringPaymentRequest }); }
El MPAN no está garantizado
Incluir recurringPaymentRequest es condición necesaria pero no suficiente. Apple
emite merchant token solo “where supported by the issuer”; si el emisor no lo soporta, regresa un DPAN normal.
Por eso el paso 2 pinta credential_type y on_demand_eligible en grande: un
false ahí no es un bug de la integración.
“Apple Pay defaults back to returning the DPAN.” Apple — Apple Pay Merchant Integration Guide (marzo 2026), p.18
managementURL es obligatoria
En el WebIDL de ApplePayRecurringPaymentRequest, managementURL está declarada como
required, junto con paymentDescription y regularBilling.
trialBilling, billingAgreement y tokenNotificationURL son opcionales.
Por eso esta herramienta trae su propia página: /apple-pay-gestion.
Sobre tokenNotificationURL
El brief la apunta a un host de Conekta. Apple la documenta como una URL del merchant, con
mutual TLS y allow-list de IPs de Apple, que recibe un GET con un eventId y obliga a un POST de
vuelta a Apple para leer el detalle. Que Conekta la centralice como PSP es plausible, pero ese endpoint no
existe en el spec público. Por eso aquí el campo va vacío por default: es opcional, y
mandarla apuntando a algo que no responde solo agrega ruido al diagnóstico.
Nada de este flujo está publicado
Barrido del spec OpenAPI de Conekta (v2.3.0) y de las 271 páginas de developers.conekta.com:
cero ocurrencias de credential_type, on_demand_eligible,
merchant_decrypted, token_notifications y de los cinco códigos de error
wallet.*. El endpoint /customers/:id/payment_sources existe pero su
oneOf público solo acepta card, cash y spei — no apple. Es un feature interno; los nombres de
campo de esta herramienta salen del brief, no de un contrato.
Ojo con un falso amigo: ya existe un campo público llamado on_demand_enabled (bandera de
configuración del checkout, flujo de tarjeta). No es lo mismo que on_demand_eligible.