FINORA Merchant · Tutorial
Cobro en la nube por API y webhooks
This content is not available in your language yet.
Objetivo
Sección titulada «Objetivo»Cobrar un pedido desde tu servidor, sin app en el teléfono del motorizado: creas la venta, el Merchant la entrega a un datáfono, el cliente paga y tu servidor se entera por un webhook firmado. Es el cobro en la nube (en inglés, cloud payments).
Qué necesitas
Sección titulada «Qué necesitas»- La clave de API del comercio con alcance
INTEGRACIONoCOMPLETO, guardada en tu servidor (variableFINORA_API_KEY). Nunca en la app ni en el repositorio. - La URL del Merchant que FINORA asigna a tu comercio, en la variable
FINORA_URL(<URL_DEL_MERCHANT>). - El código del comercio (por ejemplo
COM00042) y el identificador del datáfono (TERM0042). - Una URL
httpspública de tu servidor para recibir avisos.
-
Suscríbete al resultado una sola vez. Guarda el
secretode la respuesta: solo se muestra ahí.Ventana de terminal curl -s -X POST /api/v1/pagos/webhooks/suscripciones \-H "X-API-Key: $FINORA_API_KEY" -H "Content-Type: application/json" \-d '{"merchantCode":"COM00042","nombre":"Despacho","url":"https://despacho.ejemplo.com/finora/eventos","eventos":["intencion.resultado"]}' -
Crea la venta y envíala al datáfono en un solo viaje:
Ventana de terminal curl -s -X POST /api/v1/pagos/intenciones \-H "X-API-Key: $FINORA_API_KEY" -H "Content-Type: application/json" \-d '{"idempotencyKey":"ped-8841-2026-10-21","merchantCode":"COM00042","amount":2499.00,"currency":"VES","description":"Pedido a domicilio","externalOrderId":"PED-8841","despacho":{"terminalId":"TERM0042","ttlSegundos":300}}'La respuesta trae la
reference(FNR…) y el bloquedespachoenPENDIENTE. Si se cae la red, repite con la mismaidempotencyKey: recibes la misma venta. -
Síguela mientras llega el webhook (opcional):
Ventana de terminal curl -s /api/v1/pagos/intenciones/FNRLZ3K9Q1/despacho -H "X-API-Key: $FINORA_API_KEY"El despacho pasa por
PENDIENTE → ENTREGADA → ACEPTADA → COMPLETADA(oEN_DUDA,CANCELADA,EXPIRADA). -
Cancela si el cliente se arrepiente, solo antes de que el datáfono la acepte:
Ventana de terminal curl -s -X POST /api/v1/pagos/intenciones/FNRLZ3K9Q1/despacho/cancelar \-H "X-API-Key: $FINORA_API_KEY" -
Recibe el webhook y verifica la firma. Llega un
POSTconX-Finora-Evento: intencion.resultado,X-Finora-EntregayX-Finora-Signature: t=…,v1=…. Verifica sobre el cuerpo crudo:import crypto from 'node:crypto';function firmaValida(cuerpoCrudo, cabecera, secreto) {const p = Object.fromEntries(cabecera.split(',').map((x) => x.split('=')));if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false; // ventana de 5 minutosconst esperado = crypto.createHmac('sha256', secreto).update(`${p.t}.${cuerpoCrudo}`).digest('hex');return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(p.v1 ?? ''));}Responde
2xxrápido y deduplica porX-Finora-Entrega: si no respondes, se reintenta a 1, 5, 25 y 125 minutos.
Ejemplo de aviso
Sección titulada «Ejemplo de aviso»{ "evento": "intencion.resultado", "externalId": "PED-8841", "sucursal": { "storeCode": "SUC001" }, "referenciaFinora": "FNR8Q2LM4K", "estado": "APROBADA", "monto": 2499.00, "moneda": "VES", "riel": "TARJETA", "autorizacion": "A1B2C3", "rrn": "622916125320", "terminalId": "TERM0042", "tarjeta": { "marca": "VISA", "ultimos4": "0015" }}estado puede ser APROBADA, RECHAZADA, EN_DUDA, ANULADA, EN_CURSO o PENDIENTE. Como en el SDK,
EN_DUDA no es un rechazo.
Reglas que conviene recordar
Sección titulada «Reglas que conviene recordar»| Regla | Consecuencia |
|---|---|
| Un cobro a la vez por datáfono | Un segundo envío responde 409 TERMINAL_OCUPADO, pero la venta queda creada: reenvíala más tarde con POST …/despacho |
| Vencer no es rechazar | EXPIRADA libera el datáfono; reenvía la misma venta a otro motorizado |
| La clave tiene alcance | Un comercio principal actúa sobre sí y sus agencias; fuera de eso, 403 DESPACHO_NO_PERMITIDO |
| Un error tuyo no deshace el cobro | Los avisos salen después de registrar el veredicto |
Verificación
Sección titulada «Verificación»- La suscripción aparece en
GET /api/v1/pagos/webhooks/suscripciones?merchantCode=COM00042(sin el secreto). - Tras crear la venta,
GET …/despachomuestraPENDIENTEy luegoENTREGADAcuando el datáfono la recoge. - Con el cobro aprobado, tu servidor recibe
intencion.resultadoconestado = APROBADA, la firma verifica y elexternalIdes tu pedido. - Repetir el paso 2 con la misma
idempotencyKeydevuelve la mismareference.
Siguiente: Lección 4 · Sucursal de origen, conciliación y portal en vivo