Pular para o conteúdo principal

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:

CampoO que contém
database64url do JSON com os campos da cobrança
signatureHMAC-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

<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.

Chaves dentro de data

ChaveObrigatóriaO que contém
projectIdsimID do projeto das configurações no painel, uuid
amountFiatsimValor de 1 a 100.000, no máximo duas casas decimais. Como string "10.50" ou número 10.5
currencyFiatsimMoeda fiat: USD, EUR ou RUB
timeToPaysimPrazo de pagamento em horas: 0.5, 1, 3, 6, 12. Como string ou número
descriptionnãoDescrição para o cliente, até 1.000 caracteres
serviceDatanãoIdentificador 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
outputnãoerrors — 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:

{
"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 — 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 aconteceuO que o serviço exibe
A assinatura não corresponde, o contêiner é ilegível, o projeto pertence a outra pessoa ou não existeUm erro genérico, sem indicar o motivo
A assinatura corresponde, mas um campo está preenchido incorretamenteUm erro genérico. Com "output":"errors" — erros detalhados
As duas verificações passaramA 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.

O que vem a seguir

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