Skip to content
FINORA Academy
SistemaFINORA Checkout

FINORA Checkout · Tutorial

Integra FINORA Checkout en 10 minutos

ImplementedImplementedBeginner10 min

This content is not available in your language yet.

Al terminar este tutorial tendrás un pedido de prueba pagado en tu tienda: tu servidor abre una sesión de cobro, el cliente paga en la página de FINORA y tu servidor recibe un aviso firmado que marca el pedido como pagado. Todo contra el banco simulado: no se mueve dinero.

Lo que vas a construir tiene cuatro piezas, y solo dos son código tuyo:

sequenceDiagram
    autonumber
    participant S as Tu servidor
    participant F as FINORA
    participant C as Navegador del cliente
    S->>F: Crea la sesión (monto, pedido) con tu clave API
    F-->>S: sesionId + token de cliente + urlPago
    S-->>C: Solo sesionId y token (o la urlPago)
    C->>F: Elige el medio y paga
    F->>S: Aviso firmado (webhook) con el veredicto
    C-->>C: Vuelve a tu página de «gracias»

La clave API nunca va al navegador: quien la tenga puede cobrar en nombre de tu comercio. Por eso la sesión la abre tu servidor. Pide el monto y la referencia de tu pedido, y marca sandbox para que todo corra contra el banco simulado.

Ventana de terminal
curl -s -X POST "<URL_DEL_MERCHANT>/api/v1/checkout/sesiones" \
-H "X-API-Key: <TU_CLAVE_API>" \
-H "Content-Type: application/json" \
-d '{
"monto": 227.00,
"moneda": "VES",
"referencia": "PED-1042",
"descripcion": "Pan campesino y café",
"returnUrl": "https://mitienda.example/gracias",
"webhookUrl": "https://mitienda.example/webhooks/finora",
"sandbox": true
}'

FINORA responde 201 con algo así:

{
"sesionId": "CS7Hq2mVx0Ld",
"token": "…",
"expiraEn": "2026-10-04T15:20:00Z",
"urlPago": "https://pago.finoracore.com/s/CS7Hq2mVx0Ld?t=…",
"estado": "CREADA",
"medios": [ … ]
}

Guarda el sesionId junto a tu pedido. El token es de un solo uso: sirve solo para esta sesión, no puede cambiar el monto y vence con ella (en 15 minutos si no dices otra cosa). FINORA no lo vuelve a mostrar nunca.

La forma más corta es redirigirlo a urlPago, la página alojada de FINORA. No hay nada que instalar.

const { urlPago } = await fetch('/api/pagos', { method: 'POST', /* … */ }).then((r) => r.json());
location.href = urlPago;

El cliente ve los medios que tu comercio tiene habilitados y elige uno:

El selector de medios. El rótulo «Pruebas» indica una sesión de sandbox. Captura del servidor simulado del SDK con un comercio ficticio.

Para este tutorial, elige Pago móvil C2P y llena los datos de prueba:

CampoValor de prueba
Cédula00000001
Teléfono04140000001
Banco0001
Clave de pago123456
Los campos los define el medio de pago, no tu página. La clave de pago viaja al banco y no se guarda.

Pulsa Pagar. Con un monto que termina en .00, el banco simulado aprueba:

El recibo que ve el cliente. Tu servidor recibe los mismos datos en el aviso firmado.

Cuando la sesión se cierra, FINORA hace un POST a tu webhookUrl. Ese aviso es lo que confirma el pedido, no la vuelta del cliente a tu página: cualquiera puede escribir ?estado=APROBADA en una URL, pero nadie puede falsificar la firma sin tu secreto.

import crypto from 'node:crypto';
function firmaValida(secreto, cuerpoCrudo, cabecera) {
const partes = Object.fromEntries((cabecera ?? '').split(',').map((p) => p.trim().split('=')));
const t = Number(partes.t);
if (!Number.isFinite(t) || !partes.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > 300) return false; // aviso viejo o reenviado: fuera
const esperada = crypto.createHmac('sha256', secreto).update(`${t}.`).update(cuerpoCrudo).digest('hex');
const a = Buffer.from(partes.v1);
const b = Buffer.from(esperada);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// express.raw: la firma se calcula sobre el cuerpo tal como llegó, no sobre el JSON ya interpretado.
app.post('/webhooks/finora', express.raw({ type: 'application/json' }), (req, res) => {
if (!firmaValida(process.env.FINORA_SECRETO_WEBHOOK, req.body, req.get('X-Finora-Signature'))) {
return res.sendStatus(401);
}
if (yaProcesado(req.get('X-Finora-Entrega'))) return res.sendStatus(200);
const aviso = JSON.parse(req.body.toString('utf8'));
if (aviso.evento === 'checkout.sesion') {
marcarPedido(aviso.referencia, aviso.estado); // APROBADA, RECHAZADA, EN_DUDA, CANCELADA o EXPIRADA
}
res.sendStatus(200);
});

Tres detalles que evitan los errores de siempre:

  • Responde 2xx en menos de 10 segundos. Si no, FINORA reintenta a 1, 5, 25 y 125 minutos.
  • Usa X-Finora-Entrega para no procesar dos veces el mismo aviso.
  • Ante EN_DUDA no entregues ni cobres otra vez: llegará un segundo aviso con el desenlace.

Para probar tu endpoint sin pagar nada, pide un aviso de prueba:

Ventana de terminal
curl -s -X POST "<URL_DEL_MERCHANT>/api/v1/checkout/webhooks/prueba" \
-H "X-API-Key: <TU_CLAVE_API>" -H "Content-Type: application/json" \
-d '{"url": "https://mitienda.example/webhooks/finora"}'

La respuesta trae la firma y el cuerpo que se enviaron, para que compares con lo que calculó tu servidor. El detalle está en Verifica la firma del aviso.

Al terminar, el cliente vuelve a tu returnUrl con ?sesion=…&estado=…. Esa información sirve para mostrar el mensaje adecuado, pero antes de decir «pedido confirmado», pregúntale a tu servidor, que ya recibió el aviso.

const sesion = new URLSearchParams(location.search).get('sesion');
const { estado } = await fetch(`/api/pedidos/estado?sesion=${sesion}`).then((r) => r.json());
mostrar(estado === 'APROBADA' ? 'Gracias por tu compra' : 'Revisamos tu pago');

En sandbox, los céntimos del monto deciden el veredicto. Crea otra sesión con 20.51 y verás un rechazo por fondos insuficientes. Con 10.96 verás «No pudimos confirmar el pago», que es el caso EN_DUDA. La tabla completa está en Prueba cada veredicto en sandbox.

  • Tu clave API se quedó en tu servidor.
  • Tu página nunca vio un número de tarjeta ni una clave de pago.
  • El pedido se confirmó por un aviso firmado que puedes verificar.

Siguiente paso: entiende cómo funciona una sesión de cobro y sus estados, o sigue la ruta del desarrollador de comercio electrónico.