# Checkout Brasil

> Checkout de cartão para o comprador brasileiro: página sempre em português, CPF pedido na tela, comprador de qualquer país e botão e moldura ajustáveis pela API, com o mesmo Throne Elements e o mesmo webhook.

Fonte: https://app.thronecorporate.com/docs/cartao/brasil

## O que é

O Checkout Brasil é o checkout de cartão para o comprador brasileiro. Você cria o checkout com `POST /elements/checkout/br` e coloca o `transaction_id` no mesmo Throne Elements que já usa. O que muda está na página que o comprador vê: ela sai **sempre em português**, pede o **CPF** junto com o cartão e abre para comprador de qualquer país.

O nome e o CPF que o comprador digita vão para a adquirente junto com o cartão, na autorização. São os dados que o emissor de um cartão brasileiro espera receber numa compra.

> [!NOTE]
> Nada a mudar no que já está no ar. O `POST /elements/checkout` continua igual, com a mesma resposta e o mesmo webhook. O Checkout Brasil é um endpoint a mais, para a página do seu funil que vende ao comprador brasileiro.

## Antes de começar

O Checkout Brasil funciona **nas adquirentes habilitadas para o Checkout Brasil na sua conta**. As adquirentes de cartão da sua conta estão em `GET /elements/acquirers`, mas nem toda adquirente atende o Checkout Brasil: se nenhuma das suas atender, a criação responde `422 acquirer_not_enabled` e nada é criado. Para pedir a habilitação, fale com o suporte.

- A chave de API é a mesma, com a permissão `transactions:write`. Nenhuma chave precisa ser gerada de novo.
- O comprador precisa ter CPF. Sem um CPF válido, o pagamento não sai da página. Para o comprador sem CPF, use o checkout comum.
- A `Idempotency-Key`, o timeout de 60 segundos e a regra de repetição são os de sempre. Veja [Comece aqui](/docs/comecar#como-repetir-uma-chamada-com-seguranca).

## Criar

A criação fala com a adquirente. Use **60 segundos** de timeout no seu cliente HTTP e gere a `Idempotency-Key` antes de chamar, guardando junto do pedido. Use uma chave nova, nunca a de uma chamada ao `POST /elements/checkout`: a chave devolve a resposta da chamada em que foi usada.

```bash
curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout/br \
  --max-time 60 \
  -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount_cents": 9700,
    "currency": "brl",
    "customer": { "name": "Maria Souza", "email": "maria@exemplo.com" }
  }'
```

`201 Created`:

```json
{
  "transaction_id": "c4d7a2b1-6e3f-4a9c-8b5d-2f1e0a9c7b63",
  "status": "pending",
  "checkout_url": "https://pay.example.com/checkout/5a4b3c2d1e0f99887766554433221100",
  "display": { "currency": "BRL", "amount_cents": 9700 },
  "expires_at": "2026-10-20T14:20:00Z"
}
```

A resposta é a mesma do checkout comum: `transaction_id`, `status`, `checkout_url`, `acquirer` (a adquirente que ficou com a venda), `display`, `expires_at` e, quando houver aviso, `warnings`. Veja [a resposta do checkout comum](/docs/cartao#resposta). O que é próprio do Checkout Brasil está em [Referência dos campos](#referencia-dos-campos).

Na sua página, nada muda:

```html
<div data-throne-elements
     data-id="c4d7a2b1-6e3f-4a9c-8b5d-2f1e0a9c7b63"
     data-color="#7c3aed"
     data-theme="light"
     data-radius="10"></div>

<script src="https://app.thronecorporate.com/throne-elements.js" async></script>
```

O elemento monta a página em português, com o campo de CPF junto dos dados do cartão. O comprador digita o cartão, o nome e o CPF, e paga. Os eventos `throne-elements:ready`, `throne-elements:paid` e `throne-elements:error` chegam como sempre. A confirmação que vale para liberar o pedido continua sendo o webhook `transaction.paid` ou o `GET /transactions/{id}` com `status: "paid"`.

## O que muda em relação ao checkout comum

| Item | Checkout comum | Checkout Brasil |
|---|---|---|
| Endpoint | `POST /elements/checkout` | `POST /elements/checkout/br` |
| Idioma da página | Pelo país do comprador | Sempre português, seja qual for o país do comprador, o idioma do navegador ou o `?nl=` no `checkout_url` |
| CPF | Não pede | Pede na tela. Obrigatório e conferido antes do envio |
| País do comprador | Segue o país do comprador | Qualquer país: a página abre e o pagamento vai para a adquirente seja qual for o país do comprador ou do cartão |
| Adquirente | A pedida em `acquirer` ou a padrão da conta | A habilitada para o Checkout Brasil na conta. `acquirer` é opcional e só aceita uma que atenda o Checkout Brasil. O roteamento fixado na conta e o roteamento por valor não valem aqui |
| `one_click_from` e `recurrence` | Aceitos | Não existem: `422 field_not_supported` |
| Aparência da página | `data-color`, `data-theme` e `data-radius` do elemento | Os mesmos, mais o objeto `appearance` na criação: texto do botão, valor no botão, cores, formato, tamanho e moldura. Veja [Aparência](#aparencia) |
| Throne Elements, eventos, moedas, limites, tentativas, validade de 24 horas, status e webhook | Iguais | Iguais |

O comportamento da página depois do clique em pagar (verificação do banco, tentativas, códigos em `throne-elements:error`) é o da adquirente que ficou com a venda, como no checkout comum: veja o guia dela em [Comparar adquirentes](/docs/cartao/comparar).

## Aparência

Você pode ajustar a página do Checkout Brasil na própria criação, com o objeto `appearance`: o texto do botão, se o valor aparece nele, as cores, o arredondamento, a altura e a largura do botão, e uma moldura em volta dos campos. Tudo é opcional. O que você não mandar segue o padrão e os atributos `data-color` e `data-radius` do elemento, como hoje.

```bash
curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout/br \
  --max-time 60 \
  -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount_cents": 9700,
    "currency": "brl",
    "appearance": {
      "button": { "text": "Finalizar compra", "background_color": "#15803d" },
      "fields": { "radius": 8 },
      "frame": { "border_width": 1, "padding": 24, "shadow": "md" }
    }
  }'
```

O botão sai "Finalizar compra R$ 97,00" em verde, com o texto em branco escolhido sozinho para ficar legível, e os campos ficam dentro de uma moldura com borda e sombra.

| Campo | Aceita | Padrão | O que faz |
|---|---|---|---|
| `button.text` | Texto de 2 a 32 caracteres: letras, com ou sem acento, espaço e `! ? , - '`, com pelo menos uma letra | `Pagar` | Texto do botão. Número, símbolo de moeda e as palavras da lista abaixo não entram: o valor aparece sozinho |
| `button.show_amount` | `true` ou `false` | `true` | Mostra o valor depois do texto. Com `false`, o botão mostra só o texto e o valor aparece fora dele: logo acima do botão, ou no topo do formulário quando a venda é criada em outra moeda |
| `button.background_color` | Cor `#rrggbb` | O `data-color` | Cor do botão |
| `button.text_color` | Cor `#rrggbb`, só junto de `background_color` | Escolhida sozinha | Cor do texto do botão. O contraste com o fundo precisa ser de 3:1 ou mais |
| `button.radius` | Inteiro de `0` a `32` | O dos campos | Arredondamento do botão, em px. Com a metade da altura, o botão vira pílula |
| `button.height` | Inteiro de `44` a `64` | `48` | Altura mínima do botão, em px. Texto longo quebra a linha e o botão cresce |
| `button.full_width` | `true` ou `false` | `true` | Com `false`, o botão fica do tamanho do texto, centralizado, com no mínimo 200 px |
| `fields.focus_color` | Cor `#rrggbb` | `button.background_color`, senão o `data-color` | Cor de destaque: a borda do campo em foco, o contorno de foco do botão e o indicador da tela de espera |
| `fields.radius` | Inteiro de `0` a `32` | O `data-radius`, senão `10` | Arredondamento dos campos, em px |
| `frame.max_width` | Inteiro de `320` a `720` | `460` | Largura máxima do checkout, em px |
| `frame.padding` | Inteiro de `0` a `40` | `0` | Espaço entre a moldura e os campos, em px. Em tela de até 400 px, no máximo 6 |
| `frame.border_width` | Inteiro de `0` a `3` | `0` | Espessura da borda da moldura. Com só `border_color`, vale `1`. Em tela de até 400 px, no máximo 1 |
| `frame.border_color` | Cor `#rrggbb` | A cor das linhas dos campos | Cor da borda |
| `frame.radius` | Inteiro de `0` a `32` | `12` | Arredondamento da moldura |
| `frame.shadow` | `none`, `sm`, `md` ou `lg` | `none` | Sombra da moldura. Em site escuro, prefira a borda: a sombra quase não aparece |

Como a validação funciona:

- Mande JSON com os tipos certos: `48`, não `"48"`; `false`, não `"false"`. Uma chave com `null` vale como ausente.
- Chave que não existe, valor fora da regra ou combinação impossível: `422 invalid_appearance` e nada é criado. A resposta traz `field` com o caminho da chave, por exemplo `appearance.button.text`.
- O texto do botão não aceita, em maiúsculas ou minúsculas, as palavras real, reais, centavo, centavos, grátis, gratis, gratuito, gratuita, free, zero, cancelar, voltar, fechar e sair: o botão sempre paga o valor da venda.
- Contraste: `text_color` com contraste menor que 3:1 sobre `background_color` é recusado. De 3:1 até 4,5:1 é aceito, com o aviso `low_contrast` em `warnings`. Branco sobre `#16a34a` dá 3,29:1 (aceito, com aviso); branco sobre `#15803d`, 5,01:1.
- `fields.focus_color` que quase some sobre fundo claro ou escuro também gera `low_contrast`.
- A moldura precisa deixar pelo menos 256 px para os campos: `frame.max_width`, menos 32 (ou 56 com `shadow: lg`), menos duas vezes a soma de `frame.padding` e `frame.border_width`.

Quem vence quando você também usa os atributos do elemento:

| Aspecto | Sem a chave em `appearance` | Com a chave |
|---|---|---|
| Cor do botão | `data-color` | `button.background_color` |
| Cor de destaque dos campos | `data-color` | `fields.focus_color`, senão `button.background_color` |
| Arredondamento | `data-radius`, nos campos e no botão | `fields.radius` e `button.radius` |
| Tema e cor de fundo | `data-theme` e a detecção do fundo do seu site | Os mesmos: `appearance` não muda |

As chaves que você manda ficam gravadas na criação e não mudam pela URL do checkout; as que você não manda seguem os atributos do elemento. Para mudar o que foi gravado, crie um checkout novo.

O que não muda: os nomes e os exemplos dos campos (número do cartão, nome no cartão, validade, CVV e CPF), a frase de segurança abaixo do botão, os textos de processamento, de verificação do banco, de aprovação e de recusa, o botão "Tentar de novo", a fonte e as bandeiras. São os textos que o comprador e o banco esperam ver numa página de pagamento.

O botão em cada momento: antes do clique, o seu texto, com ou sem o valor; durante o envio, "Processando", desativado e nas suas cores; recusado, a tela de recusa com "Tentar de novo", e de volta ao formulário o botão volta ao seu texto; aprovado, a tela de pagamento aprovado.

## Erros

Os erros do checkout comum valem aqui, com o mesmo significado: `invalid_amount`, `currency_not_supported`, `fx_unavailable`, `card_not_enabled`, `invalid_acquirer`, os de `Idempotency-Key`, `502 checkout_failed` e `503 temporarily_unavailable`. Veja [a tabela do checkout comum](/docs/cartao#erros). O que é próprio do Checkout Brasil:

| Status | `error` | `message` | Quando | Repetir |
|---|---|---|---|---|
| `422` | `acquirer_not_enabled` | O Checkout Brasil não está habilitado para nenhuma adquirente da sua conta. Fale com o suporte. | A conta não tem adquirente habilitada para o Checkout Brasil. Nada foi criado | Não, até habilitar |
| `422` | `acquirer_not_enabled` | A adquirente pedida não está habilitada para o Checkout Brasil na sua conta. | O campo `acquirer` pediu uma adquirente que não atende o Checkout Brasil. Nada foi criado | Com chave nova, sem o campo ou com outra adquirente |
| `422` | `field_not_supported` | O campo one_click_from não é aceito no Checkout Brasil. Remova o campo e repita a chamada. | O corpo trouxe `one_click_from` ou `recurrence`. Nada foi criado | Com chave nova, sem o campo |
| `422` | `invalid_appearance` | appearance.button.color não existe. Em appearance.button valem: text, show_amount, background_color, text_color, radius, height e full_width. | O objeto `appearance` trouxe chave que não existe, valor fora da regra ou combinação impossível. O campo `field` diz qual chave. Nada foi criado | Com chave nova, depois de corrigir |

No Checkout Brasil, `invalid_amount` vem com `alternatives` vazio e o `503` não sugere outra adquirente: acima do máximo, divida o pedido.

Decida sempre pelo campo `error`. O `message` é texto de apoio e pode mudar.

## Perguntas que aparecem sempre

**O CPF do comprador fica guardado?** Não. Ele vai para a adquirente na autorização do pagamento e só. O Throne não guarda o CPF digitado, e ele não volta na consulta nem no webhook. Se você quiser o documento registrado na venda, mande `customer.document` na criação, como no checkout comum.

**Serve para Pix?** Não. O Checkout Brasil é só cartão. Para Pix, use `POST /transactions` com `payment_method: "pix"`, como em [Cobrar com Pix](/docs/pix).

**Posso usar o mesmo Throne Elements?** Sim. O script, os atributos e os eventos são os mesmos: só troque o `data-id` pelo `transaction_id` novo. Vale a regra de sempre: para trocar de checkout, use um container novo ([detalhes](/docs/cartao/elements#pagina-de-uma-tela-so-react-vue-e-outras)).

**Preciso de outra chave de API?** Não. A mesma chave, com `transactions:write`, cria os dois checkouts.

**E se o comprador não tiver CPF?** O pagamento não sai: o campo é obrigatório e conferido na tela. Para esse comprador, crie o checkout comum.

**O `customer.document` da criação preenche o CPF?** Não. O campo CPF da página nasce vazio e o comprador digita o dele. O que vai para a adquirente é o CPF digitado na tela; o `customer.document` fica registrado na venda e volta no webhook, como no checkout comum.

**O `?nl=` no `checkout_url` muda o idioma?** Não. No Checkout Brasil a página sai em português sempre, inclusive para teste.

**A venda aparece diferente na consulta ou no webhook?** Não. É uma venda de cartão como as outras: mesmo `GET /transactions/{id}`, mesmo `transaction.paid`, mesmo `acquirer`.

**Posso mudar a frase de segurança ou o nome dos campos?** Não. Só o botão, as cores, o formato e a moldura mudam, pelo objeto `appearance`. O resto é o texto que o comprador e o banco esperam ver numa página de pagamento.

**O `appearance` funciona no `POST /elements/checkout`?** Não. Ele só existe no Checkout Brasil; no checkout comum o campo é ignorado.

## Referência dos campos

Requisição, `POST /elements/checkout/br`:

| Campo | Obrigatório | Regra |
|---|---|---|
| `amount_cents` | Sim | Inteiro na menor unidade da moeda (R$ 97,00 = `9700`). Mínimo e máximo como no checkout comum: pela adquirente, pela moeda e pela cotação do dia |
| `currency` | Sim | Código de 3 letras, maiúsculas ou minúsculas. As mesmas 48 moedas do checkout comum; para o comprador brasileiro, `brl` |
| `acquirer` | Não | Só uma adquirente habilitada para o Checkout Brasil na sua conta; outra volta `422 acquirer_not_enabled`. Sem o campo, a venda vai para a habilitada |
| `customer.name` | Não | Como no checkout comum: vai pré-preenchido no nome do cartão, e o comprador pode corrigir |
| `customer.email`, `customer.document`, `customer.document_type`, `customer.phone`, `customer.address` | Não | Como no checkout comum: ficam registrados na venda e voltam no webhook. Nenhum gera erro; o que vier fora do formato é ajustado ou descartado, com aviso em `warnings` |
| `appearance` | Não | Objeto com `button`, `fields` e `frame`. Veja [Aparência](#aparencia). Fora da regra: `422 invalid_appearance` |
| `one_click_from`, `recurrence` | Não existem | `422 field_not_supported` |

`metadata` e itens não são aceitos, como no checkout comum: guarde o `transaction_id` para ligar a venda ao seu pedido.

Resposta `201`: os mesmos campos do checkout comum. Veja a [referência da resposta](/docs/cartao#resposta). O que é próprio do Checkout Brasil:

- `warnings` pode trazer, além dos avisos de `customer`, o aviso `low_contrast`, sobre `appearance`.

Veja também [Cartão: como funciona](/docs/cartao) e [Throne Elements](/docs/cartao/elements).
