RF-010 — Intentos de pago en el admin
|
|
| Estado |
Implementado en la rama feature/bancard-vpos (historia #264); pendiente de revisión y despliegue |
| Tipo |
Rutas de admin + página y widgets del admin + job diario |
| Ubicación |
src/lib/payment-attempt-admin/ · src/api/admin/payment-attempts/ · src/admin/routes/intentos-de-pago/ · src/admin/widgets/order-payment-attempts.tsx · src/admin/widgets/order-payment-details.tsx · src/jobs/payment-attempts-daily-summary.ts |
| Depende de |
Modelo de intentos de pago — RF-003, RF-011, ADR-0006 · correo — RF-007 |
Requisito
Con el modelo "pedido después del pago", los pagos que no se confirman no generan pedido y el
admin de Medusa no los muestra. Operaciones y ventas necesitan ver todos los intentos de pago
(en especial los que no terminaron en pedido), seguir a esos clientes, resolver reclamos de
cobros y detectar problemas con las pasarelas; y recibir un aviso diario con los casos que
requieren acción.
Solución adoptada
Una vista propia del admin sobre las tres fuentes del modelo nativo (análisis §5): el intento en
curso en payment_session.data.attempt, los cerrados de la misma sesión en
data.attempts_history y el archivo en cart.metadata.payment_attempts (sobrevive al borrado
de la sesión). Una consulta única las une, elimina duplicados por (sesión, número) y agrega
cliente, carrito y pedido. Las acciones sobre la pasarela las aporta cada proveedor por un
registro común (gateways.ts), sin exponer claves ni tokens.
Superficies
| Superficie |
Función |
GET /admin/payment-attempts |
Listado con filtros status (lista), provider_id, q (correo, nombre, referencia, pedido, carrito, sesión), from/to, cart_id, order_id (incluye los intentos anteriores del mismo carrito), paginación |
GET /admin/payment-attempts/:id |
Detalle: intento, sesión y pagos, carrito con ítems, pedido, historial de la sesión, aviso de la pasarela (sin datos de tarjeta) y acciones permitidas. id = <sesión>.<número> |
POST …/:id/consult |
Consulta en la pasarela (vPOS: single_buy/confirmations; QR: no disponible) |
POST …/:id/rollback |
Revierte un intento en curso y lo cierra (stock liberado, carrito editable) |
POST …/:id/complete-order |
"Crear el pedido igual" para un pagado sin pedido (stock comprometido, análisis §5.5) |
POST …/:id/release |
Cierra un pagado sin pedido sin crear el pedido: refund_pending si hay pago capturado, rolled_back si no |
GET /admin/payment-attempts/stats |
Conteos por estado en la ventana, abiertos ahora, pagados sin pedido, devoluciones pendientes y salud del monitoreo de Bancard |
| Página "Intentos de pago" |
Barra lateral del admin; tabla con filtros (estado, medio, fecha), búsqueda y vista por defecto de no confirmados; indicadores arriba; detalle con acciones confirmadas |
| Widget "Intentos de pago" en el pedido |
Zona order.details.after: los intentos del carrito que originó el pedido, incluidos los rechazos previos |
| Widget "Datos del pago" en el pedido |
Zona order.details.side.after: estado, monto, referencia, ticket, respuesta, fechas; nunca número de autorización ni datos de tarjeta |
Job payment-attempts-daily-summary |
08:00 de Asunción: resumen de intentos no confirmados de las últimas 24 h al correo SALES_DEPARTMENT_EMAIL, con alertas |
Alertas (observabilidad, tarea #165)
| Alerta |
Dónde |
Señal en el log |
| Pago cobrado sin pedido |
Resumen diario, conciliación cada 2 min, indicador del admin |
[bancard-vpos] conciliación: {...} — hay intentos pagados sin pedido… (warn) |
| Devolución manual pendiente |
Resumen diario, indicador del admin |
[bancard-vpos] Reversión manual pendiente… (error) |
| Monitoreo de Bancard sin señal (> 15 min sin el POST vacío) |
Conciliación del vPOS, resumen diario, indicador del admin |
[bancard-vpos] ALERTA: monitoreo de Bancard sin señal desde … (warn) |
| Confirmación con token inválido |
Webhook |
[bancard-vpos] webhook: token inválido… (warn) |
| Tasa de rechazo alta (≥ 30 % con ≥ 5 pagos decididos) |
Resumen diario |
[payment-attempts] resumen diario … — ALERTAS: … (warn) |
Las reglas de alerta en Grafana/Loki se definen sobre esos prefijos (tarea de infraestructura).
Reglas de negocio
- Solo se puede operar (consultar, revertir, liberar, crear pedido) sobre el intento vigente de
una sesión existente; los archivados son de solo lectura.
- Revertir solo aplica a
created o pending; crear el pedido y liberar, solo a
paid_without_order. Toda acción pide confirmación en la interfaz.
- Ningún listado ni detalle expone claves, tokens, huella del carrito ni datos de tarjeta.
- Rutas bajo
/admin protegidas por la autenticación estándar del admin.
Configuración
| Variable |
Uso |
SALES_DEPARTMENT_EMAIL |
Destinatario del resumen diario (vacío: no se envía, solo se registra en el log) |
ADMIN_CORS |
Su primer origen arma los enlaces al admin en el correo |
Criterios de aceptación
| # |
Criterio |
Prueba |
| 1 |
Las rutas exigen autenticación de admin |
integration-tests/http/payment-attempts-admin.spec.ts |
| 2 |
El listado une QR y tarjeta, filtra por medio, texto y estado, y no expone datos internos |
ídem |
| 3 |
El detalle trae intento, sesión, carrito con ítems, pedido y acciones según el estado |
ídem |
| 4 |
Consultar devuelve la respuesta de la pasarela sin token; revertir cierra el intento y desbloquea el carrito; repetirlo da 400 |
ídem |
| 5 |
Un intento confirmado muestra su pedido y su pago capturado; un intento cerrado sigue listándose tras uno nuevo (historial) |
ídem |
| 6 |
El resumen diario cuenta los no confirmados y arma las alertas; sin correo configurado solo registra |
ídem |
| 7 |
stats refleja la señal de monitoreo de Bancard |
ídem |
| 8 |
Página, detalle y widgets: verificación manual en el admin (/app/intentos-de-pago) |
Manual |
Limitaciones conocidas
- El monitoreo de Bancard se guarda en memoria del proceso: con réplicas, cada una ve su
última señal; tras un reinicio cuenta como "sin señal" hasta el primer POST.
- Bancard QR no ofrece consulta de estado; la acción "Consultar" solo existe para vPOS.
- El resumen diario lista hasta 200 intentos.