Cobro empujado
Tu software crea una intención de cobro en el Merchant y la empuja al terminal que elijas. El terminal (AuroraPOS) la recoge por long-poll, la acepta, cobra con tarjeta y reporta el veredicto. La venta se concilia en la sucursal donde el terminal estaba asignado ese día.
tu ERP ──POST /api/v1/pagos/intenciones { despacho: { terminalId: "TERM0042" } }──▶ Merchant ── cola por terminal
▲ │
terminal AuroraPOS (deviceToken) ── GET /api/v1/pos/ordenes/pendiente ──────────────────┘ ▼
── POST …/{ref}/aceptar · cobra con tarjeta · POST …/{ref}/resultado
Dos credenciales: tu X-API-Key (lado administrativo, /api/v1/pagos) y el deviceToken del terminal (lo tiene la app; tú no lo tocas).
Estados del despacho
PENDIENTE ──(sondeo)──▶ ENTREGADA ──(aceptar)──▶ ACEPTADA ──(resultado)──▶ COMPLETADA | EN_DUDA | CANCELADA
└── cancelar ────────┴──▶ CANCELADA └── ttl vencido ──▶ EXPIRADA (ACEPTADA no expira)
dispatchStatuses independiente del estado de la intención (CREADA,PAGADA…): uno dice si la orden llegó al equipo; el otro, si la venta se cobró.- Un cobro a la vez por terminal. Con una orden viva, el segundo despacho responde
409 TERMINAL_OCUPADO. - Vencer no es rechazar.
EXPIRADAdeja el terminal libre; si el veredicto llega tarde se registra igual.
Crear y empujar en un viaje
curl -s -X POST https://pos.finoracore.com/api/v1/pagos/intenciones \
-H "X-API-Key: $FINORA_API_KEY" -H "Content-Type: application/json" \
-d '{
"idempotencyKey": "ped-8841-2026-09-13",
"merchantCode": "COM00042",
"amount": 2499.00, "currency": "VES",
"description": "Pedido delivery", "externalOrderId": "PED-8841",
"despacho": { "terminalId": "TERM0042", "ttlSegundos": 300,
"pagador": { "tipoDocumento": "V", "documento": "12345678" } }
}'
const r = await fetch("https://pos.finoracore.com/api/v1/pagos/intenciones", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": process.env.FINORA_API_KEY },
body: JSON.stringify({
idempotencyKey: `ped-8841-${hoy}`, merchantCode: "COM00042",
amount: 2499.0, currency: "VES", description: "Pedido delivery", externalOrderId: "PED-8841",
despacho: { terminalId: "TERM0042", ttlSegundos: 300, pagador: { tipoDocumento: "V", documento: "12345678" } },
}),
});
const intencion = await r.json(); // 200 siempre (crear y reintentar se ven igual)
if (!r.ok) console.error(intencion.codigo, intencion.referencia); // TERMINAL_OCUPADO deja la intención creada
var cuerpo = """
{"idempotencyKey":"ped-8841-2026-09-13","merchantCode":"COM00042","amount":2499.00,"currency":"VES",
"description":"Pedido delivery","externalOrderId":"PED-8841",
"despacho":{"terminalId":"TERM0042","ttlSegundos":300,"pagador":{"tipoDocumento":"V","documento":"12345678"}}}
""";
var req = HttpRequest.newBuilder(URI.create("https://pos.finoracore.com/api/v1/pagos/intenciones"))
.header("Content-Type", "application/json").header("X-API-Key", System.getenv("FINORA_API_KEY"))
.POST(HttpRequest.BodyPublishers.ofString(cuerpo)).build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
var res = await http.PostAsJsonAsync("https://pos.finoracore.com/api/v1/pagos/intenciones", new {
idempotencyKey = "ped-8841-2026-09-13", merchantCode = "COM00042",
amount = 2499.00m, currency = "VES", description = "Pedido delivery", externalOrderId = "PED-8841",
despacho = new { terminalId = "TERM0042", ttlSegundos = 300, pagador = new { tipoDocumento = "V", documento = "12345678" } },
});
r = requests.post("https://pos.finoracore.com/api/v1/pagos/intenciones", headers={"X-API-Key": KEY}, json={
"idempotencyKey": "ped-8841-2026-09-13", "merchantCode": "COM00042",
"amount": 2499.00, "currency": "VES", "description": "Pedido delivery", "externalOrderId": "PED-8841",
"despacho": {"terminalId": "TERM0042", "ttlSegundos": 300, "pagador": {"tipoDocumento": "V", "documento": "12345678"}},
})
intencion = r.json()
$res = json_decode(finora_post("/api/v1/pagos/intenciones", [
"idempotencyKey" => "ped-8841-2026-09-13", "merchantCode" => "COM00042",
"amount" => 2499.00, "currency" => "VES", "description" => "Pedido delivery", "externalOrderId" => "PED-8841",
"despacho" => ["terminalId" => "TERM0042", "ttlSegundos" => 300, "pagador" => ["tipoDocumento" => "V", "documento" => "12345678"]],
]), true);
| Campo | Obligatorio | Notas |
|---|---|---|
idempotencyKey | sí | Repetirla devuelve la misma intención: reintenta sin miedo tras un corte de red. |
merchantCode | sí | Tu comercio (COM00042) o una agencia dentro de tu alcance. |
amount, currency | sí | Unidades con decimales (2499.00). |
despacho.terminalId | sí (si hay bloque) | TID destino; no tiene que ser móvil. |
despacho.ttlSegundos | no | 300 por defecto, máximo 900. |
despacho.pagador | no | Se copia a la intención solo si no traía documento. |
Respuesta 200: la IntencionResponse con reference, state, amount, pendingAmount, rails[], attempts[] y el bloque despacho:
{ "reference": "FNRLZ3K9Q1", "state": "CREADA", "amount": 2499.00, "capturedAmount": 0, "pendingAmount": 2499.00, "currency": "VES",
"despacho": { "estado": "PENDIENTE", "terminalId": "TERM0042", "outletStoreCode": "SUC001",
"despachadoEn": "…", "entregadoEn": null, "aceptadoEn": null, "expiraEn": "…", "codigoResultado": null, "mensajeResultado": null } }
Si el despacho falla, la intención queda creada y el error trae su referencia para reintentar:
{ "type": "urn:finora:pos:despacho:terminal-ocupado", "title": "El terminal ya tiene una orden en curso", "status": 409,
"codigo": "TERMINAL_OCUPADO", "referencia": "FNRLZ3K9Q1" }
Seguir, reintentar y cancelar
| Método | Ruta | Qué hace |
|---|---|---|
GET | /api/v1/pagos/intenciones/{ref} | La intención con su bloque despacho (o null si no fue empujada). |
GET | /api/v1/pagos/intenciones/{ref}/despacho | Solo el bloque. |
POST | /api/v1/pagos/intenciones/{ref}/despacho {terminalId, ttlSegundos?} | Empuja una intención ya creada (reintento tras TERMINAL_OCUPADO, o a otro motorizado tras EXPIRADA). Idempotente. |
POST | /api/v1/pagos/intenciones/{ref}/despacho/cancelar | Solo con PENDIENTE o ENTREGADA; tras ACEPTADA el motorizado puede estar cobrando (409 TRANSICION_INVALIDA). |
curl -s https://pos.finoracore.com/api/v1/pagos/intenciones/FNRLZ3K9Q1/despacho -H "X-API-Key: $FINORA_API_KEY"
curl -s -X POST https://pos.finoracore.com/api/v1/pagos/intenciones/FNRLZ3K9Q1/despacho/cancelar -H "X-API-Key: $FINORA_API_KEY"
curl -s -X POST https://pos.finoracore.com/api/v1/pagos/intenciones/FNRLZ3K9Q1/despacho -H "X-API-Key: $FINORA_API_KEY" \
-H "Content-Type: application/json" -d '{"terminalId":"MOTO0009"}'
Cuando el terminal reporta APROBADA, la intención pasa a PAGADA y el despacho a COMPLETADA con codigoResultado: "00". Sondea GET …/despacho cada pocos segundos o, si prefieres un aviso, crea la venta como sesión de checkout con terminalId y webhookUrl: es el mismo despacho con webhook firmado.
Asignación diaria del terminal (motorizados)
outlet(venta) = asignación(terminal, fecha local) ?? terminal.outlet. Un terminal fijo no necesita asignaciones; solo se registra lo que se mueve.
# Hoy el motorizado sale de la sucursal SUC001
curl -s -X POST https://pos.finoracore.com/api/v1/pagos/asignaciones -H "X-API-Key: $FINORA_API_KEY" -H "Content-Type: application/json" \
-d '{"terminalId":"TERM0042","storeCode":"SUC001","fecha":"2026-09-13","asignadoPor":"supervisor.perez"}'
curl -s "https://pos.finoracore.com/api/v1/pagos/asignaciones?terminalId=TERM0042" -H "X-API-Key: $FINORA_API_KEY" # historial
Upsert por (terminal, fecha); terminal y tienda deben ser del mismo comercio.
Alcance de la clave (regla D9)
Tu clave identifica a tu comercio y de ahí sale sobre qué comercios puede actuar: un principal, sobre sí mismo y sus agencias; una agencia, solo sobre sí misma. Empujar a un terminal fuera del alcance → 403 DESPACHO_NO_PERMITIDO. Una clave filtrada compromete un comercio, no la plataforma: revócala en Claves API y crea otra.
Errores
| HTTP | codigo | Qué hacer |
|---|---|---|
| 401 | CLAVE_INVALIDA | Revisar la clave. |
| 403 | CLAVE_SIN_ACCESO_AL_COMERCIO, DESPACHO_NO_PERMITIDO | No reintentar; el comercio o el terminal están fuera de tu alcance. |
| 404 | TERMINAL_NO_ENCONTRADO, INTENCION_NO_ENCONTRADA | Corregir TID o referencia. |
| 409 | TERMINAL_OCUPADO | Reintentar más tarde o cancelar la orden anterior. |
| 409 | ESTADO_INVALIDO | Intención pagada, anulada, vencida, ya despachada a otro terminal o con cobro en duda. |
| 409 | TRANSICION_INVALIDA | Cancelar tras aceptar, aceptar sin entregar. Consultar el estado. |
Para gobernar los terminales (estado, parámetros, suspender, mensajes) mira TMS.