Saltar al contenido principal

JavaScript (Node.js)

El ejemplo en Node.js utiliza el framework Express para el servidor y el módulo nativo crypto para la validación.

1. Recepción de la Notificación (Express)​

Es crucial usar un middleware como express.raw para asegurarse de que el cuerpo de la solicitud se lee como un buffer sin ser modificado.

const express = require('express');
const { validateSyPagoSignature } = require('./signature-validator');

const app = express();

// Middleware para leer el body como un buffer crudo para las rutas de webhook
app.use('/webhook-sypago', express.raw({ type: 'application/json' }));

app.post('/webhook-sypago', async (req, res) => {
try {
// 1. Extraer los componentes de la notificación
const signature = req.headers['x-signature'];
if (!signature) {
return res.status(400).send('Cabecera X-Signature es requerida');
}

const nonce = req.headers['x-signature-nonce'];
if (!nonce) {
return res.status(400).send('Cabecera X-Signature-Nonce es requerida');
}

// El payload es el buffer crudo del body (req.body)
const payload = req.body;

// 2. Validar la firma
await validateSyPagoSignature(signature, payload, nonce);

// 3. La firma es válida. Procesar la notificación.
console.log("Firma válida. Procesando notificación...");
// Aquí iría la lógica para procesar el payload.
// const notificationData = JSON.parse(payload.toString('utf8'));

res.status(200).json({ message: 'Firma válida y notificación recibida' });

} catch (error) {
// La firma no es válida. Descartar la petición.
console.error(`Error al validar la firma: ${error.message}`);
res.status(401).send(error.message);
}
});

// ... iniciar el servidor
// const PORT = 3000;
// app.listen(PORT, () => console.log(`Servidor escuchando en el puerto ${PORT}`));

2. Validación de la Firma​

Este módulo exporta la función de validación.

const crypto = require('crypto');

async function validateSyPagoSignature(signatureB64, payloadBuffer, nonce) {
// --- Obtención de Secretos ---
// El `operationSecret` DEBE ser recuperado de su base de datos.
// Lo guardó cuando inició la transacción con SyPago y recibió el `transaction_id`.
const operationSecret = '9f4aaf08-8d04-4007-a097-c0e95eddad5e'; // ¡EJEMPLO! Reemplazar con su lógica de obtención.

// La `publicKeyPEM` DEBE ser obtenida del endpoint GET /api/v1/user/key
// usando su token JWT. Es recomendable cachear esta clave.
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9hXXl4g886loHmI10dLrJWFHEB8r
cyqD1hBdIM3ekQkb5YTpOShAu+7xk0fyL/0IBjLEKwSd7rsKrYGtIgJj0w==
-----END PUBLIC KEY-----`; // ¡EJEMPLO! Reemplazar con la clave real obtenida.

// --- Proceso de Verificación ---

// 1. Reconstruir el mensaje a verificar (payload.nonce.operationSecret)
// Es importante usar el buffer del payload directamente
const stringToVerify = Buffer.concat([
payloadBuffer,
Buffer.from(`.${nonce}.${operationSecret}`)
]);

// 2. Crear un objeto de verificación con el algoritmo SHA-256
const verify = crypto.createVerify('SHA256');

// 3. Cargar el mensaje en el objeto
verify.update(stringToVerify);
verify.end();

// 4. Verificar la firma usando la clave pública y la firma en Base64
// El formato de la firma es ASN.1 DER, que es el estándar que `crypto` espera.
const isSignatureValid = verify.verify(publicKeyPEM, signatureB64, 'base64');

if (!isSignatureValid) {
throw new Error('La firma no es válida');
}

// La firma es válida
return true;
}

module.exports = { validateSyPagoSignature };