Skip to navigation

Terminal Sessions

Terminal Sessions te permite enviar cobros a una terminal POS de Recurrente directamente desde tu sistema (ERP, POS propio, o cualquier integración). Tu sistema envía un comando con el monto, y la terminal automáticamente muestra la pantalla de cobro para que el cliente pague con tarjeta.

Casos de uso

  • Integración ERP/POS: Tu sistema de facturación o punto de venta genera el cobro, y la terminal de Recurrente lo procesa.
  • Cobros remotos: Envía cobros a terminales en diferentes ubicaciones desde un sistema centralizado.
  • Automatización: Elimina la necesidad de ingresar montos manualmente en la terminal.

Requisitos previos

  • Una cuenta de Recurrente con llaves de API
  • Al menos una terminal POS vinculada a tu cuenta
  • El ID de tu terminal (lo encuentras en POS en tu dashboard — haz clic en una terminal para ver su ID)

Configurar la terminal en modo espera

Para que la terminal reciba comandos de tu sistema, debe estar en la pantalla de espera. Esta pantalla muestra “Listo para cobrar” y espera automáticamente por nuevos comandos.

  1. En tu dashboard, ve a POS
  2. Haz clic en la terminal que quieres configurar
  3. En la página de detalle, haz clic en Modo espera (en la esquina superior derecha)
  4. La terminal mostrará la pantalla de espera

Cuando tu sistema envíe un comando, la terminal automáticamente muestra la pantalla de cobro. Después de cada pago (exitoso o fallido), la terminal regresa al modo espera para recibir el siguiente comando.

En modo espera, el operador de la terminal no puede ingresar montos manualmente. Todos los cobros se controlan desde tu sistema vía API.

Flujo de integración

Tu Sistema Recurrente Terminal POS
│ │ │
│ POST /terminal_session_ │ │
│ commands │ │
│ {terminal_id, amount, │ │
│ currency, external_id} │ │
│─────────────────────────────>│ │
│ │ Crea checkout + comando │
│ │ (status: pending) │
│ {id, status, checkout_url} │ │
│<─────────────────────────────│ │
│ │ │
│ │ Terminal levanta comando │
│ │ (status: dispatched) │
│ │─────────────────────────────>│
│ │ │
│ │ Cliente paga │
│ │ con tarjeta
│ │ │
│ Webhook: │ │
│ payment_intent.succeeded │ │
│<─────────────────────────────│ │

1. Crear un comando de terminal

Envía un POST a /api/terminal_session_commands con el monto y la terminal destino.

curl -X POST https://app.recurrente.com/api/terminal_session_commands \
-H "X-SECRET-KEY: tu_llave_secreta" \
-H "Content-Type: application/json" \
-d '{
"terminal_id": "trm_abc123",
"amount_in_cents": 5000,
"currency": "GTQ",
"external_id": "order-1234"
}'

Respuesta

  • 201 Created — se creó un nuevo comando
  • 200 OK — ya existe un comando activo con el mismo external_id (se retorna el existente)

Si recibes 200 y el status es dispatched, significa que el comando ya fue levantado por la terminal. Usa un nuevo external_id para crear un comando nuevo.

{
"id": 42,
"external_id": "order-1234",
"status": "pending",
"terminal_id": "trm_abc123",
"amount_in_cents": 5000,
"currency": "GTQ",
"checkout_id": "ch_xyz789",
"checkout_url": "https://app.recurrente.com/checkout-session/ch_xyz789"
}

Parámetros

ParámetroTipoRequeridoDescripción
terminal_idstringSiID de la terminal POS
amount_in_centsintegerSi*Monto en centavos (ej. 5000 = Q50.00)
amountnumberSi*Monto en unidades (ej. 50.00). Alternativa a amount_in_cents
currencystringSiMoneda: GTQ o USD
external_idstringSiID de tu sistema para este cobro (para idempotencia)
descriptionstringNoConcepto del cobro (ej. número de orden). Aparece en el recibo del cliente y en la actividad del comercio. No combinable con items
itemsarrayNoProductos existentes a cobrar: [{price_id | product_id, quantity}]. El total se calcula de los items — no envíes amount ni description. Solo productos de pago único en la moneda del cobro
tax_idstringNoNIT del comprador, si tu sistema ya lo tiene. La terminal se salta la pantalla del NIT y abre la de comprobantes con la factura emitida

Envía amount_in_cents o amount, no ambos. Si envías amount, se convierte automáticamente a centavos.

2. Recibir el resultado del pago

Registra un webhook endpoint para recibir notificaciones cuando el pago se complete.

Escucha el evento payment_intent.succeeded:

{
"event": "payment_intent.succeeded",
"data": {
"checkout": {
"id": "ch_xyz789",
"metadata": {
"source": "terminal_session_webhook",
"terminal_id": "trm_abc123",
"external_id": "order-1234"
}
},
"amount_in_cents": 5000,
"currency": "GTQ"
}
}

Usa el campo metadata.external_id para relacionar el pago con la orden en tu sistema.

Imprimir comprobantes

Después de un cobro aprobado la terminal imprime la copia del cliente si mandaste print_receipt: true. Para la copia del comercio —o para reimprimir una venta anterior— manda un comando de impresión aparte:

curl -X POST https://app.recurrente.com/api/terminal_print_commands \
-H "X-SECRET-KEY: $RECURRENTE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"terminal_id": "trm_abc123",
"checkout_id": "ch_xyz789",
"copy": "merchant",
"external_id": "order-1234-merchant-copy"
}'

La terminal imprime sin salir de Modo espera, así que el siguiente cobro entra igual. El checkout_id viene de la respuesta del comando de cobro.

ParámetroTipoRequeridoDescripción
terminal_idstringSiID de la terminal POS
checkout_idstringSiID del checkout de la venta a imprimir
copystringNomerchant (default) o customer
duplicatebooleanNoImprime DUPLICADO en el papel. Úsalo al reimprimir una copia que ya salió
external_idstringNoID de tu sistema, para idempotencia

La impresión es best-effort: llega a una terminal en Modo espera y el resultado no se expone por API. Nunca afecta el estado financiero del pago.

Estados del comando

EstadoDescripcion
pendingCreado, esperando que la terminal lo levante
dispatchedLa terminal recibio el comando y esta procesando el cobro
consumedEl cobro se completo exitosamente
supersededReemplazado por un comando mas reciente a la misma terminal
failedEl cobro fallo

Idempotencia

El external_id es unico por cuenta. Si envias el mismo external_id dos veces:

  • Si el comando anterior esta activo (pending o dispatched), se retorna el comando existente.
  • Si el comando anterior fue superseded o failed, se retorna el comando existente sin crear uno nuevo.

Esto te permite reintentar requests de forma segura sin crear cobros duplicados.

Superseding (reemplazo de comandos)

Si envias un nuevo comando a la misma terminal con un external_id diferente, todos los comandos pendientes anteriores para esa terminal se marcan como superseded. La terminal solo procesa el comando mas reciente.

Esto es util cuando:

  • El cliente cambia el monto de la orden
  • Necesitas cancelar un cobro y enviar uno nuevo
  • Tu sistema reintenta con un nuevo ID de orden

Ejemplo completo

import requests
API_URL = "https://app.recurrente.com/api"
HEADERS = {
"X-SECRET-KEY": "tu_llave_secreta",
"Content-Type": "application/json"
}
def send_to_terminal(terminal_id, amount, currency, order_id):
"""Envía un cobro a una terminal POS."""
response = requests.post(
f"{API_URL}/terminal_session_commands",
headers=HEADERS,
json={
"terminal_id": terminal_id,
"amount": amount,
"currency": currency,
"external_id": order_id
}
)
return response.json()
# Enviar cobro de Q150.00 a la terminal
result = send_to_terminal("trm_abc123", 150.00, "GTQ", "order-5678")
print(f"Comando creado: {result['status']}")