Ir al contenido
FINORA Academy
SistemaFINORA Checkout

FINORA Checkout · Tutorial

Integra FINORA Checkout en 10 minutos

ImplementadoImplementadoBásico10 min

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.