Crear un comando de terminal

Envía un comando de cobro a una terminal POS. Recurrente crea un checkout y lo despacha a la terminal indicada. Si ya existe un comando activo con el mismo `external_id`, retorna el comando existente en vez de crear uno nuevo (idempotencia). Cuando la terminal recibe el comando, muestra automáticamente la pantalla de cobro para que el cliente pague con tarjeta. **Usa una llave LIVE para una terminal física.** Una llave TEST heredada que todavía apunta a la cuenta LIVE se rechaza con `403 terminal_test_key_requires_sandbox`, antes de crear el checkout o mover dinero. Las llaves TEST solo se aceptan cuando la solicitud ya está aislada dentro de un Sandbox; ese flujo no contacta hardware ni procesadores reales. La terminal debe estar en **Modo espera** y reportando disponibilidad. Si no lo está, la API responde `409` con `code: terminal_not_in_standby` sin crear un checkout nuevo. Los reintentos con un `external_id` existente conservan la idempotencia y retornan el comando original. Una respuesta exitosa incluye `terminal_availability`. ### Flujo 1. Tu sistema envía `POST /api/terminal_session_commands` con el monto, moneda y terminal. 2. Recurrente crea un checkout y un comando en estado `pending`. 3. La terminal levanta el comando y lo pasa a `dispatched`. 4. El cliente paga en la terminal. 5. Recibes un webhook `payment_intent.succeeded` con el resultado. También puedes consultar el comando con `GET /api/terminal_session_commands/{random_id}`. Los estados definitivos son `canceled`, `superseded`, `consumed` y `failed`; `pending`, `dispatched` y `cancel_requested` todavía pueden cambiar. ### Idempotencia Si envías dos requests con el mismo `external_id` dentro de la misma cuenta, el segundo retorna el comando original sin crear uno duplicado. Dos cuentas distintas pueden usar el mismo `external_id`. Esto te permite reintentar de forma segura. ### Superseding Si envías un nuevo comando a la misma terminal (con un `external_id` diferente), los comandos anteriores pendientes se marcan como `superseded` y la terminal solo procesa el más reciente. ### Meses sin intereses (installments) Si quieres que el cobro se procese en cuotas, envía `installments` con el número de meses. Solo aplica a cobros en `GTQ` y los valores permitidos son `3`, `6`, `12` o `18` (algunas cuentas tienen configuraciones distintas). Si la tarjeta del cliente no soporta la opción elegida, el cobro se rechaza con `unsupported_installments`. ### Pantallas post-pago Por defecto, después de un pago exitoso la terminal muestra las pantallas para solicitar NIT, correo y teléfono. Envía `show_post_payment_screens: false` para omitirlas y volver automáticamente a Modo espera. Recurrente emite la factura como C/F cuando corresponde y adelanta el webhook y los correos que normalmente esperan a que el comprador termine esas pantallas. Si el monto y la configuración de facturación hacen obligatorio un NIT válido, Recurrente conserva las pantallas aunque envíes `false`. ### Cuentas conectadas Para originar el cobro desde una plataforma y registrarlo en una cuenta hija: 1. Autentica el request con la llave LIVE de la plataforma en `X-SECRET-KEY` y envía el ID `ac_...` de la cuenta hija en `X-ACCOUNT-ID`. 2. Obtén el `terminal_id` público (`trm_...`) en el panel de la cuenta hija, en **POS → detalle de la terminal**, y guárdalo en tu configuración. Actualmente no existe un endpoint público para listar terminales. 3. Confirma que el dispositivo inició sesión en esa misma cuenta hija y está en **Modo espera**. Un pinpad emparejado con la plataforma es invisible para la hija (y viceversa). 4. Envía el comando con `terminal_id`, monto, moneda y un `external_id` único de tu sistema. La respuesta incluye el `id` del comando (`tsc_...`), `checkout_id`, `status` y `terminal_availability`. El checkout, el pago y la factura se crean bajo la cuenta hija. La plataforma recibe `payment_intent.succeeded` con `connected: true` y el `account_id` de la hija; si la hija también tiene un webhook endpoint, Recurrente entrega el evento a ambos. Usa `checkout.metadata.external_id` para conciliar la orden original, `checkout.metadata.terminal_id` para identificar el dispositivo y `tax_invoice_url` para recuperar la factura cuando exista. Cuando algo no calza, el 404 incluye un `code` que identifica cuál de las tres cosas falta: | `code` | Qué revisar | |---|---| | `connected_account_not_found` | La cuenta que enviaste no está conectada a la tuya (o es nieta, no hija directa). | | `terminal_not_found` | La terminal no está asociada a la cuenta que va a cobrar. | | `connected_account_mismatch` | Enviaste `account_id` de una hermana distinta a la del header `X-ACCOUNT-ID`. | | `recipient_not_found` | El `recipient_id` de un `transfer_setup` no es tu cuenta ni una hija conectada. |

Authentication

X-SECRET-KEYstring
Tu clave secreta de API. Una llave de cuenta (`sk_live_...`, `sk_test_...`) opera sobre su propia cuenta y, con `X-ACCOUNT-ID`, sobre sus cuentas conectadas. Una llave de organización (`sk_org_live_...`, `sk_org_test_...`) alcanza todas las cuentas de una organización y solo sirve para leer: saldos, movimientos y reportes. Para operar sobre una cuenta debe nombrarla con `X-ACCOUNT-ID`; omitirlo en una lectura devuelve todas las cuentas de la organización. Cualquier otro endpoint responde `403` con `code: organization_key_unsupported`.

Request

This endpoint expects an object.
terminal_idstringRequired

ID público trm_... de la terminal POS donde se enviará el cobro. Cópialo desde POS → detalle de la terminal en la cuenta que va a cobrar; actualmente no existe un endpoint público para listar terminales.

currencyenumRequired
Moneda del cobro
Allowed values:
external_idstringRequired

ID único de tu sistema para este cobro dentro de la cuenta autenticada. Se usa para idempotencia — si envías el mismo external_id dos veces en esa cuenta, no se crea un duplicado.

amount_in_centsintegerOptional

Monto a cobrar en centavos. Envía amount_in_cents o amount, no ambos.

amountdoubleOptional

Monto a cobrar en unidades (ej. 50.00). Alternativa a amount_in_cents.

installmentsenumOptional

Número de meses sin intereses. Solo válido con currency: GTQ. Valores permitidos por defecto [3, 6, 12, 18] (puede variar por cuenta).

Allowed values:
show_post_payment_screensbooleanOptionalDefaults to true

Muestra las pantallas post-pago de NIT, correo y teléfono. Envía false para omitirlas y volver a Modo espera, salvo cuando un NIT válido sea obligatorio.

transfer_setupslist of objectsOptional

(Opcional) Transferencias a ejecutar tras un cobro exitoso. Úsalo para enrutar fondos a una cuenta conectada (modelo destino) o para cobrar una comisión a una subcuenta. El destinatario debe ser tu cuenta o una cuenta conectada.

Response

Comando creado exitosamente
idstringOptional

ID público aleatorio del comando

external_idstringOptional

ID único de tu sistema dentro de la cuenta autenticada

statusenumOptional
Estado del comando
finalbooleanOptional

true cuando el comando alcanzó un resultado definitivo y ya no puede cambiar

terminal_idstringOptional
ID de la terminal POS
amount_in_centsintegerOptional
Monto en centavos
currencyenumOptional
Moneda del cobro
installmentsinteger or nullOptional

Meses sin intereses solicitados, o null si el cobro va sin cuotas

show_post_payment_screensbooleanOptional

Indica si el comando solicita mostrar las pantallas post-pago de NIT, correo y teléfono

terminal_availabilityenumOptional
Disponibilidad observada de la terminal para recibir comandos
checkout_idstringOptional
ID del checkout generado
checkout_urlstringOptionalformat: "uri"

URL del checkout (la terminal usa esta URL internamente)

checkout_statusenumOptional
Estado actual del checkout asociado
cancellation_requested_atdatetime or nullOptional

Momento en que se solicitó detener un comando ya despachado

canceled_atdatetime or nullOptional

Momento en que la cancelación se volvió definitiva

Errors

403
Forbidden Error
404
Not Found Error
409
Conflict Error
422
Unprocessable Entity Error