E2 Apple Pay · bajo demanda
STG api.stg.conekta.io Llaves Company d API 2.3.0 Firma

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).

En staging NO se puede obtener un MPAN. Ninguna combinación lo permite.

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:

FirmaGatewayTarjetaSesiónMPAN
PSP Conekta Stage-certsandbox ✓ válida✗ el sandbox de Apple nunca emite MPAN
PSP Conekta Stageproducciónreal ✗ 417
Cuenta propia merchant.ANB.QAproducciónreal ✓ 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 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.

0

Comprobaciones del dispositivo

Antes de nada: si algo de esto falla, el resto no puede funcionar.

1

Crear el customer

POST /customers — sin método de pago todavía. Devuelve el cus_ al que después se le cuelga la credencial.

2

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.

Crea el customer primero.
3

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.

4

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)
La versión de la sesión es lo primero que hay que cambiar. El brief dice “agregar el 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.

Fuentes