Métodos de pago

MODO PRUEBA

Confirmas todos los métodos igual: winal.js con paymentMethod y paymentToken. Lo que cambia es qué trae next_action cuando el intent queda en requires_action — este es el shape REAL que devuelve la Api hoy (verificado contra ConnectorMappings y el conector Sim).

MétodoCómo se confirmanext_action.typeLatenciaEstado en fase 0
Tarjeta Token de tarjeta (tokenización client-side) redirect (solo si pide 3DS) Instantánea funciona hoy
SPEI El pagador transfiere a una CLABE virtual bank_transfer Minutos funciona en pruebas
CoDi El pagador escanea un QR con su banca móvil display_qr Minutos funciona en pruebas
DiMo Push al alias telefónico del pagador (sin QR) display_qr (qr_data siempre null) Minutos celular como token
OXXO El pagador paga en caja con una referencia oxxo_voucher Minutos a días funciona en pruebas
BNPL (Kueski/Aplazo/Mercado Crédito) Redirección al checkout de aprobación de crédito del proveedor redirect Minutos (aprobación de crédito) diseñado, no ejecutable aún
Card-present (SmartPOS) Token que entrega el lector EMV (chip/contactless) redirect (solo si el lector pide 3DS) Instantánea funciona en pruebas
Sobre "latencia"
El descriptor del conector Sim se declara como Instant (confirmas tú mismo con "Simular pago entrante", así que es inmediato). Las latencias de la tabla son las que aplican en producción según el método (ConfirmationLatency: instantánea para tarjeta/wallet, minutos para SPEI/CoDi/DiMo/OXXO en tiempo real, y hasta días para OXXO con corte diferido según el proveedor conectado).

Tarjeta

Confirmas con paymentMethod: "card" y un paymentToken — en pruebas, cualquiera de los tok_sim_* (tabla completa en Modo de pruebas); en producción, el token que entrega el SDK del proveedor tras tokenizar la tarjeta en el navegador. El PAN nunca toca tu servidor ni Winal (regla de arquitectura, PCI SAQ A).

El resultado más común es directo: succeeded (captura inmediata) o un decline duro/suave. Si el proveedor pide autenticación adicional (3DS), el intent queda en requires_action con:

next_action real
{ "type": "redirect", "url": "https://proveedor.mx/3ds/..." }

winal.js redirige automáticamente window.location a esa URL cuando ve este tipo. También existe el flujo auth/capture separado (útil en POS): con tok_sim_auth el intento queda authorized sin capturar, y tu servidor dispara POST /v1/payment_intents/{id}/capture cuando confirmas la venta — el intent permanece en processing hasta que el proveedor confirme la captura.

js
winal.confirmPayment({
  clientSecret, intentId,
  paymentMethod: "card",
  paymentToken: "tok_sim_ok",
  mountEl: document.getElementById("winal-mount"),
  onStatus: (status) => console.log(status),
});

Tarjeta REAL en el navegador (mountCardForm)

Todo lo anterior asume que ya tienes un paymentToken (en pruebas, un tok_sim_*). Para tarjetas de verdad, ese token lo genera el SDK del proveedor dentro del navegador del pagadorwinal.mountCardForm automatiza esa parte: pinta el formulario correcto según qué conector haya ganado el ruteo del tenant para card y, al enviarlo, tokeniza y llama confirmPayment por ti (mismo polling y mismo render de next_action de siempre).

js
await winal.mountCardForm({
  intentId,
  clientSecret,
  mountEl: document.getElementById("winal-mount"),
  onStatus: (status) => console.log(status),
});
// El form ya quedó montado y con su botón "Pagar" conectado: no hay nada más que llamar.

mountCardForm primero pide GET /public/payment_intents/{id}/checkout-config?method=card con el header X-Winal-Client-Secret (autenticación de capacidad; el secreto va en el header, no en el query, para no filtrarse a logs/Referer) para saber qué conector ganó el ruteo:

El PAN jamás pasa por Winal ni por tu servidor
Esto es PCI SAQ A (regla de arquitectura, ADR-0005): el navegador tokeniza directo contra el proveedor con su public_key (la única credencial que este endpoint puede devolver — nunca un access_token ni ningún otro secreto). Tu backend solo ve el token ya generado, igual que con tok_sim_* hoy.

SPEI

Confirmas con paymentMethod: "spei" (el token no aplica; envía cualquier valor). El intent queda en requires_action con la CLABE virtual a la que debe transferir el pagador:

next_action real (Sim)
{
  "type": "bank_transfer",
  "clabe": "646180473921058317",
  "beneficiary": "SIM SPEI",
  "expires_at": "2026-07-06T18:30:00Z"
}

La CLABE sintética de Sim usa el prefijo real de pruebas de STP (646180) más 12 dígitos aleatorios. winal.js pinta la CLABE y el beneficiario con botón de copiar. La confirmación real llega cuando el pagador transfiere desde su banco: Winal se entera por evidencia del proveedor (webhook o status query), nunca por timeout. En pruebas, usa "Simular pago entrante".

CoDi

Confirmas con paymentMethod: "codi". El intent queda en requires_action con un QR que el pagador escanea desde su banca móvil:

next_action real (Sim)
{
  "type": "display_qr",
  "qr_data": "SIM-CODI-sim_rtp_4af1c02e9b3d4f4e8f0a1b2c3d4e5f60",
  "expires_at": "2026-07-05T18:40:00Z"
}

winal.js incluye su propio generador de códigos QR (byte-mode, ECC nivel L, ISO/IEC 18004) y dibuja el QR en el navegador a partir de qr_data — ningún servicio externo de por medio. También muestra el texto crudo con botón de copiar por si el pagador no puede escanear. En producción, qr_data trae el payload real que entregue el proveedor CoDi conectado.

DiMo

📱 Convención de fase 0: el celular del pagador viaja como payment_token
DiMo exige el teléfono del pagador para generar el mensaje de cobro. En esta fase, al confirmar con payment_method: "dimo" envía el celular (10 dígitos) en payment_token — es el "instrumento" del cobro. Si lo omites, el intento falla con motivo del proveedor SIM_MISSING_PAYER_PHONE. Con el conector Sim cualquier celular de 10 dígitos funciona (p. ej. 5512345678); DiMo real llegará vía STP tras la homologación de Banxico y formalizará un campo dedicado.

Cuando esté completo, el patrón será igual a CoDi pero como push directo al alias registrado del pagador, sin QR (qr_data siempre null):

next_action prevista
{
  "type": "display_qr",
  "qr_data": null,
  "expires_at": "2026-07-05T18:40:00Z"
}

DiMo real llegará vía STP tras la homologación de Banxico (prevista dic. 2026); hasta entonces solo existe en el conector Sim, y con la limitación descrita arriba.

OXXO

Confirmas con paymentMethod: "oxxo". El intent queda en requires_action con una referencia para pagar en caja:

next_action real (Sim)
{
  "type": "oxxo_voucher",
  "reference": "48213097652014",
  "barcode_url": null,
  "expires_at": "2026-07-08T18:30:00Z"
}

En Sim, barcode_url siempre es null (un proveedor real conectado sí lo entrega); winal.js solo pinta el código de barras si el valor existe. El pagador paga en tienda y el proveedor notifica a Winal; en pruebas, simula el pago con el botón del portal o el endpoint admin — ver Modo de pruebas.

BNPL (compra ahora, paga después)

Confirmas con paymentMethod: "bnpl" — un solo literal para los tres proveedores (Kueski Pay, Aplazo, Mercado Crédito); cuál se usa lo decide el ruteo configurado del tenant, igual que con tarjeta. El pagador aprueba un plan de pago diferido en el checkout del proveedor: el intent queda en requires_action con una redirección, mismo next_action.type que un challenge 3DS:

next_action prevista
{ "type": "redirect", "url": "https://checkout-del-proveedor-bnpl.mx/aprobar/..." }

La aprobación o el rechazo del crédito se resuelve por webhook verificado + fetch o por consulta de estado (regla 5) — nunca por timeout local, igual que 3DS.

Honestidad: hoy no hay ningún camino BNPL ejecutable, ni siquiera en Sim
Los tres conectores están completos en código (mapeo de estados, resolución async, catálogo de errores), pero el conector Sim no implementa bnpl — no existe un tok_sim_bnpl para probarlo localmente. Confirmar con payment_method: "bnpl" hoy responde:
{ "error": { "code": "payment_intent.no_route",
    "message": "No hay conector configurado para el método 'bnpl'." } }
Activar cualquiera de los tres requiere cuenta y certificación real con el proveedor — trámite del dueño, no una limitación de este código.

Card-present (SmartPOS) y CoDi en mostrador

Confirmas con paymentMethod: "card_present". A diferencia de tarjeta en línea, el paymentToken lo genera el lector físico (SmartPOS con chip EMV/contactless), no el navegador — el PAN nunca sale del hardware certificado (misma regla 2 que tarjeta en línea). En pruebas, los tokens de Sim:

TokenResultado
emv_sim_okChip, aprobado, captura inmediata
emv_sim_ok_contactlessContactless/NFC, aprobado, captura inmediata
emv_sim_authAutoriza sin capturar (mismo flujo auth/capture que tarjeta)
emv_sim_declinedDecline duro
bash
curl -s https://api.winal.com.mx/v1/payment_intents/{id}/confirm \
  -H "Authorization: Bearer $SK" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{ "payment_token": "emv_sim_ok", "payment_method": "card_present" }'

Las terminales (número de serie, modelo, sucursal) se dan de alta y administran desde portal → Terminales — no hay un endpoint público de /v1 para registrarlas; una vez activa, su id es lo único que tu integración necesita.

CoDi en mostrador

CoDi cobrado desde una terminal física no es un método distinto: es el mismo paymentMethod: "codi" de siempre — solo agrega metadata.terminal_id con el id de la terminal al crear el intent, para que el QR quede asociado a ESA caja en tus reportes. El next_action es idéntico al CoDi de pantalla:

bash
curl -s https://api.winal.com.mx/v1/payment_intents \
  -H "Authorization: Bearer $SK" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{ "amount_minor": 30000, "currency": "MXN",
        "metadata": { "terminal_id": "5b90ab6a-4a73-44d9-8416-e7923d2d7c15" } }'
next_action real, tras confirmar con "codi"
{
  "type": "display_qr",
  "qr_data": "SIM-CODI-sim_rtp_bc1473020b6c408da29d102bc5ff8431",
  "expires_at": "2026-07-08T07:04:52.845164+00:00"
}

La decisión de reusar codi tal cual (en vez de inventar un método codi_mostrador) es deliberada: la terminal es solo el origen del cobro, no un método de pago distinto. El hardware EMV real (certificación PCI PTS) está gated; el módulo de gestión de terminales (alta, estados, metadata) funciona hoy de punta a punta contra Sim.