Cómo integrar pagos QR en Node.js y Express
Guía para aceptar pagos QR en Node.js con Express: crea el cobro, escucha el webhook y confirma el pago.
Guía para aceptar pagos QR en Node.js con Express: crea el cobro, escucha el webhook y confirma el pago.
Aceptar pagos QR en Node.js y Express es una de las formas más rápidas de eliminar la fricción en el checkout de tu e‑commerce o aplicación web. En esta guía te muestro cómo integrar un sistema de cobro con QR interoperable (compatible con Yape, Plin y cualquier billetera del ecosistema BCRP) utilizando la API de una pasarela moderna, creando el cobro, escuchando el webhook de confirmación y verificando la firma en tiempo real, todo con código limpio y seguro.
Antes de escribir la primera línea de código, conviene entender por qué los pagos QR son una decisión estratégica para un negocio peruano que opera sobre Node.js.
crypto de Node.js tienes todo lo necesario para poner el flujo en producción en cuestión de horas.Si quieres profundizar en cómo funciona la interoperabilidad y por qué es un estándar en Perú, puedes leer nuestra guía sobre cómo funciona la interoperabilidad QR del BCRP. Además, conviene entender qué es la CCE y por qué importa para tu negocio.
Para seguir esta guía necesitas:
Todos los secretos deben manejarse mediante variables de entorno. En producción, usa process.env y nunca los hardcodees en tu código fuente.
Si aún no tienes una cuenta, puedes consultar la documentación para developers y ver el detalle de la API REST.
Crea un proyecto nuevo o añade las rutas necesarias en tu aplicación existente. Vas a necesitar las siguientes dependencias:
npm install axios dotenv express
axios se encargará de las peticiones HTTP a la API de pagos; dotenv cargará las variables de entorno; express levantará el servidor y gestionará las rutas.
Crea un archivo .env (y añádelo a .gitignore):
PAYMENT_API_URL=https://api.pasarela-ejemplo.com/v1
PAYMENT_API_KEY=tu_api_key_secreta
PAYMENT_WEBHOOK_SECRET=tu_secreto_para_webhooks
En tu app.js o server.js configuras las rutas básicas:
require('dotenv').config();
const express = require('express');
const app = express();
app.use(express.json());
// tus rutas aquí...
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Servidor corriendo en puerto ${PORT}`);
});
Ahora estás listo para escribir los endpoints de cobro y de notificación.
El flujo de cobro con QR se dispara desde tu backend cuando el cliente decide pagar. La API expone un endpoint POST /api/v1/payments que devuelve, entre otras cosas, la imagen del QR en formato base64 y una URL de pago (checkout_url) que puedes mostrar en tu interfaz o redirigir al usuario.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
amount | decimal | Sí | Monto total del cobro. Mínimo S/ 1.00. |
description | string | Sí | Concepto del pago (se muestra al cliente). |
reference | string | No (recomendado) | Tu identificador interno de la orden. Sirve para conciliación. |
expires_in | integer | No | Tiempo de expiración en minutos (por defecto 15). El QR dinámico deja de ser válido tras este plazo. |
customer | object | No | Datos opcionales del cliente (ej. { email, name }). |
Además, debes incluir el header Idempotency-Key con un valor único generado por ti (por ejemplo, el order_id o un UUID). Esto evita que, por un reintento accidental, se cree un cobro duplicado.
/api/checkoutconst axios = require('axios');
const { v4: uuidv4 } = require('uuid'); // opcional
app.post('/api/checkout', async (req, res) => {
try {
const { amount, description, reference } = req.body;
if (!amount || !description) {
return res.status(400).json({ error: 'amount y description son obligatorios' });
}
const idempotencyKey = reference || uuidv4();
const response = await axios.post(
`${process.env.PAYMENT_API_URL}/payments`,
{
amount,
description,
reference,
expires_in: 15,
},
{
headers: {
Authorization: `Bearer ${process.env.PAYMENT_API_KEY}`,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
}
);
const { qr_code_base64, checkout_url, id: payment_id } = response.data;
// Devuelve al frontend lo necesario para mostrar el QR
res.json({
payment_id,
qr_code: qr_code_base64, // imagen en base64 para incrustar en <img>
checkout_url, // link de pago alternativo
});
} catch (error) {
console.error('Error creando el cobro:', error.response?.data || error.message);
res.status(500).json({ error: 'Error al generar el cobro QR' });
}
});
El QR generado es dinámico y único para ese cobro; expira a los 15 minutos y cualquier billetera interoperable (Yape, Plin, etc.) puede leerlo. Tu frontend simplemente muestra la imagen y, en paralelo, queda a la espera de la confirmación vía webhook o polling controlado.
Tan importante como crear el cobro es confirmar que el cliente pagó. La pasarela envía una notificación HTTP POST a un endpoint que tú definas en el panel (por ejemplo, https://tu-dominio.com/api/webhook). Ese webhook incluye la información del pago y una firma HMAC‑SHA256 en el header X‑Signature que debes verificar para evitar peticiones maliciosas.
app.post('/api/webhook', (req, res) => {
try {
const signature = req.headers['x-signature'];
const payload = JSON.stringify(req.body);
// 1. Verificar la firma
const crypto = require('crypto');
const expectedSignature = crypto
.createHmac('sha256', process.env.PAYMENT_WEBHOOK_SECRET)
.update(payload, 'utf-8')
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Firma inválida' });
}
// 2. Procesar el evento
const event = req.body;
if (event.status === 'confirmed') {
// El pago fue confirmado por la entidad financiera
console.log(`Pago confirmado para la orden ${event.reference}`);
// Aquí actualizas tu base de datos, envías el producto, etc.
}
// 3. Responder 200 rápido para evitar reenvíos
res.sendStatus(200);
} catch (error) {
console.error('Error en el webhook:', error);
res.status(500).json({ error: 'Error interno' });
}
});
El paso de verificación de firma es crítico. Si necesitas una explicación detallada de cómo funciona y cómo evitar errores comunes, consulta el artículo sobre cómo verificar firmas HMAC‑SHA256 en tu backend. También es recomendable implementar un pequeño retardo o cola para procesar el evento de manera robusta, pero el concepto base es el que ves arriba.
El manejo correcto de la seguridad en una API de pagos va más allá del webhook. Dos prácticas que debes adoptar desde el inicio son:
Al generar un cobro, usas el header Idempotency-Key. Si una solicitud falla por timeout y la reenvías con la misma clave, el backend de la pasarela devuelve el resultado original sin crear un segundo cobro. Esto es vital para que el cliente no termine pagando dos veces sin querer. A nivel de tu aplicación, puedes guardar la clave en una tabla de idempotencia o simplemente confiar en que la pasarela maneja la semántica correcta. Para profundizar, te sugiero leer idempotencia en APIs de pago: por qué es crítica.
Como viste, verificas cada notificación antes de procesarla. Esto asegura que solo las peticiones originadas por la pasarela modifiquen el estado de tus órdenes. Nunca confíes únicamente en la IP de origen o en el contenido sin firma.
Toda la comunicación viaja sobre TLS 1.3 (HTTPS). Del lado de la pasarela, se cumple con la normativa PLAFT (prevención de lavado de activos) según los lineamientos de la SBS y UIF‑Perú. La infraestructura de pagos opera sobre la CCE del BCRP, lo que te da la tranquilidad de trabajar con un estándar supervisado. La pasarela TAYPI, por ejemplo, cuenta con certificaciones ISO 27001:2022 e ISO 9001:2015, lo que habla de un entorno maduro en seguridad y gestión de calidad.
La pasarela ofrece un entorno de pruebas (sandbox) donde los pagos no mueven dinero real. Debes usar las claves de sandbox y realizar al menos los siguientes casos:
Idempotency-Key para comprobar que no se duplica.Una vez que todo funcione, migras a las credenciales de producción. A partir de ese momento, cada pago confirmado seguirá el ciclo de liquidación: el dinero se deposita en tu cuenta bancaria CCI al día hábil siguiente (T+1), sin saldos retenidos en la pasarela. Para activar la liquidación necesitas completar el proceso de verificación KYB (comercio Nivel 2).
La comisión estándar por pago confirmado es de 2.50% + S/ 0.20 + IGV. Si quieres ver el desgloso con ejemplos y calcular cuánto recibes neto, revisa la página de precios de TAYPI. No hay costos de instalación, mensualidades ni permanencia, lo que hace que la integración sea rentable incluso para ticket bajo.
El QR dinámico expira en 15 minutos (o el tiempo que hayas configurado). Si el cliente intenta escanearlo después, el pago no se procesa. Tu sistema debe manejar este caso mostrando un mensaje apropiado y ofreciendo la opción de generar un nuevo cobro con la misma referencia.
No. La API devuelve el QR en base64, que puedes incrustar directamente en una etiqueta <img> con el prefijo data:image/png;base64,.... Solo necesitas axios para las peticiones HTTP y crypto (nativo de Node.js) para la verificación de firmas.
El webhook es el mecanismo principal, pero también puedes implementar un chequeo proactivo: guarda el payment_id obtenido al crear el cobro y, tras unos minutos, consulta el endpoint GET /api/v1/payments/{id} (si la pasarela lo ofrece) para obtener el estado. La confirmación vía webhook sigue siendo la opción más rápida y recomendada.
No, y no debes saltártelo. Si no verificas la firma, cualquiera podría enviar peticiones POST a tu endpoint simulando pagos. La verificación HMAC‑SHA256 es lo que te garantiza que la notificación proviene realmente de la pasarela. Es una capa de seguridad mínima y fácil de implementar con el código que te mostré.
Sí. El patrón es el mismo: llamada POST autenticada para crear el cobro y un endpoint que reciba el webhook. En entornos serverless (AWS Lambda, Vercel, etc.) la diferencia es que el manejador del webhook se expone como una función, pero el código de verificación y procesamiento es prácticamente el mismo.
Integrar pagos QR en Node.js y Express es un proyecto que puedes completar en pocas horas siguiendo la arquitectura descrita: generas un cobro con la API, muestras el QR al cliente, escuchas el webhook con verificación HMAC‑SHA256 y aplicas idempotencia para protegerte de duplicados. El resultado es un checkout rápido, compatible con todas las billeteras interoperables del Perú y libre de los contracargos que afectan a las tarjetas.
Si tu negocio ya tiene un backend en Node.js, tienes el 80 % del camino andado. La clave está en manejar bien los secretos, configurar correctamente el webhook y probar cada escenario en sandbox antes de recibir pagos reales. Con una pasarela de pagos QR que opera sobre los carriles del BCRP, como TAYPI, te beneficias de la liquidación T+1, la verificación bancaria directa y un modelo de comisiones transparente que solo cobra por transacción confirmada: sin mensualidades y sin montos retenidos.
Da el siguiente paso y automatiza los cobros de tu aplicación.
¿Listo para empezar? Crea tu cuenta gratis en TAYPI y activa tu entorno sandbox para hacer las primeras pruebas hoy mismo, sin costo de instalación ni compromiso de permanencia.
Crea tu cuenta gratis y genera tu primer QR en minutos.
Abrir cuenta gratis