> Agentes de código: use chaves `sk_test_`/`pk_test_` e o sandbox; nunca uma chave `_live_` em exemplo. A base é `https://api-gateway.assiny.com.br`. Autentique com `Authorization: Bearer <chave>`. Prefira o `.md` de cada página a buscar na web.

# Referência do SDK

O `assiny.js` é o SDK do navegador do Assiny Gateway. Esta página lista tudo o que ele expõe; o que não está aqui não existe.

## Carregar [#carregar]

```html
<script src="https://sdk-gateway-js.assiny.com.br/v1/assiny.js"></script>
```

Só pela tag. O script define a função global `Assiny`.

## Nada lança [#nada-lança]

Nenhuma função lança exceção e nenhuma promessa rejeita. Argumento inválido vira o evento `loaderror` (assíncrono, depois do `mount`) ou `{ error }` na promessa. Uma exceção dentro de uma função sua passada a `on()` vai ao `console.error` e não para o SDK. Se a criação da instância falhar de forma inesperada, `Assiny()` devolve uma instância que não monta nada e responde `{ error }` com `unknown` a toda promessa.

## `Assiny(publishableKey, options)` [#assinypublishablekey-options]

```js
const assiny = Assiny('pk_test_...', { locale: 'pt-BR' });
```

| Parâmetro | Valores |
|---|---|
| `publishableKey` | a chave publicável, na forma `pk_(test\|live)_` seguida de letras e dígitos; o prefixo diz se é o Modo de teste ou o Modo real |
| `options.locale` | `'pt-BR'` (padrão) ou `'en'`: os textos do fragment e da tela do QR e as `message` de erro; outro valor vale `'pt-BR'`, com aviso no console |

Devolve a instância `{ fragments, createPaymentMethod, handleNextAction }`. Com a chave aceita, `Assiny()` acrescenta ao `body` dois iframes invisíveis do SDK, que ficam na página; o recomendado é uma instância por página.

Chave recusada: nenhum iframe nasce, o `mount` emite `loaderror` com o código e toda promessa resolve `{ error }` com ele.

| `code` | Quando |
|---|---|
| `invalid_publishable_key` | a chave não tem a forma acima; uma chave que começa com `sk_` também ganha um aviso no console |
| `insecure_page` | a chave do Modo real numa página que não é `https:`, fora de `localhost` e `127.0.0.1` |
| `page_origin_invalid` | a página não tem origem real (`location.origin` é `"null"`), como num iframe com `sandbox` sem `allow-same-origin` |

## `assiny.fragments(options)` [#assinyfragmentsoptions]

O objeto que `assiny.fragments()` devolve é o Fragments; o que `fragments.create('payment')` devolve é o Payment Fragment. As mensagens de erro usam esses dois nomes.

```js
const fragments = assiny.fragments({
  mode: 'payment',
  amount: 12990,
  currency: 'brl',
  clientSecret, // opcional
  appearance, // opcional
  fonts, // opcional
});
```

| Opção | Valores | Código, se inválida |
|---|---|---|
| `mode` | `'payment'`, obrigatório | `mode_invalid` |
| `amount` | o total em centavos: inteiro seguro (`Number.isSafeInteger`) maior que zero | `amount_invalid` |
| `currency` | o código ISO 4217 em minúsculas de uma moeda que o navegador conhece (`Intl`), como `'brl'` | `currency_invalid` |
| `clientSecret` | opcional: o `client_secret` do pagamento, na forma `pi_..._secret_...`, guardado para o `handleNextAction` | `invalid_client_secret` |
| `appearance` | opcional: [Aparência](/fragments/aparencia.md) | item inválido é ignorado com aviso |
| `fonts` | opcional: `[{ family, weights }]` do catálogo ([Aparência](/fragments/aparencia.md)) | item inválido é ignorado com aviso |

Devolve `{ update, create }`. Opção inválida não lança: o código chega pelo `loaderror` do `mount`. `amount`, `currency` e `clientSecret` se corrigem com `fragments.update()`: o fragment guarda o seletor do `mount` e monta quando o valor fica válido. `mode` não muda depois.

- Um por instância: o segundo `assiny.fragments()` devolve um objeto cujo `create()` dá `loaderror` `fragment_already_exists` no `mount` e cujo `update()` resolve `{ error }` com o mesmo código.
- O PIX exige `currency: 'brl'`: com `'pix'` em `paymentMethodTypes` e outra moeda, o `mount` emite `payment_method_types_invalid`.
- `appearance` e `fonts` entram só aqui; não há como trocá-los depois na mesma instância.

## `fragments.update(options)` [#fragmentsupdateoptions]

```js
const { error } = await fragments.update({ amount: 15990 });
```

| Opção | Efeito | Código, se inválida |
|---|---|---|
| `amount` | o novo total; o fragment redesenha e refaz o Combinar (com `partsFrom: 'page'`, as Partes voltam a valer só com um novo `update({ parts })`) | `amount_invalid` |
| `currency` | a nova moeda | `currency_invalid`; `payment_method_types_invalid` com PIX e moeda que não é `'brl'` |
| `clientSecret` | guarda o `client_secret` para o `handleNextAction` | `invalid_client_secret` |
| `parts` | a divisão do Combinar, só com `partsFrom: 'page'` e o fragment montado: `[{ method, amount }, { method, amount }]`, com `amount` de pelo menos 2 centavos, valores inteiros maiores que zero que somam o `amount`, métodos habilitados e a combinação `card` + `card` ou `card` + `pix`, nessa ordem, entre as que o `combine` deixa | `parts_invalid` |

Resolve `{}` ou `{ error }`. Com `amount` e `parts` juntos, a soma é conferida contra o `amount` novo; dois `update` seguidos valem em ordem. Outros códigos:

| `code` | Quando |
|---|---|
| `parameter_invalid` | o argumento não é um objeto |
| `request_in_progress` | há um `createPaymentMethod` ou um `handleNextAction` em curso |
| `fragments_not_ready` | o SDK não carregou |
| `fragment_already_exists` | o `update()` de um segundo objeto `fragments` |
| `mode_invalid` | o `assiny.fragments()` nasceu com `mode` inválido |
| `request_timeout` | sem resposta em 2 s |

Chave desconhecida é ignorada, com aviso no console.

## `fragments.create('payment', options)` [#fragmentscreatepayment-options]

```js
const payment = fragments.create('payment', {
  paymentMethodTypes: ['card', 'pix'],
  combine: 'auto',
  partsFrom: 'component',
  fields: { billingDetails: { name: 'auto', address: { country: 'auto' } } },
  wallets: { applePay: 'auto' },
});
```

| Opção | Valores | O que faz |
|---|---|---|
| `paymentMethodTypes` | lista não vazia de `'card'` e `'pix'`, obrigatória | as abas, nessa ordem; a primeira começa selecionada; item repetido é removido com aviso no console |
| `combine` | `'auto'` (padrão), `'never'` ou lista de `'card_card'` e `'card_pix'` | com `'auto'`, a aba Combinar métodos aparece com `amount` de pelo menos 2 centavos: "Cartão + Cartão" (`card_card`) com `'card'`, "Cartão + PIX" (`card_pix`) com `'card'` e `'pix'`; a lista oferece só as combinações listadas, na ordem de `'auto'`: `['card_card', 'card_pix']` com `'card'` e `'pix'` vale `'auto'` (com só `'card'`, passe `['card_card']`; a lista cheia é `options_invalid`) e `[]` vale `'never'` |
| `partsFrom` | `'component'` (padrão) ou `'page'` | com `'page'`, o fragment esconde o campo de valor do Combinar e mostra a divisão de `fragments.update({ parts })`; `'page'` com `combine: 'never'` ou `[]` é `options_invalid`; Partes numa combinação fora da lista são `parts_invalid` |
| `fields.billingDetails.name` | `'auto'` (padrão) ou `'never'` | `'never'` esconde o campo Nome no cartão |
| `fields.billingDetails.address.country` | `'auto'` (padrão) ou `'never'` | `'never'` esconde o campo País |
| `wallets.applePay` | `'auto'` (padrão) ou `'never'` | reservado: aceito e sem efeito nesta versão |

Devolve `{ mount, unmount, on, off }`. O primeiro argumento só aceita `'payment'`. Os erros da criação chegam pelo `loaderror` do `mount`:

| `code` | Quando |
|---|---|
| `fragment_type_invalid` | o tipo não é `'payment'` |
| `fragment_already_exists` | já existe um fragment nesta instância, ou o `create()` é de um segundo objeto `fragments` |
| `payment_method_types_invalid` | `paymentMethodTypes` ausente, vazia ou com valor fora de `'card'` e `'pix'` |
| `options_invalid` | as opções não são um objeto, uma chave é desconhecida, um valor está fora da lista, um item de `combine` é repetido, desconhecido ou não permitido por `paymentMethodTypes` (`['card_pix']` sem `'pix'`), ou `partsFrom: 'page'` com `combine: 'never'` ou `[]` |

Um `create()` com opções inválidas não ocupa o lugar do fragment da instância: um `create()` válido depois ainda funciona.

## `payment.mount(selector)` [#paymentmountselector]

```js
payment.mount('#pagamento');
```

`selector` é um seletor CSS que casa com exatamente um elemento. O fragment ocupa 100% da largura dele e a altura acompanha o conteúdo. O `ready` sai uma vez por `mount`. Elemento removido do DOM conta como desmontado. Os erros chegam pelo `loaderror`:

| `code` | Quando |
|---|---|
| `invalid_publishable_key`, `insecure_page`, `page_origin_invalid` | a chave da instância foi recusada |
| o código do `create()` | `fragment_type_invalid`, `fragment_already_exists`, `payment_method_types_invalid` ou `options_invalid` |
| `mode_invalid` | o `mode` do `assiny.fragments()` não é `'payment'` |
| `mount_target_not_found` | o seletor não casa com exatamente um elemento |
| `already_mounted` | o fragment já está montado |
| `amount_invalid`, `currency_invalid`, `invalid_client_secret`, `payment_method_types_invalid` | o `assiny.fragments()` tem um desses valores inválido, ou o PIX está com moeda que não é `'brl'`; o fragment monta quando um `fragments.update()` corrige |
| `fragments_not_ready` | o SDK ou o fragment não carregou em 10 s |
| `request_timeout` | o SDK não respondeu à configuração (5 s) ou à montagem (10 s) |
| `unknown` | uma falha inesperada |

## `payment.unmount()` [#paymentunmount]

Remove o iframe do fragment e apaga o que foi digitado. Um `createPaymentMethod` em curso resolve `{ error }` com `fragments_not_ready`. Depois, `mount()` monta de novo. Um `handleNextAction` em curso não para.

## `payment.on(event, handler)` e `payment.off(event, handler)` [#paymentonevent-handler-e-paymentoffevent-handler]

Ligam e desligam uma função num evento e devolvem o próprio `payment`. Nome fora da lista, ou `handler` que não é função, é ignorado.

| Evento | Quando | Payload |
|---|---|---|
| `ready` | uma vez por `mount`, com o fragment configurado; não espera as fontes | `{ fragmentType: 'payment' }` |
| `change` | logo depois do `ready` e a cada troca de aba, mudança de campo ou mudança do total | `{ fragmentType: 'payment', value: { type, save, parts }, complete, empty, error }` |
| `focus` | o foco entra no fragment | `{ fragmentType: 'payment' }` |
| `blur` | o foco sai do fragment | `{ fragmentType: 'payment' }` |
| `loaderror` | o fragment não montou | `{ fragmentType: 'payment', error: { type, code, message } }` |

O payload do `change`:

| Campo | Tipo | O que é |
|---|---|---|
| `value.type` | `'card'`, `'pix'` ou `'combine'` | a aba selecionada |
| `value.save` | `boolean`, só com `'card'` | nesta versão, sempre `false` |
| `value.parts` | `[{ method, amount }, { method, amount }]`, só com `'combine'` | as Partes, quando o valor da primeira é maior que zero e menor que o total, mesmo com o cartão incompleto |
| `complete` | `boolean` | cartão: número, validade e CVC completos e válidos; PIX: sempre `true`; Combinar: o valor válido e os cartões da combinação completos |
| `empty` | `boolean` | nada digitado; no PIX, sempre `true` |
| `error` | `{ code, message }`, quando há | o erro que está na tela |

Nenhum evento traz o que foi digitado no cartão.

## `assiny.createPaymentMethod({ fragments, params })` [#assinycreatepaymentmethod-fragments-params-]

```js
const { parts, error } = await assiny.createPaymentMethod({
  fragments,
  params: { billing_details: { name, email, phone, tax_id, tax_id_type, address } },
});
```

- `fragments`: o objeto devolvido pelo `assiny.fragments()` desta instância.
- `params.billing_details`: os dados do pagador. Cada campo que você manda vale como veio, para todas as Partes; campo que você não manda entra do fragment se o campo correspondente está ligado (`'auto'`) e preenchido: Nome no cartão em `name`, País em `address.country`, por cartão. Nada é sobrescrito; `''`, `null` e `undefined` contam como não mandado. No PIX, só o que você mandou. Chaves aceitas: `name`, `email`, `phone`, `tax_id`, `tax_id_type` e `address` com `line1`, `line2`, `city`, `state`, `postal_code` e `country`; outra chave é descartada, com aviso no console. Na política padrão, o pagador precisa de `email` no cartão e no PIX e de `name` no cartão; sem eles, a criação do método de pagamento ou o confirm recusa com `payer_field_missing`.

Resolve `{ parts: [{ method, amount, payment_method }] }`, com `method` `'card'` ou `'pix'` e `payment_method` o `pm_` criado, na ordem das Partes da aba selecionada (cartão ou PIX: uma Parte com o `amount` inteiro; Combinar: duas, que somam o `amount`), ou `{ error: { type, code, message, part } }`, com `part` só quando a falha é de uma Parte.

O que acontece:

- O fragment confere os campos da aba selecionada; campo incompleto ou inválido resolve um `validation_error` sem ir à API e mostra o erro no campo.
- Enquanto os métodos são criados, os campos ficam desligados (`.Input:disabled`).
- Um `pm_` por Parte, em ordem; a primeira falha interrompe as seguintes e o erro diz em `part` o índice da Parte que falhou.
- Depois de um `{ error }`, o que foi digitado fica e o campo que a API apontou fica marcado; na nova tentativa, a Parte que não mudou reusa o `pm_` já criado. Depois de um sucesso, a próxima chamada cria `pm_` novos.

| `code` | Quando |
|---|---|
| `fragments_not_ready` | o `fragments` não é o desta instância, o fragment não está montado e pronto, ou houve `unmount()` durante a chamada |
| `parameter_invalid` | `params` não é um objeto ou não pode ser copiado; ou a API recusou um parâmetro |
| `request_in_progress` | há outro `createPaymentMethod` ou um `handleNextAction` em curso |
| os de `validation_error` | um campo do fragment, ou a divisão do Combinar (`incomplete_parts`, `parts_invalid`) |
| os de `card_error` e os outros da API | a resposta da API, pela tabela [abaixo](#respostas-da-api) |
| `request_timeout` | 8 s sem resposta da API num `pm_`, ou 20 s na chamada inteira |

## `assiny.handleNextAction({ clientSecret })` [#assinyhandlenextaction-clientsecret-]

```js
const { paymentIntent, error } = await assiny.handleNextAction({
  clientSecret: 'pi_..._secret_...',
});
```

- `clientSecret`: o `client_secret` do pagamento, na forma `pi_..._secret_...`. O da chamada vence o guardado por `assiny.fragments({ clientSecret })` ou `fragments.update({ clientSecret })`.
- Funciona só com a instância de `Assiny()`: não precisa do fragment montado.

O SDK lê o pagamento a cada 3 s e segue o próximo passo, uma ação por vez, até o fim:

| Status lido | O que o SDK faz |
|---|---|
| `succeeded`, `canceled`, `requires_payment_method`, `requires_confirmation` | encerra e resolve `{ paymentIntent }`; em `succeeded` com a tela do QR aberta, mostra o pagamento confirmado e fecha 4 s depois |
| `processing` | lê de novo, sem desenhar |
| `requires_action` com o QR do PIX | abre ou mantém a tela do QR do PIX ([PIX e a tela do QR](/fragments/pix.md)) |
| `requires_action` com outra ação, ou sem ação | fecha a tela e resolve `{ error }` com `next_action_unsupported` |
| qualquer outro | resolve `{ error }` com `unknown` |

Esc ou Fechar na tela do QR param a leitura e resolvem `{ paymentIntent }` com o estado da última leitura, em geral `requires_action`; uma nova chamada reabre a ação atual. Até a primeira leitura há 20 s; depois, a promessa vive enquanto a leitura vive.

Resolve:

```json
{
  "paymentIntent": {
    "id": "pi_...",
    "object": "payment_intent",
    "livemode": false,
    "status": "succeeded",
    "amount": 12990,
    "currency": "brl",
    "parts": [
      { "id": "pip_...", "position": 1, "method": "pix", "amount": 12990, "status": "succeeded" }
    ],
    "last_payment_error": null
  }
}
```

`parts` traz `id`, `position`, `method`, `amount` e `status` de cada Parte; `last_payment_error` é `{ type, code, message }` ou `null`. Sem `next_action` e sem os outros campos do pagamento.

| `code` | Quando |
|---|---|
| `invalid_client_secret` | o `clientSecret` não tem a forma `pi_..._secret_...` |
| `client_secret_missing` | sem `clientSecret` na chamada e nenhum guardado |
| `request_in_progress` | há outro `handleNextAction` ou um `createPaymentMethod` em curso |
| `fragments_not_ready` | o SDK não carregou |
| `payment_intent_not_found` | a primeira leitura respondeu 404 |
| o código da tabela [abaixo](#respostas-da-api), como `api_key_invalid`, `permission_denied` ou `unknown` | a primeira leitura foi recusada |
| `api_unavailable` | a primeira leitura falhou na rede, respondeu 500 ou mais, ou veio ilegível |
| `request_timeout` | 8 s sem resposta na primeira leitura, ou 20 s sem a primeira leitura |
| `next_action_unsupported` | o pagamento pede uma ação que o SDK não conhece |
| `overlay_not_ready` | a tela do QR não abriu em 10 s |

Depois da primeira leitura, uma leitura que falha não encerra: o SDK tenta na volta seguinte e, depois de três falhas seguidas, a tela do QR mostra "Sem conexão. Tentando de novo.".

## Prazos [#prazos]

| O quê | Prazo | Ao vencer |
|---|---|---|
| a configuração enviada pelo `assiny.fragments()` | 5 s | `loaderror` com `request_timeout` |
| `mount()` | 10 s | `loaderror` com `request_timeout` |
| `unmount()` | 2 s | nada visível: o iframe já saiu |
| `fragments.update()` | 2 s | `{ error }` com `request_timeout` |
| `createPaymentMethod()`, a chamada inteira | 20 s | `{ error }` com `request_timeout` |
| `handleNextAction()`, até a primeira leitura | 20 s | `{ error }` com `request_timeout` |
| cada chamada à API (a criação de um `pm_`, uma leitura do pagamento) | 8 s | `request_timeout`; depois da primeira leitura, nova tentativa |
| o intervalo da leitura do pagamento | 3 s | — |
| a carga do SDK, do fragment e da tela do QR | 10 s | `fragments_not_ready` (SDK e fragment) ou `overlay_not_ready` (tela do QR) |
| a tela do QR depois do `succeeded` | 4 s | fecha e resolve `{ paymentIntent }` |

## Erros [#erros]

`{ type, code, message }`, e `part` quando a falha é de uma Parte no `createPaymentMethod`. A `message` vem do catálogo do SDK, no `locale` da instância, nunca do servidor. A lista é fechada: um código fora dela chega como `api_error` `unknown`.

| `type` | `code` | `message` (pt-BR) |
|---|---|---|
| `validation_error` | `incomplete_number` | `O número do cartão está incompleto.` |
| `validation_error` | `invalid_number` | `O número do cartão é inválido.` |
| `validation_error` | `incomplete_expiry` | `A validade do cartão está incompleta.` |
| `validation_error` | `invalid_expiry_month` | `O mês de validade do cartão é inválido.` |
| `validation_error` | `invalid_expiry_year` | `O ano de validade do cartão é inválido.` |
| `validation_error` | `incomplete_cvc` | `O código de segurança está incompleto.` |
| `validation_error` | `invalid_cvc` | `O código de segurança é inválido.` |
| `validation_error` | `card_brand_not_accepted` | `Esta bandeira não é aceita.` |
| `validation_error` | `incomplete_parts` | `Preencha as duas partes do pagamento.` |
| `validation_error` | `parts_invalid` | `A divisão do pagamento é inválida.` |
| `card_error` | `invalid_number` | `O número do cartão é inválido.` |
| `card_error` | `incorrect_number` | `O número do cartão está incorreto.` |
| `card_error` | `invalid_expiry_month` | `O mês de validade do cartão é inválido.` |
| `card_error` | `invalid_expiry_year` | `O ano de validade do cartão é inválido.` |
| `card_error` | `invalid_cvc` | `O código de segurança é inválido.` |
| `card_error` | `card_not_supported` | `Este cartão não é aceito.` |
| `invalid_request_error` | `payer_field_missing` | `Falta um dado do pagador.` |
| `invalid_request_error` | `email_invalid` | `O e-mail é inválido.` |
| `invalid_request_error` | `method_not_supported` | `Este método de pagamento não é aceito.` |
| `invalid_request_error` | `parameter_invalid` | `Um parâmetro enviado é inválido.` |
| `invalid_request_error` | `api_key_invalid` | `A chave de API é inválida.` |
| `invalid_request_error` | `permission_denied` | `A chave de API não tem permissão para esta chamada.` |
| `invalid_request_error` | `invalid_publishable_key` | `A chave publicável é inválida.` |
| `invalid_request_error` | `insecure_page` | `Uma chave live só funciona em página HTTPS.` |
| `invalid_request_error` | `invalid_client_secret` | `O client_secret é inválido.` |
| `invalid_request_error` | `amount_invalid` | `O valor é inválido.` |
| `invalid_request_error` | `currency_invalid` | `A moeda é inválida.` |
| `invalid_request_error` | `mode_invalid` | `O modo é inválido; use 'payment'.` |
| `invalid_request_error` | `payment_method_types_invalid` | `As opções de pagamento são inválidas.` |
| `invalid_request_error` | `options_invalid` | `As opções do Payment Fragment são inválidas.` |
| `invalid_request_error` | `fragment_type_invalid` | `Tipo de Payment Fragment desconhecido; só 'payment' existe.` |
| `invalid_request_error` | `fragment_already_exists` | `Já existe um Fragments ou Payment Fragment nesta instância.` |
| `invalid_request_error` | `mount_target_not_found` | `O seletor do mount não casa com exatamente um elemento.` |
| `invalid_request_error` | `page_origin_invalid` | `A página precisa de uma origem real para montar o Payment Fragment.` |
| `invalid_request_error` | `fragments_not_ready` | `O Payment Fragment não está montado e pronto.` |
| `invalid_request_error` | `already_mounted` | `O Payment Fragment já está montado.` |
| `invalid_request_error` | `request_in_progress` | `Já existe uma chamada em andamento.` |
| `invalid_request_error` | `client_secret_missing` | `Falta o client_secret da Intenção.` |
| `invalid_request_error` | `payment_intent_not_found` | `A Intenção de pagamento não foi encontrada.` |
| `api_error` | `api_unavailable` | `O serviço está indisponível; tente de novo.` |
| `api_error` | `rate_limited` | `Muitas tentativas; aguarde um pouco.` |
| `api_error` | `request_timeout` | `A chamada demorou demais.` |
| `api_error` | `next_action_unsupported` | `O próximo passo deste pagamento não é suportado.` |
| `api_error` | `overlay_not_ready` | `A janela do pagamento não abriu; tente de novo.` |
| `api_error` | `unknown` | `Ocorreu um erro inesperado.` |

## Respostas da API [#respostas-da-api]

Como o SDK traduz a resposta da API em `type` e `code`. A `message` do servidor nunca é lida.

Na criação de cada `pm_` (`createPaymentMethod`):

| Resposta da API | `type` e `code` do SDK |
|---|---|
| 400 `card_error` com `invalid_number`, `incorrect_number`, `invalid_expiry_month`, `invalid_expiry_year`, `invalid_cvc` ou `card_not_supported` | `card_error`, o mesmo `code` |
| 400 com `payer_field_missing`, `email_invalid` ou `method_not_supported` | `invalid_request_error`, o mesmo `code` |
| 400 com `parameter_unknown`, `parameter_missing` ou `parameter_invalid` | `invalid_request_error` `parameter_invalid` |
| 401 `api_key_invalid` | `invalid_request_error` `api_key_invalid` |
| 403 `permission_denied` | `invalid_request_error` `permission_denied` |
| 429 | `api_error` `rate_limited` |
| 502, 503, 504, falha de rede, corpo sem o objeto `error`, sucesso sem um `pm_` | `api_error` `api_unavailable` |
| 8 s sem resposta | `api_error` `request_timeout` |
| qualquer outra | `api_error` `unknown` |

Na leitura do pagamento (`handleNextAction`):

| Resposta da API | O que o SDK faz |
|---|---|
| 200 com o pagamento | segue |
| 200 ilegível | `api_error` `api_unavailable` |
| 429 | não é erro: espera o `Retry-After` (em segundos ou data, no mínimo 1 s; sem ele, 3 s) e lê de novo |
| 404 | `invalid_request_error` `payment_intent_not_found` |
| 500 ou mais, falha de rede | `api_error` `api_unavailable` |
| 8 s sem resposta | `api_error` `request_timeout` |
| 400, 401, 403 e as demais | a tabela da criação |

Na leitura, o erro só encerra o `handleNextAction` na primeira leitura; nas seguintes, o SDK tenta de novo.

## Avisos no console [#avisos-no-console]

Os avisos saem só em pt-BR, com o prefixo `[Assiny]`, e nunca trazem o que foi digitado.

| Quando | Aviso |
|---|---|
| uma chave `sk_` no `Assiny()` | `[Assiny] Chave secreta no navegador: use a chave publicável pk_.` |
| um `locale` desconhecido | `[Assiny] locale desconhecido: '…'; usando pt-BR.` |
| um método repetido em `paymentMethodTypes` | `[Assiny] paymentMethodTypes repetido: '…' aparece mais de uma vez e foi deduplicado.` |
| um item da aparência fora da lista ou da faixa | `[Assiny] Appearance: '…' foi ignorado (fora da lista fechada ou da faixa).` |
| uma família ou um peso fora do catálogo | `[Assiny] Fonte: '…' foi ignorada (fora do catálogo).` |
| uma chave fora do contrato em `billing_details` | `[Assiny] billing_details: a chave '…' foi descartada (fora do contrato).` |
| uma chave desconhecida em `fragments.update`, `createPaymentMethod.params` ou `handleNextAction` | `[Assiny] fragments.update: a chave '…' foi ignorada (fora do contrato).` (e o mesmo com `createPaymentMethod.params` e `handleNextAction`) |
