> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recurrente.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recurrente.com/_mcp/server.

# Terminal Sessions

> Envía cobros a terminales POS de Recurrente desde tu sistema

**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.

> **Note**
>
> 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.

```bash
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.

```json
{
  "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ámetro         | Tipo    | Requerido | Descripción                                                                                                                                                                                        |
| ----------------- | ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `terminal_id`     | string  | Si        | ID de la terminal POS                                                                                                                                                                              |
| `amount_in_cents` | integer | Si\*      | Monto en centavos (ej. 5000 = Q50.00)                                                                                                                                                              |
| `amount`          | number  | Si\*      | Monto en unidades (ej. 50.00). Alternativa a `amount_in_cents`                                                                                                                                     |
| `currency`        | string  | Si        | Moneda: `GTQ` o `USD`                                                                                                                                                                              |
| `external_id`     | string  | Si        | ID de tu sistema para este cobro (para idempotencia)                                                                                                                                               |
| `description`     | string  | No        | Concepto del cobro (ej. número de orden). Aparece en el recibo del cliente y en la actividad del comercio. No combinable con `items`                                                               |
| `items`           | array   | No        | Productos 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_id`          | string  | No        | NIT 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                                                            |

> **Note**
>
> 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](/referencia-api/api-reference/webhook-endpoints/create-webhook-endpoint) para recibir notificaciones cuando el pago se complete.

Escucha el evento `payment_intent.succeeded`:

```json
{
  "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:

```bash
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ámetro     | Tipo    | Requerido | Descripción                                                                   |
| ------------- | ------- | --------- | ----------------------------------------------------------------------------- |
| `terminal_id` | string  | Si        | ID de la terminal POS                                                         |
| `checkout_id` | string  | Si        | ID del checkout de la venta a imprimir                                        |
| `copy`        | string  | No        | `merchant` (default) o `customer`                                             |
| `duplicate`   | boolean | No        | Imprime **DUPLICADO** en el papel. Úsalo al reimprimir una copia que ya salió |
| `external_id` | string  | No        | ID de tu sistema, para idempotencia                                           |

> **Note**
>
> 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

| Estado       | Descripcion                                                 |
| ------------ | ----------------------------------------------------------- |
| `pending`    | Creado, esperando que la terminal lo levante                |
| `dispatched` | La terminal recibio el comando y esta procesando el cobro   |
| `consumed`   | El cobro se completo exitosamente                           |
| `superseded` | Reemplazado por un comando mas reciente a la misma terminal |
| `failed`     | El 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

```python
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']}")
```