Al terminar el ciclo de pago (exitoso, fallido, o abandonado), ZonaPagos redirige al usuario mediante HTTP GET a la URL de retorno del comercio. La request incluye dos parámetros en query string:
GET https://micomercio.com/pago/retorno?id_comercio=31416&id_pago=ORDEN-001
El callback NO incluye el estado del pago. Solo notifica que “el usuario terminó algo”. Tienes que llamar /VerificacionPago desde tu backend para saber qué pasó.
async function procesarCallback(idComercio, idPago, res) { // 1. Validar que el id_comercio sea el nuestro if (Number(idComercio) !== Number(process.env.ZP_ID_COMERCIO)) { logger.warn("Callback con id_comercio ajeno", { idComercio }); return res.status(403).send("Forbidden"); } // 2. Buscar el pago en nuestra BD const pago = await db.pagos.findOne({ str_id_pago: idPago }); if (!pago) { logger.error("Callback para pago inexistente", { idPago }); return res.status(404).send("Pago no encontrado"); } // 3. Verificar estado con ZonaPagos const estado = await verificarEstado(idPago); // 4. Actuar según el estado return redirigirPorEstado(res, pago, estado);}
A veces el usuario cierra la pestaña antes del redirect final. Tu backend no va a recibir el callback, pero el pago puede estar aprobado.Por eso existe la sonda: detecta estos casos.
El callback no está firmado. Un atacante que conozca tu URL y un id_pago válido podría disparar un GET falso pretendiendo ser ZonaPagos.Mitigación obligatoria:
Siempre verifica el estado con /VerificacionPago (como muestra este flujo).
Nunca entregues producto basándote solo en la llegada del callback.