Saltar a contenido

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