RF-004 — Estado de pago en tiempo real
Requisito
Informar al storefront el cambio de estado de una sesión de pago asincrónica —confirmado,
rechazado, expirado o cancelado— sin que el cliente recargue la página ni consulte de forma
periódica, y liberar la conexión cuando el navegador se desconecta. Lo usan los dos medios cuyo
pago confirma Bancard: el QR y la tarjeta por vPOS.
Solución adoptada
Exponer un canal Server-Sent Events sobre el propio servidor HTTP de Medusa, con un registro en
memoria de conexiones indexado por sesión de pago. La decisión de usar SSE en lugar de Socket.IO
está documentada en el código: evita puerto adicional y dependencias externas.
sequenceDiagram
participant N as Navegador
participant API as Backend
participant BAN as Bancard
N->>API: GET /payment-events/:session_id?publishable_api_key=pk_...
API-->>N: event: connected
Note over N,API: Conexión abierta, sin timeout de socket
BAN->>API: Webhook de confirmación
API-->>N: event: payment_status {status: captured, order_id}
N->>N: Redirigir a la confirmación del pedido
N--xAPI: close
API->>API: Liberar la conexión del registro
Contrato del canal
| Aspecto |
Definición |
| Ruta |
GET /payment-events/:session_id — fuera de /store porque EventSource no admite cabeceras propias |
| Autenticación |
publishable_api_key por query string, validada por prefijo pk_ |
| Evento de apertura |
connected con el session_id |
| Evento de estado |
payment_status con status en captured, failed, expired o canceled |
| Carga útil adicional |
order_id, nombre del pagador, marcas de tiempo y código de respuesta según el caso |
| CORS |
Origen reflejado del solicitante, con credenciales habilitadas y preflight OPTIONS |
Reglas de negocio
- Emitir a todas las conexiones abiertas de una misma sesión de pago y descartar las que ya
cerraron.
- Desactivar el timeout del socket y el buffering intermedio (
X-Accel-Buffering: no) para que
el evento llegue de inmediato.
- Tratar la emisión como accesoria: una falla en el canal no interrumpe la captura del pago ni el
cierre del intento.
- Cada medio ofrece su propia consulta puntual de estado como respaldo del canal:
GET /store/bancard-qr/sessions/:id/status y GET /store/bancard-vpos/sessions/:id/status,
ambas con el carrito dueño. La ruta común /store/payment-status/:session_id del modelo
anterior fue eliminada al migrar el QR (#263).
Criterios de aceptación
| # |
Criterio |
| 1 |
Establecer la conexión y recibir el evento connected con clave publicable válida |
| 2 |
Rechazar con 401 la conexión sin publishable_api_key o con formato inválido |
| 3 |
Recibir payment_status con estado captured dentro del mismo segundo del webhook |
| 4 |
Recibir payment_status con estado expired al vencer sin pago (QR vencido o intento de tarjeta conciliado) |
| 5 |
Liberar el registro de la sesión al cerrarse la última conexión |
Limitaciones conocidas
- Mantener el registro de conexiones en memoria del proceso: con más de una instancia, el evento
solo alcanza a los clientes conectados a la instancia que procesó el webhook.
- Validar la clave publicable solo por prefijo, sin verificarla contra el módulo API Key.
- No reemitir eventos perdidos: un cliente que se conecta después de la confirmación depende de
la consulta puntual de estado.