Saltar a contenido

E2E con GatewayQR

Corridas de extremo a extremo, sin binario de POS, contra los dos procesos reales (BeneficiosCenter y GatewayQR) levantados localmente contra SQL Server real. Esta página documenta dos casos concretos, en orden cronológico: el fix de signo de importe_recdesc (entrega 2) y el pipeline de resultado de pago (entrega 3, Q2).

Corrida 1 — fix de signo de importe_recdesc (entrega 2)

Fuente: reports/e2e-servidor-20260911-162402.md del repositorio del sistema.

Topología de la corrida

Proceso Puerto Detalle
BeneficiosCenter 8081 beneficioscenter-backend-0.0.1-SNAPSHOT.jar, DB beneficioscenter, JDK 25 (requiere class-file 61).
GatewayQR 8082 gatewayqr-20250919.war, rama feature/beneficios-integration, commit fddef57, DB GatewayQR, perfil dev, corrido con JDK 8 (Spring Boot 1.5.14).

app.beneficioscenter.api-key se pasó por línea de comandos, sin commitear ni imprimir en el reporte original.

El fix que valida esta corrida

Commit fddef57 en GatewayQR: fix(webhook): store importe_recdesc as negative discount, not raw positive. Antes del fix, importe_recdesc se guardaba en positivo; el POS rechazaba la coherencia de importes.

Paso 0 — confirmar el caso jueves

Mismo escenario resuelto con fecha sintética de jueves y con el reloj real (viernes) — el signo es el punto del fix, no cuál beneficio gana:

Reloj Beneficio ganador discountAmount
Jueves sintético (2026-09-10T14:00:00-03:00) Benefit 1, "10% los jueves para DINI Negocios" 10000.00
Viernes real (reloj del sistema) Benefit 3, "15% por aniversario" 15000.00

Loop HTTP completo (POS → GatewayQR → BeneficiosCenter → emulador → webhook)

POST /api/trxid (comercio "Dino", sucursal 99, pos 89) devolvió el id 10140. POST /api/trxpago con {"id":10140,"importe":100000,"tipoautorizador":"TECSO"} disparó el loop completo, bloqueando hasta la respuesta.

(a) Intención de pago saliente, con benefits_methods_data real

{
  "original_amount": 100000,
  "benefits_methods_data": [
    {
      "benefits_card": {"code": "991", "description": "15% por aniversario en SUC-001/SUC-014/SUC-022, con tope de descuento"},
      "establishment_id": "29880765",
      "discount": {"amount": 15000, "percentage": 15, "maximum_discount_amount": 25000, "type": "DISCOUNT"}
    },
    {
      "benefits_card": {"code": "990", "description": "15% por aniversario en SUC-001/SUC-014/SUC-022, con tope de descuento"},
      "establishment_id": "29880765",
      "discount": {"amount": 15000, "percentage": 15, "maximum_discount_amount": 25000, "type": "DISCOUNT"}
    }
  ],
  "branch": "SUC-014"
}

benefits_methods_data viene de la llamada real de BeneficiosCenterClient a /api/v1/beneficios/resolver — idéntica a la del Paso 0, viernes.

(b) Respuesta del emulador (webhook entrante)

{
  "amount": 85000,
  "discounted_amount": 15000,
  "payment_method_id": 990,
  "status": "approved"
}

El emulador sigue mandando discounted_amount en positivo (es el contrato del autorizador real, no cambió). El fix está en cómo el gateway lo guarda.

(c) TrxRtaPos final entregado al POS

{
  "id": 10140,
  "estado": "AUTORIZACION_DE_PAGO_APROBADA",
  "importe": 100000.00,
  "payment_method_id": 990,
  "importe_final": 85000.00,
  "importe_recdesc": -15000.00
}

Coherencia verificada en el POS: importe (100000) + importe_recdesc (-15000) == importe_final (85000) ✔. dD = -1.0 * importe_recdesc = 15000 >= 0 → el POS acepta el pago (antes del fix, dD daba -15000 < 0 y rechazaba con "recdesc positivo = recargo: dato inválido").

Antes / después

importe_recdesc Resultado en el POS
Antes del fix +15000.00 (positivo) Rechazado por coherencia/signo
Ahora (esta corrida) -15000.00 (negativo) Aceptado, coherencia verificada end-to-end

Qué es en vivo vs qué no

Todo lo de esta corrida es HTTP en vivo contra los dos procesos reales, sin componentes mockeados, stubs ni resultados inventados. La única fuente no-HTTP consultada fue una lectura por SQL (sin escrituras) para confirmar que los datos de entorno (URLs del autorizador, sucursal) seguían intactos de la corrida anterior.

Corrida 2 — Pipeline de resultado de pago (entrega 3, Q2)

Misma topología (BeneficiosCenter real en 8081, GatewayQR real en 8082) contra SQL Server real, esta vez para verificar el pipeline completo de resultado de pago: que el gateway llame a /resultado después de que el autorizador aprueba, que quede persistido en resultado_pago, y que el panel de Resultados lo refleje.

Money-path del GatewayQR corregido antes de esta corrida

Dos fixes de las llamadas al autorizador, ambos necesarios para que este loop fuera confiable antes de medir nada sobre él:

  • SEC-01 — webhook fail-closed. Antes, si server.apikey.tecso no estaba configurada en el ambiente, la validación del webhook de aprobación se salteaba entera (if que solo comparaba cuando la clave estaba seteada) — cualquiera podía simular una aprobación. Ahora, sin la clave configurada, el webhook responde 401 (fail-closed) y la comparación usa MessageDigest.isEqual en tiempo constante.
  • CORR-01 — crash en pagos en cuotas. BigDecimal.divide(cuot) sin escala explícita lanzaba ArithmeticException en cualquier división no exacta (por ejemplo 3 cuotas). Se cambió a divide(cuot, 2, RoundingMode.HALF_EVEN).

Estos dos fixes son del money-path del gateway (cómo cobra), no del pipeline de resultado en sí — se documentan acá porque esta corrida los ejercita en vivo.

Loop verificado

Un pago de 100.000 con la cuenta 990 (Dini Negocios), contra el emulador del gateway:

  1. POST /resolver resuelve el beneficio "Aniversario DINO 15%" (benefitId: 3) para la cuenta 990: discountAmount: 15000.00.
  2. El emulador aprueba con payment_method_id: 990, discounted_amount: 15000.
  3. El GatewayQR arma la respuesta al POS: importe_final: 85000.00.
  4. El GatewayQR llama, fire-and-forget, a POST /api/v1/beneficios/resultado:
{
  "requestId": "GW-...",
  "estado": "APROBADO",
  "paymentMethodId": 990,
  "benefitId": 3,
  "descuentoAplicado": 15000.00,
  "importeOriginal": 100000.00,
  "importeFinal": 85000.00
}

benefitId: 3 viaja porque el gateway lo recuperó de su propia intención persistida (jsonreqintencion), matcheando por payment_method_id — ver benefitId: de dónde sale. 5. BeneficiosCenter responde 202 y persiste, poco después, la fila en resultado_pago. 6. GET /api/admin/tablero/resultados para el día de la corrida devuelve exactamente lo que pasó:

{
  "totalConResultado": 1,
  "aprobados": 1,
  "rechazados": 0,
  "tasaAprobacionPct": 100.00,
  "descuentoEfectivoTotal": 15000.00,
  "porMedioDePago": [
    { "paymentMethodId": 990, "cantidad": 1, "porcentaje": 100.00 }
  ]
}

Estos son los mismos números que muestra el panel poblado en Consola web — Resultados de pago.

Qué prueba esta corrida y qué no

Es una corrida real de punta a punta contra el emulador del gateway (no contra el autorizador DINI/TECSO real) — confirma que el pipeline completo funciona (resolución → aprobación → /resultado → persistencia → panel), no reemplaza la confirmación formal de DINI sobre el contrato con el autorizador real (ver Contrato con el autorizador, RISKS.md R-02).

Dónde sigue