Saltar al contenido principal

Códigos de error

Toda respuesta de error de la API es un ProblemDetail (RFC 9457) con una propiedad extra codigo, estable entre versiones. detail es para ti; codigo es para tu programa. type es urn:finora:pos:<área>:<codigo-en-kebab>.

{
"type": "urn:finora:pos:checkout:sesion-vencida",
"title": "Sesión vencida",
"status": 410,
"detail": "La sesión venció al intentar usarla",
"instance": "/api/v1/checkout/sesiones/CS…/medio",
"codigo": "SESION_VENCIDA"
}

Clave de API (X-API-Key)​

HTTPcodigoCuándoQué hacer
401CLAVE_INVALIDACabecera ausente, clave inexistente o revocada.Revisar la clave en Claves API.
403CLAVE_SIN_ACCESO_AL_COMERCIOEl merchantCode (o el de la intención / del terminal) no está en el alcance de la clave.No reintentar.
404COMERCIO_NO_ENCONTRADOmerchantCode inexistente.Corregir.

Checkout (servidor del comercio)​

HTTPcodigoCuándo
400PETICION_INVALIDAmonto ≤ 0, moneda no ISO, TTL fuera de 60–3600, URL no http(s), JSON ilegible.
403TERMINAL_AJENOEl terminal no es del comercio ni de una agencia suya.
404SESION_NO_ENCONTRADANo existe o no es del alcance de la clave (no se confirma cuál).
404TERMINAL_NO_ENCONTRADO, ENTREGA_NO_ENCONTRADA
409COBRO_EN_CURSOCancelar con autorización en vuelo o terminal que ya aceptó.

Checkout (token de cliente)​

HTTPcodigoCuándoQué hace el SDK
401TOKEN_INVALIDOToken ausente, inválido o de otra sesión; sesión inexistente.Pantalla de error final; finora-error.
410SESION_VENCIDAVenció al intentar usarla (queda EXPIRADA).Resultado EXPIRADA.
409SESION_CERRADAYa tiene veredicto, se canceló o expiró.Consulta /estado y muestra lo que diga.
409COBRO_EN_CURSOAutorización en vuelo u orden viva/aceptada.Espera y sondea.
400MEDIO_NO_DISPONIBLERiel inexistente/inactivo, efectivo, fuera de mín/máx, tarjeta sin terminal.Vuelve al selector con el aviso.
400DATOS_INCOMPLETOSFaltan o no validan los campos del riel; detail los lista con el code entre comillas simples.Marca los campos y enfoca el primero.
409TERMINAL_OCUPADOEl terminal ya tiene otra orden viva.Vuelve al selector.
429DEMASIADAS_PETICIONESMás de 60/min sobre la misma sesión (Retry-After: 60).El sondeo sigue a su ritmo.

Códigos de la sesión (no son errores HTTP)​

codigoCon estadoSignifica
TERMINAL_SIN_RESPUESTACREADAEl terminal no tomó la orden a tiempo; la sesión se reabre.
CANCELADA_EN_TERMINALCREADAEl operador canceló en el terminal.
CANCELADA_POR_CLIENTE / CANCELADA_POR_COMERCIOCANCELADAQuién la retiró.
DE 39 (00, 51…)APROBADA / RECHAZADA / EN_DUDARespuesta del banco. Tabla en Sandbox.

Cobro empujado y TMS​

HTTPcodigoCuándo
403DESPACHO_NO_PERMITIDOEl comercio no manda sobre ese terminal (regla D9).
404TERMINAL_NO_ENCONTRADO, INTENCION_NO_ENCONTRADA, COMANDO_NO_ENCONTRADO
409TERMINAL_OCUPADOOrden viva en el terminal. Reintentar más tarde o cancelar la anterior.
409ESTADO_INVALIDOIntención pagada, anulada, vencida, ya despachada o con cobro en duda.
409TRANSICION_INVALIDAAceptar sin entregar, cancelar tras aceptar.
400PAYLOAD_INVALIDOTipo de comando desconocido o payload incompleto.
403COMANDO_NO_PERMITIDOComando sensible sin autor; ACTIVAR sobre BLOQUEADO; COBRAR a suspendido.

Portal (Bearer del usuario)​

HTTPcodigoCuándo
401NO_AUTENTICADOSin token, vencido o usuario desactivado (la cadena responde 401 sin cuerpo).
403SIN_COMERCIO, FUERA_DE_ALCANCE, PERMISO_REQUERIDO, SIN_DERECHO_DE_DESCARGA, ENLACE_INVALIDO
404NO_ENCONTRADOTambién para recursos de otro comercio.
410ENLACE_VENCIDOEl enlace de descarga caducó: pide otro.
400 / 409PETICION_INVALIDA / CONFLICTO
503ARTEFACTO_NO_DISPONIBLE, REPORTE_NO_DISPONIBLE

Patrón recomendado​

const r = await fetch(url, opciones);
if (!r.ok) {
const p = await r.json().catch(() => ({}));
switch (p.codigo) {
case "TERMINAL_OCUPADO": return reintentarEn(30_000);
case "CLAVE_INVALIDA": return alertarOperaciones("clave revocada");
case "PETICION_INVALIDA": throw new Error(p.detail); // bug tuyo: no reintentar
default: throw new Error(`${r.status} ${p.codigo ?? "SIN_CODIGO"}`);
}
}