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.
Firma de la merchant session
Fijado a certificado propio (merchant.ANB.QA): la sesión se firma por
mTLS directo con Apple y Conekta descifra con ese cert desde el Vault. El payload sale
sin campo source y con
account_mode: "own" — exactamente el mismo shape del cargo one-off de
/dev, que es el que sí descifra en esta company.
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.
El source del payload no se elige aquí: se deriva del modo de firma de
arriba, porque están acoplados. Apple cifra el token con el certificado del merchant que firmó la
sesión, así que pedirle a Conekta que descifre con la otra llave produce CipherError.
Certificados de Conekta → source: "external"; certificado propio → sin
campo source. Son los dos únicos valores que documenta Conekta
(botón de Apple Pay para web).
El brief interno menciona "checkout" y "merchant_decrypted", que
no existen en el spec ni en las 271 páginas de la doc pública.
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.
Payloads exactos
Lo que se manda en cada salto, tal cual. Ojo con la distinción: el
ApplePayPaymentRequest va a Apple (es el que lleva
recurringPaymentRequest); el payment_sources va a Conekta.
Se muestran completos, sin truncar — criptograma incluido.
Todavía no hay payloads. Corre el paso 1 o 2.
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.