RF-006 — Medios de pago locales en el checkout
|
|
| Estado |
Implementado |
| Tipo |
Paneles de pago propios + canal en tiempo real |
| Ubicación |
src/lib/constants.tsx · src/modules/checkout/components/bancard-qr-panel/ · bancard-vpos-frame/ · src/lib/data/bancard-qr.ts · bancard-vpos.ts · payment-attempt.ts · checkout-after-payment.ts · src/lib/bancard-iframe.ts · src/modules/order/components/bank-transfer-panel/ · src/lib/hooks/use-bancard-payment-sse.ts |
| Depende de |
RF-000 · providers del backend — RF-001 y RF-003 de Medusa |
Requisito
Ofrecer los medios de pago habilitados por el backend con su presentación correspondiente;
resolver los pagos por Bancard (QR y tarjeta) dentro del paso de revisión del checkout,
mostrando el QR con su cuenta regresiva o el formulario de tarjeta, y avanzar al pedido cuando
el backend confirma el cobro, sin que el cliente recargue la página; y en el pago por
transferencia, entregar los datos bancarios necesarios para completarla.
Solución adoptada
Declarar en el storefront la representación visual de cada provider_id y dejar la habilitación
al backend. Para Bancard (ADR-0006 del backend) el storefront no llama a placeOrder: en el
paso de revisión el cliente repasa su pedido y confirma ("Pagar con tarjeta" o "Generar el código
QR"); recién ahí se abre el intento de pago (/store/bancard-qr/process o
/store/bancard-vpos/process), que reserva el stock y bloquea el carrito, y aparece el QR o el
iframe. El resultado llega por Server-Sent Events con sondeo de respaldo. Cuando llega el
order_id, finishPaidCheckout cierra el checkout como lo haría placeOrder (dirección,
cachés, cookie del carrito) y redirige al pedido.
sequenceDiagram
participant N as Navegador
participant SF as Storefront
participant API as Backend
N->>SF: Confirma el pedido en el paso de revisión
SF->>API: POST /store/bancard-qr/process {cart_id}
API-->>SF: qr_url · hook_alias · expires_in_min
SF->>N: QR + cuenta regresiva
SF->>API: GET /payment-events/{sessionId} (SSE) · GET …/status (respaldo)
Note over N,API: El cliente abona desde su aplicación bancaria; el webhook crea el pedido
API-->>SF: payment_status · captured {order_id}
SF->>N: finishPaidCheckout → /order/:id/confirmed
Medios representados
| Medio |
provider_id |
Estado |
| Bancard QR |
pp_bancard_qr_bancard_qr |
En uso; desde #263 se paga en el paso de revisión (pedido después del pago) |
| Tarjeta de crédito o débito (Bancard vPOS) |
pp_bancard_vpos_bancard_vpos |
Historia #149: iframe de Bancard en el paso de revisión; pendiente de certificación |
| Transferencia bancaria |
pp_bank_transfer_bank_transfer |
En uso, con descuento aplicado por el backend |
| Pago manual |
pp_system_default |
Disponible |
| Stripe y PayPal |
pp_stripe_* · pp_paypal_paypal |
Heredados del starter, inactivos |
Funciones
| Función |
Detalle |
| Apertura del intento |
Al confirmar en el paso de revisión, no al entrar: hasta ese momento el carrito sigue editable y no se reserva stock. Reutiliza el intento vigente si lo hay (por ejemplo al recargar la página) |
| QR |
Imagen de Bancard, monto, apps compatibles, referencia y cuenta regresiva con la vigencia que informa el backend |
| Tarjeta |
Script de Bancard alojado en el sitio, iframe con nonce de la CSP, cuenta regresiva. El alto del iframe se ajusta a lo que informa Bancard, con un piso por tipo de formulario (src/lib/bancard-iframe.ts) |
| Rechazo |
Se muestra el motivo en lenguaje claro ("Fondos insuficientes") y qué puede hacer el cliente, nunca el código de respuesta |
| Editar un paso cancela el pago |
Tocar "Editar" en cualquier paso del checkout revierte el intento en Bancard, libera el stock y desbloquea el carrito. No hay botón de cancelar debajo del QR ni del iframe: la cancelación se resuelve en el servidor releyendo el carrito sin caché |
| Confirmación en vivo |
Canal SSE contra /payment-events/{sessionId}; sondeo de respaldo cada 15 s (3 s mientras se espera el pedido) |
| Cierre del checkout |
finishPaidCheckout: dirección guardada, cachés invalidadas, cookie del carrito borrada, redirección al pedido |
| Reintento |
Rechazo o vencimiento: generar un QR nuevo o reintentar con otra tarjeta, o elegir otro medio |
| Simulador |
En desarrollo, con NEXT_PUBLIC_BANCARD_QR_SIMULATOR / NEXT_PUBLIC_BANCARD_VPOS_SIMULATOR, botones que hacen lo que haría el pagador |
| Panel de transferencia |
Cuentas bancarias, titular, RUC, tipo de cuenta y monto a transferir (en la página del pedido) |
| Página del pedido |
Con tarjeta: fecha y hora, número, importe y respuesta; nunca número de autorización ni código (manual 1.23 de Bancard) |
Reglas de negocio
- Decidir en el backend qué medios se ofrecen; el storefront solo sabe representarlos.
- Con Bancard (QR o tarjeta) el storefront nunca llama a
placeOrder: el pedido lo crea el
backend al confirmar el pago.
- Mientras hay un pago en curso el carrito está bloqueado. El cliente no queda encerrado: editar
cualquier paso del checkout cancela el intento y desbloquea el carrito. Si igual llega
payment_attempt_in_progress desde el backend, se traduce a lenguaje claro.
- La cuenta regresiva usa la vigencia que informa el backend (
expires_in_min); la expiración
efectiva la determina el backend.
- Reaccionar al
order_id (SSE, sondeo o simulador) una sola vez, cerrando el checkout y
redirigiendo a la confirmación.
Configuración
| Variable |
Uso |
NEXT_PUBLIC_MEDUSA_BASE_URL |
Origen del canal de eventos |
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY |
Clave publicable enviada por parámetro de consulta al canal |
NEXT_PUBLIC_BANCARD_VPOS_ORIGIN |
Origen de Bancard vPOS (CSP y elección del script) |
NEXT_PUBLIC_BANCARD_QR_SIMULATOR · NEXT_PUBLIC_BANCARD_VPOS_SIMULATOR |
Solo desarrollo: botones del simulador |
NEXT_PUBLIC_BANCARD_QR_EXPIRATION_SECONDS se eliminó en #263: la vigencia la informa el backend.
Criterios de aceptación
| # |
Criterio |
| 1 |
Exhibir únicamente los medios habilitados por el backend, con su rótulo y su ícono |
| 2 |
El paso de revisión pide confirmación antes de cobrar; al confirmar aparece el QR con su cuenta regresiva o el formulario de tarjeta, sin botón "Realizar pedido" |
| 3 |
Confirmado el pago, avanzar a la confirmación del pedido sin recarga manual y con el carrito del navegador vacío |
| 4 |
Informar el vencimiento o rechazo y permitir generar un QR nuevo o elegir otro medio |
| 5 |
Editar cualquier paso con un pago en curso lo cancela y deja el carrito editable |
| 6 |
Recuperar la conexión de eventos tras una interrupción de red (y seguir por sondeo si no vuelve) |
| 7 |
Exhibir los datos bancarios y el monto exacto en el pago por transferencia |
Limitaciones conocidas
- Duplicar los datos bancarios en el código, declarados en dos componentes distintos: un cambio de
cuenta exige modificar y desplegar.
- Conservar el código inactivo de Stripe y PayPal heredado del starter.
- La imagen del QR la sirve Bancard (
*.bancard.com.py / *.infonet.com.py, en img-src de la CSP).