# Cobrar com Pix

> Criar cobrança Pix, mostrar o QR Code, acompanhar o pagamento e vender produto físico.

Fonte: https://app.thronecorporate.com/docs/pix

## Como funciona

1. O **seu servidor** cria a cobrança com `POST /transactions`.
2. Você mostra ao comprador o QR Code e o código copia e cola.
3. O comprador paga no aplicativo do banco.
4. O Throne envia o webhook `transaction.paid` para o seu servidor. Só então libere o pedido.

A cobrança Pix é sempre em reais. Antes de começar, veja [autenticação e Idempotency-Key](/docs/comecar).

O recebimento por Pix precisa estar habilitado na sua conta. Se não estiver, a criação responde `422 pix_not_enabled` e nada é criado; o cartão e os saques por Pix continuam normais. No painel, em Documentação da API, a linha do PIX aparece só quando o recebimento está habilitado.

## Criar a cobrança

```bash cURL
curl -X POST https://app.thronecorporate.com/api/v1/transactions \
  -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "payment_method": "pix",
    "amount_cents": 4990,
    "customer": {
      "name": "Maria Souza",
      "document": "12345678909",
      "email": "maria@exemplo.com"
    },
    "metadata": { "loja_pedido_id": "PED-1042" }
  }'
```
```php PHP
$payload = [
    'payment_method' => 'pix',
    'amount_cents'   => 4990, // R$ 49,90
    'customer' => [
        'name'     => 'Maria Souza',
        'document' => '12345678909', // CPF ou CNPJ, só números
        'email'    => 'maria@exemplo.com',
    ],
    'metadata' => ['loja_pedido_id' => 'PED-1042'],
];

$ch = curl_init('https://app.thronecorporate.com/api/v1/transactions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('THRONE_PK') . ':' . getenv('THRONE_SK'),
        'Content-Type: application/json',
        'Accept: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);
$cobranca = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 201 = criada
```
```js Node.js
import { randomUUID } from 'node:crypto';

const res = await fetch('https://app.thronecorporate.com/api/v1/transactions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'Idempotency-Key': randomUUID(),
  },
  body: JSON.stringify({
    payment_method: 'pix',
    amount_cents: 4990,
    customer: { name: 'Maria Souza', document: '12345678909', email: 'maria@exemplo.com' },
    metadata: { loja_pedido_id: 'PED-1042' },
  }),
});
const cobranca = await res.json(); // res.status === 201
```
```python Python
import os, uuid, requests

r = requests.post(
    "https://app.thronecorporate.com/api/v1/transactions",
    headers={
        "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}",
        "Accept": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "payment_method": "pix",
        "amount_cents": 4990,
        "customer": {"name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com"},
        "metadata": {"loja_pedido_id": "PED-1042"},
    },
    timeout=20,
)
cobranca = r.json()  # r.status_code == 201
```

### Campos

| Campo | Obrigatório | Regra |
|---|---|---|
| `payment_method` | Sim | `"pix"` |
| `amount_cents` | Sim | Inteiro em centavos, de `100` (R$ 1,00) a `5000000` (R$ 50.000,00) |
| `customer.name` | Sim | De 3 a 255 caracteres |
| `customer.document` | Sim | CPF (11 dígitos) ou CNPJ (14 dígitos), **só números** |
| `customer.email` | Sim | E-mail válido |
| `customer.phone` | Não | Até 20 caracteres, por exemplo `+5511999998888` |
| `customer.birth_date` | Não | `AAAA-MM-DD` |
| `customer.address` | Não | `zip_code`, `street`, `number`, `complement`, `neighborhood`, `city`, `state` (UF, 2 letras), `country` (2 letras) |
| `expires_at` | Não | Data e hora ISO 8601. Se não vier, a cobrança vale 1 hora |
| `metadata` | Não | Objeto livre que volta na consulta e no webhook. Use nomes de campo com um prefixo seu, como `loja_pedido_id` |
| `items` | Não | Lista de produtos. Veja [produto físico](#produto-fisico) |
| `shipping_address` | Não | Endereço de entrega. Veja [produto físico](#produto-fisico) |
| `splits` | Não | Divisão do recebimento. Veja [dividir o recebimento](#dividir-o-recebimento) |

A descrição que o comprador vê no banco é definida pelo Throne e não pode ser alterada. Campos fora desta lista são ignorados.

## Resposta

`201 Created`:

```json
{
  "id": "72394240-db89-4ec5-b370-4a3e7b998409",
  "status": "pending",
  "payment_method": "pix",
  "amount_cents": 4990,
  "fee_cents": 250,
  "net_amount_cents": 4740,
  "currency": "BRL",
  "native_amount_cents": 4990,
  "fx_rate": null,
  "paid_at": null,
  "expires_at": "2026-10-03T15:20:00Z",
  "payment_data": {
    "qr_code_payload": "00020126580014br.gov.bcb.pix0136...",
    "qr_code_image_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
    "expires_at": "2026-10-03T12:20:00-03:00"
  },
  "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" },
  "shipping_address": null,
  "items": null,
  "metadata": { "loja_pedido_id": "PED-1042" },
  "created_at": "2026-10-03T14:20:00Z"
}
```

- **Guarde o `id`.** É com ele que você consulta a cobrança e reconhece o webhook.
- `fee_cents` e `net_amount_cents` já vêm calculados pela taxa da sua conta.
- `payment_data.qr_code_payload` é o copia e cola. `payment_data.qr_code_image_base64` é a imagem do QR Code em PNG, em base64 puro.

## Mostrar o QR Code

```html
<img src="data:image/png;base64,{{ qr_code_image_base64 }}" alt="QR Code Pix" width="240" height="240">
<input readonly value="{{ qr_code_payload }}">
<button onclick="navigator.clipboard.writeText('{{ qr_code_payload }}')">Copiar código Pix</button>
```

Se `qr_code_image_base64` vier `null`, gere o QR Code você mesmo a partir de `qr_code_payload` (qualquer biblioteca de QR Code serve). Se `qr_code_payload` vier vazio, não mostre nada ao comprador: crie outra cobrança com uma `Idempotency-Key` nova.

`payment_data.expires_at` vem com fuso (`-03:00`) e o `expires_at` da cobrança vem em UTC: os dois representam o mesmo instante.

## Confirmar o pagamento

A cobrança nasce `pending` e vira `paid` quando o pagamento é confirmado. Há duas formas de saber:

1. **Webhook `transaction.paid`** (recomendado). Chega no seu servidor assim que o pagamento é confirmado. Veja [Webhooks](/docs/webhooks) para validar a assinatura.
2. **Consulta** com `GET /transactions/{id}`, usando o `id` da criação. Precisa da permissão `transactions:read`.

```bash
curl https://app.thronecorporate.com/api/v1/transactions/72394240-db89-4ec5-b370-4a3e7b998409 \
  -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \
  -H "Accept: application/json"
```

Payload do webhook de um Pix pago:

```json
{
  "event_id": "8c1f3c5e-2c1a-4d1e-9a2b-1f0c9d7e6b5a",
  "type": "transaction.paid",
  "version": "2026-05-01",
  "created_at": "2026-10-03T14:21:07Z",
  "data": {
    "transaction_id": "72394240-db89-4ec5-b370-4a3e7b998409",
    "status": "paid",
    "payment_method": "pix",
    "amount_cents": 4990,
    "fee_cents": 250,
    "net_amount_cents": 4740,
    "paid_at": "2026-10-03T14:21:05Z",
    "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" },
    "shipping_address": null,
    "items": null,
    "metadata": { "loja_pedido_id": "PED-1042" }
  }
}
```

Regras que evitam problemas:

- Libere o pedido **só** com o webhook `transaction.paid` ou com uma consulta que mostre `status: "paid"`.
- Deduplique pelo `data.transaction_id`: o mesmo pagamento pode chegar mais de uma vez.
- Trate o vencimento pelo `expires_at`, não pelo status: uma cobrança não paga costuma continuar `pending` depois do prazo. Um Pix pago depois do prazo ainda vira `paid` e gera `transaction.paid`. Não existe webhook de expiração.
- Campos que vierem a mais no `data` (como `card_brand` ou `fx_rate` com valor `null`) podem ser ignorados.
- A API não tem listagem de cobranças. Guarde o `id` de cada uma e use o webhook para conciliar.

### Ciclo da cobrança

| Status | Quando |
|---|---|
| `pending` | Aguardando o pagamento. Continua assim depois do prazo se ninguém pagar |
| `paid` | Pagamento confirmado |
| `expired` | Raro: a cobrança venceu e foi encerrada. Mesmo assim, se o pagamento chegar, ela vira `paid` |

Estorno de Pix não é feito pela API. Fale com o suporte do Throne.

## Produto físico

Para vender produto físico, envie a lista de itens e o endereço de entrega. Os dois aparecem no painel e voltam no webhook, então você processa o pedido sem outra chamada.

```json
{
  "payment_method": "pix",
  "amount_cents": 29800,
  "customer": { "name": "Maria Compradora", "document": "98765432100", "email": "maria@exemplo.com", "phone": "+5511988887777" },
  "items": [
    { "title": "Camisa Polo, Azul, M", "sku": "POLO-AZUL-M", "quantity": 2, "unit_amount_cents": 9900 },
    { "title": "Boné Preto", "sku": "BONE-PRETO", "quantity": 1, "unit_amount_cents": 10000 }
  ],
  "shipping_address": {
    "recipient_name": "Maria Compradora",
    "recipient_phone": "+5511988887777",
    "zip_code": "04567000",
    "street": "Rua das Flores",
    "number": "250",
    "complement": "Bloco B, apto 304",
    "neighborhood": "Vila Mariana",
    "city": "São Paulo",
    "state": "SP",
    "country": "BR",
    "shipping_method": "standard"
  }
}
```

- `items`: até 100 itens. Cada um exige `title`, `quantity` e `unit_amount_cents`; `sku`, `description`, `category` e `image_url` são opcionais.
- **A soma de `quantity × unit_amount_cents` precisa ser exatamente `amount_cents`.** Senão: `422 items_total_mismatch`.
- `shipping_address`: exige `recipient_name`, `zip_code`, `street`, `number`, `city` e `state` (UF). `shipping_method` aceita `standard`, `express` ou `pickup`.

## Dividir o recebimento

Com `splits`, parte do valor de uma cobrança vai para outra conta do Throne quando ela é paga (por exemplo, num marketplace).

```json
"splits": [
  { "recipient_seller_id": 4821, "type": "percentage", "percent_basis_points": 1500 },
  { "recipient_seller_id": 5310, "type": "fixed", "amount_cents": 500 }
]
```

- `type: "percentage"`: `percent_basis_points` de 1 a 10000 (1500 = 15%).
- `type: "fixed"`: `amount_cents` a partir de 100.
- Até 20 divisões por cobrança. Não é possível dividir para a sua própria conta (`422 split_invalid`).

## Links de pagamento

Um link de pagamento é uma página pronta do Throne, que você compartilha por WhatsApp, e-mail ou redes. A chave precisa da permissão `payment_links:write` (e `payment_links:read` para consultar).

```bash
curl -X POST https://app.thronecorporate.com/api/v1/payment-links \
  -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "description": "Consultoria de 1 hora", "amount_cents": 15000, "max_uses": 1 }'
```

| Campo | Obrigatório | Regra |
|---|---|---|
| `description` | Sim | Até 500 caracteres |
| `amount_cents` | Sim | De `100` a `5000000` |
| `customer_required` | Não | Se o comprador precisa preencher os dados (padrão `true`) |
| `max_uses` | Não | Quantas vezes o link pode ser pago, de 1 a 100000 |
| `expires_at` | Não | Data futura em ISO 8601 |
| `redirect_url_success` | Não | Para onde o comprador vai depois de pagar |
| `redirect_url_expired` | Não | Para onde vai se o link tiver expirado |
| `metadata` | Não | Objeto livre |

A resposta traz o campo `url`: é ele que você compartilha. Outros endpoints:

- `GET /payment-links`: os 100 links mais recentes.
- `GET /payment-links/{id}`: um link, com `used_count` e `usable`.
- `POST /payment-links/{id}/revoke`: desativa o link. Se já estava desativado: `409 already_revoked`.

Cada pagamento feito pelo link é uma cobrança Pix normal e gera `transaction.paid`.

Como o link é sempre Pix, ele segue a mesma regra: com o recebimento por Pix desabilitado na conta, `POST /payment-links` responde `422 pix_not_enabled` e um link já criado mostra "Link indisponível" ao comprador até o recebimento ser habilitado de novo.

## Erros comuns

| Status | `error` | O que fazer |
|---|---|---|
| `422` | (validação) | Corrija o campo indicado em `errors`. O caso mais comum é faltar `customer` ou `customer.document` |
| `422` | `invalid_request` | Regra de valor ou método (por exemplo, abaixo de R$ 1,00). Leia o `message` |
| `422` | `pix_not_enabled` | O recebimento por Pix não está habilitado nesta conta. Nada foi criado. Fale com o suporte; cartão e saques por Pix continuam |
| `422` | `items_total_mismatch` | A soma dos itens não bate com `amount_cents` |
| `422` | `split_invalid` | Divisão para a própria conta |
| `409` | `idempotency_key_processing` | A primeira chamada com essa `Idempotency-Key` ainda está em andamento. Espere alguns segundos e repita a mesma chamada, igual. Veja [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca) |
| `429` | `rate_limit_exceeded` | Mais de 10 cobranças por segundo. Espere o `Retry-After` e tente com uma `Idempotency-Key` nova |
| `502` | `acquirer_error`, com `"retry_same_key": true` | A cobrança pode ter sido criada. Repita a mesma chamada, com a mesma `Idempotency-Key` e o mesmo corpo, e nunca com uma chave nova |
| `502` | `acquirer_error`, sem `retry_same_key` | A adquirente recusou antes de criar, e nada foi criado. Tente de novo com uma `Idempotency-Key` nova |

Lista completa em [Erros](/docs/errors).
