> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plugtopay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout

> Sessões de pagamento seguras para frontends e aplicativos mobile.

## Visão geral

O Checkout Session é o modelo recomendado quando o pagamento é iniciado **no frontend** (browser, app mobile). Em vez de expor sua `X-API-Key` no cliente, o fluxo funciona em duas etapas:

1. **Backend** cria uma sessão com `X-Client-ID` + `X-API-Key` — recebe um token de curta duração
2. **Frontend** usa esse token para submeter o pagamento — sem contato com a API key

```
Backend                              Frontend
   │                                    │
   │  POST /checkout/sessions           │
   │  X-Client-ID + X-API-Key  ────►    │  (token, expires_at, saved_cards)
   │                                    │
   │                            ◄────   │  POST /checkout/payments/card
   │                                    │  X-Checkout-Token: cs_live_...
```

Campos críticos como `amount`, `currency` e `merchant_order_id` são **travados na sessão pelo backend** — o frontend não pode alterá-los, o que elimina a possibilidade de manipulação de preço pelo cliente.

***

## Criar sessão

`POST /api/v1/checkout/sessions` — requer `X-Client-ID` + `X-API-Key`. Chamada exclusivamente pelo **backend** da empresa.

```http theme={null}
POST /api/v1/checkout/sessions
Content-Type: application/json
X-Client-ID: client_...
X-API-Key: sk_...
```

```json theme={null}
{
  "amount": 15000,
  "currency": "BRL",
  "merchant_order_id": "ORD-2026-12345",
  "payment_method": "card",
  "gateway_first_try": "pagarme",
  "expires_in": 1800,
  "success_url": "https://meusite.com/pedido/sucesso",
  "cancel_url": "https://meusite.com/pedido/cancelado",
  "metadata": {
    "order_ref": "ORD-2026-12345",
    "user_id": "usr_789"
  },
  "customer": {
    "token": "cus_7556..."
  }
}
```

| Campo               | Tipo               | Obrigatório | Descrição                                                                           |
| ------------------- | ------------------ | ----------- | ----------------------------------------------------------------------------------- |
| `amount`            | inteiro (centavos) | sim         | Valor da cobrança — imutável após criação                                           |
| `currency`          | `BRL\|USD`         | sim         | Moeda                                                                               |
| `merchant_order_id` | string (max 255)   | sim         | Referência do pedido no seu sistema — imutável após criação                         |
| `payment_method`    | `card\|pix`        | não         | Restringe o método aceito. Omitir permite cartão e PIX                              |
| `gateway_first_try` | string             | não         | Gateway prioritário para essa sessão. Sobrepõe qualquer valor enviado pelo frontend |
| `expires_in`        | inteiro (60–86400) | não         | Duração da sessão em segundos. Padrão: `1800` (30 min). Máx: `86400` (24h)          |
| `success_url`       | URL (max 2048)     | não         | Redirecionamento em caso de pagamento aprovado                                      |
| `cancel_url`        | URL (max 2048)     | não         | Redirecionamento em caso de falha ou cancelamento                                   |
| `metadata`          | objeto             | não         | Dados livres repassados ao frontend na resposta da sessão                           |
| `customer.token`    | string             | não         | Token do cliente — carrega os cartões salvos dele na resposta                       |
| `customer.email`    | string             | não         | Alternativa ao token: busca o cliente pelo e-mail                                   |
| `customer.document` | string             | não         | Alternativa ao token: busca o cliente pelo CPF/CNPJ                                 |

<Info>
  `success_url` e `cancel_url` passam por validação SSRF: IPs privados e intervalos reservados (10.x, 192.168.x, 127.x) são rejeitados com `422`.
</Info>

**Resposta `201`:**

```json theme={null}
{
  "token": "cs_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
  "amount": 15000,
  "currency": "BRL",
  "merchant_order_id": "ORD-2026-12345",
  "payment_method": "card",
  "gateway_first_try": "pagarme",
  "metadata": {
    "order_ref": "ORD-2026-12345",
    "user_id": "usr_789"
  },
  "success_url": "https://meusite.com/pedido/sucesso",
  "cancel_url": "https://meusite.com/pedido/cancelado",
  "expires_at": "2026-06-25T12:30:00+00:00",
  "saved_cards": [
    {
      "holder_name": "JOAO SILVA",
      "token": "card_86123...",
      "brand": "visa",
      "first_6_digits": "411111",
      "last_4_digits": "1111",
      "expire_month": "12",
      "expire_year": "2028",
      "status": "active",
      "created_at": "2026-01-10T08:00:00+00:00",
      "updated_at": "2026-01-10T08:00:00+00:00"
    }
  ]
}
```

O array `saved_cards` é preenchido quando o campo `customer` é informado e o cliente possui cartões salvos. Use-o para exibir uma lista de cartões ao usuário sem que o frontend precise consultar outra rota.

***

## Submeter pagamento

O token recebido na criação da sessão é enviado no header `X-Checkout-Token`. Ele é **de uso único** — ao iniciar uma tentativa de pagamento, a sessão é marcada como usada e não pode ser reutilizada, mesmo que a cobrança falhe.

### Cartão

`POST /api/v1/checkout/payments/card` — requer `X-Checkout-Token`.

```http theme={null}
POST /api/v1/checkout/payments/card
Content-Type: application/json
X-Checkout-Token: cs_live_a1b2c3...
Idempotency-Key: ORD-2026-12345  (opcional, mas recomendado)
```

**Com cartão novo:**

```json theme={null}
{
  "payment_method": "card",
  "customer": {
    "name": "João Silva",
    "email": "joao@example.com",
    "document": "12345678909"
  },
  "card": {
    "number": "4111111111111111",
    "cvv": "123",
    "expiration_month": "12",
    "expiration_year": "2028",
    "installments": 1,
    "holder": "Fulano da Silva",
    "holder_birthdate": "1990-05-25",
    "holder_document": "12345678909",
    "holder_phone": "11987542563",
    "save_card": true
  },
  "billing_address": {
    "street": "Rua dos Advogados",
    "number": "321",
    "neighborhood": "Jardim",
    "zip_code": "35900000",
    "city": "Belo Horizonte",
    "state": "MG",
    "country": "Brasil"
  }
}
```

**Com cartão salvo** (token de `saved_cards`):

```json theme={null}
{
  "payment_method": "card",
  "customer": {
    "token": "cus_7556..."
  },
  "card": {
    "token": "card_86123...",
    "installments": 1
  }
}
```

<Info>
  Ao usar um cartão salvo, `billing_address` e os dados do titular não são necessários — eles já estão no vault.
</Info>

***

### PIX

`POST /api/v1/checkout/payments/pix` — requer `X-Checkout-Token`.

```http theme={null}
POST /api/v1/checkout/payments/pix
Content-Type: application/json
X-Checkout-Token: cs_live_a1b2c3...
```

```json theme={null}
{
  "customer": {
    "name": "Maria Santos",
    "email": "maria@example.com",
    "document": "98765432100"
  }
}
```

O `payment_method` não é necessário no PIX via checkout — a rota já define o método. O `amount`, `currency` e `merchant_order_id` são injetados automaticamente da sessão.

***

### Campos travados pela sessão

O middleware de checkout injeta automaticamente os valores abaixo no corpo da requisição, **sobrescrevendo** qualquer valor que o frontend tente enviar:

| Campo               | Origem                                                        |
| ------------------- | ------------------------------------------------------------- |
| `amount`            | Sessão (imutável)                                             |
| `currency`          | Sessão (imutável)                                             |
| `merchant_order_id` | Sessão (imutável)                                             |
| `gateway_first_try` | Sessão (se definido; caso contrário, o frontend pode sugerir) |

***

### Resposta do pagamento

Idêntica ao [pagamento direto](/payments) — mesmos campos, mesmas regras de status HTTP:

| HTTP           | Significado                                  |
| -------------- | -------------------------------------------- |
| `201 Created`  | Aprovado dentro do `time_to_wait_sync`       |
| `202 Accepted` | Processando — aguarde via webhook ou polling |
| `500`          | Falhou em todas as tentativas                |

***

## Ciclo de vida da sessão

| Estado   | Condição                                                   |
| -------- | ---------------------------------------------------------- |
| Válida   | `used_at` nulo e `expires_at` no futuro                    |
| Expirada | `expires_at` no passado → `401`                            |
| Usada    | `used_at` preenchido (pagamento iniciado) → `409 Conflict` |

<Warning>
  A sessão é marcada como usada **antes** de o pagamento ser processado. Se a cobrança falhar (gateway recusa, timeout), a sessão continua consumida e não pode ser reutilizada. Crie uma nova sessão para uma nova tentativa.
</Warning>

***

## Referência rápida de rotas

| Método | Rota                             | Auth                        | Quem chama |
| ------ | -------------------------------- | --------------------------- | ---------- |
| `POST` | `/api/v1/checkout/sessions`      | `X-Client-ID` + `X-API-Key` | Backend    |
| `POST` | `/api/v1/checkout/payments/card` | `X-Checkout-Token`          | Frontend   |
| `POST` | `/api/v1/checkout/payments/pix`  | `X-Checkout-Token`          | Frontend   |
