# Formulários HTML

Uma das formas de adicionar pagamentos em cripto a um site, uma loja virtual ou um bot. O cliente clica em um botão, e a página de pagamento com a cobrança se abre.

O formulário é enviado somente via POST para `https://dash.bitsby.app/invoices/form` e contém dois campos:

| Campo       | O que contém                                  |
| ----------- | --------------------------------------------- |
| `data`      | base64url do JSON com os campos da cobrança |
| `signature` | HMAC-SHA256 da string `data`, 64 caracteres hex |

Um projeto pode ter no máximo 50 cobranças em aberto criadas dessa forma a cada momento.

## Exemplo de formulário

```html
<form method="post" action="https://dash.bitsby.app/invoices/form" target="_blank">
    <input type="hidden" name="data" value="eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZm...">
    <input type="hidden" name="signature" value="f77d6da1be35bd2900e0bfed9f202b04...">
    <button type="submit">Pay</button>
</form>
```

O servidor da loja preenche os dois valores ao renderizar a página. O segredo nunca aparece na marcação.

O atributo `target="_blank"` é opcional: com ele, o carrinho permanece aberto na aba original.

## Como o contêiner é montado

1. Monte um objeto JSON com os campos da cobrança.
2. Codifique-o como base64url. Essa é a string `data`.
3. Calcule `signature = HMAC-SHA256(data, formSecret)`.

A string `data` é assinada como um todo, então a ordem das chaves no JSON, a indentação e o estilo de escape Unicode não importam. O receptor também aceita base64 padrão, com ou sem padding.

Não remonte `data` depois de assinar: se um único byte mudar, a assinatura precisa ser calculada de novo.

Como calcular a assinatura, com exemplos prontos em quatro linguagens, está em [Assinatura do formulário HTML](./form-signature.md).

## Chaves dentro de data

| Chave          | Obrigatória | O que contém                                                                                                                                                       |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projectId`    | sim      | ID do projeto das configurações no painel, uuid                                                                                                                    |
| `amountFiat`   | sim      | Valor de 1 a 100.000, no máximo duas casas decimais. Como string `"10.50"` ou número `10.5`                                                                       |
| `currencyFiat` | sim      | Moeda fiat: USD, EUR ou RUB                                                                                                                                        |
| `timeToPay`    | sim      | Prazo de pagamento em horas: 0.5, 1, 3, 6, 12. Como string ou número                                                                                               |
| `description`  | não      | Descrição para o cliente, até 1.000 caracteres                                                                                                                     |
| `serviceData`  | não      | Identificador do pedido no lado da loja, até 1.000 caracteres. Não é exibido ao cliente, mas fica visível no código-fonte do formulário — não coloque nada sensível aqui |
| `output`       | não      | `errors` — exibir os detalhes dos erros de validação                                                                                                               |

Uma chave opcional pode ser omitida do JSON — é o mesmo que uma string vazia. As chaves podem vir em qualquer ordem; o receptor ignora chaves desconhecidas.

Os valores são strings ou números JSON. Booleanos, arrays e objetos aninhados não contam como valores de campo e chegam como uma string vazia.

Exemplo do conteúdo de `data`:

```json
{
    "projectId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "amountFiat": 10.5,
    "currencyFiat": "USD",
    "timeToPay": 1,
    "description": "Order #7, delivery",
    "serviceData": "order-7"
}
```

Aqui o valor e o prazo de pagamento são passados como números; strings também são aceitas. O `output` opcional não foi definido.

## Formato do valor

Somente dígitos e ponto decimal, no máximo duas casas decimais: `10`, `10.00`, `1000.50`. Espaços ao redor são removidos.

Qualquer outro formato é rejeitado — separadores de milhar, vírgula em vez de ponto, notação exponencial, sinal antes do número, símbolo de moeda. Não há arredondamento: uma terceira casa decimal não é truncada, ela causa rejeição.

## Segredo do formulário

O segredo fica nas configurações do projeto no painel, no campo Segredo do formulário (Form secret). O servidor o emite quando o projeto é criado. Não é possível definir um valor próprio; o segredo só pode ser reemitido — por exemplo, se for comprometido.

O segredo deve permanecer no servidor da loja. Se ele acabar no HTML da vitrine, a assinatura perde o sentido.

O segredo do formulário e o segredo do webhook são chaves diferentes; não os confunda.

## Identificador do pedido

Sempre preencha `serviceData`: coloque ali o seu próprio identificador do pedido. O valor volta na [notificação de pagamento](../../webhook-url/index.md) — a loja o usa para encontrar o pedido.

Um `serviceData` preenchido protege contra duplicatas. Reenviar o formulário ou clicar duas vezes no botão não cria uma segunda cobrança: o cliente é enviado à cobrança em aberto existente. A correspondência é feita pelos campos do pedido — projeto, valor, moeda, descrição e `serviceData` — e não pelos bytes de `data`.

O valor deve ser único por pedido. Com o mesmo valor, um segundo cliente cai na cobrança em aberto do primeiro.

Um `serviceData` vazio remove essa proteção: não há pelo que reconhecer uma repetição, e cada envio cria uma nova cobrança. As duplicatas consomem o limite de 50 cobranças em aberto e, quando uma é paga, a loja não tem como ligar o pagamento a um pedido.

## Erros e modo de depuração

O formulário passa por duas verificações: primeiro a assinatura e o contêiner, depois os valores dos campos.

| O que aconteceu                                                                                        | O que o serviço exibe                                     |
| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
| A assinatura não corresponde, o contêiner é ilegível, o projeto pertence a outra pessoa ou não existe | Um erro genérico, sem indicar o motivo                    |
| A assinatura corresponde, mas um campo está preenchido incorretamente                                  | Um erro genérico. Com `"output":"errors"` — erros detalhados |
| As duas verificações passaram                                                                          | A página de pagamento com a cobrança                      |

A primeira verificação nunca revela o motivo, sob nenhuma configuração.

O par `"output":"errors"` no JSON ativa os detalhes da segunda verificação: o serviço nomeia o campo, lista os valores permitidos e informa quando o teto de 50 cobranças em aberto é atingido. A chave fica dentro do contêiner assinado, então não pode ser injetada de fora.

Durante a configuração do formulário, coloque `"output":"errors"` no JSON; depois de configurado, remova a chave e remonte o contêiner.

Se a própria assinatura não corresponder, compare seus `data` e signature com o conjunto de referência em [Assinatura do formulário HTML](./form-signature.md).

## O que vem a seguir

Uma cobrança criada pelo formulário é processada da mesma forma que uma criada pela API ou no painel:

* **A confirmação do pagamento** chega à [Webhook URL](../../webhook-url/index.md). Verifique a [assinatura da notificação](../../webhook-url/signature-verification.md) com o segredo do webhook e entregue os pedidos somente com base nela.
* **O retorno do cliente ao seu site** é configurado pela URL de sucesso (Successful URL) e pela URL de falha (Unsuccessful URL) — veja a seção “Retorno do cliente ao site da loja” na página [Ciclo de vida da cobrança](../../invoice-lifecycle.md). Chegar a essas URLs não confirma o pagamento.
* **Os status da cobrança e a janela de busca de pagamentos** estão descritos em [Ciclo de vida da cobrança](../../invoice-lifecycle.md).
* **O cliente transferiu um valor errado** — veja [Vinculação de pagamentos e divergências de valores](../../payment-matching.md).
