# Assinatura do formulário HTML

O cliente pode alterar qualquer campo do formulário direto no navegador — por exemplo, definir um valor menor. Por isso os campos da cobrança viajam em um contêiner assinado: o serviço recalcula a assinatura e, se ela não corresponder, não cria a cobrança.

O servidor da loja calcula a assinatura. Somente o par pronto de `data` e `signature` vai ao navegador.

## O que é assinado

```
data      = base64url(json)
signature = HMAC-SHA256(data, formSecret)
```

O HMAC é calculado sobre a string `data`, não sobre os bytes do JSON. O receptor verifica a assinatura contra a string enviada como está, e só então a decodifica.

Disso decorre que não há canonicalização: a ordem das chaves, a indentação do JSON e o estilo de escape Unicode não afetam a assinatura. Qualquer codificador JSON padrão funciona.

## Conjunto de referência

Coloque estes valores no seu código e compare a saída.

O segredo de exemplo:

```
dvc3khto5WpjEPojZfYJsbJzFEPPVnRa
```

O valor e o prazo de pagamento como números; strings também são aceitas:

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

A string `data` é o base64url desses bytes. O padding foi removido aqui; o receptor também a aceita com padding:

```
eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZmLTRhN2ItOGM5ZC0wZTFmMmEzYjRjNWQiLCJhbW91bnRGaWF0IjoxMC41LCJjdXJyZW5jeUZpYXQiOiJVU0QiLCJ0aW1lVG9QYXkiOjEsImRlc2NyaXB0aW9uIjoiT3JkZXIgIzcsIGRlbGl2ZXJ5Iiwic2VydmljZURhdGEiOiJvcmRlci03In0
```

A assinatura desta string com o segredo acima:

```
09628020c47400c9231420450af642adfdb3c246f33e3e12cb8e28f96c2a785e
```

Com estes valores, os exemplos abaixo imprimem exatamente estes `data` e signature. O mesmo JSON com a chave `"output":"errors"` no final produz uma string `data` diferente e uma assinatura diferente.

Se `data` não corresponder embora o JSON pareça o mesmo, a causa é o codificador JSON: outra ordem de chaves ou espaços depois dos separadores. O receptor aceitará esse contêiner, mas ele não corresponderá à referência ao pé da letra.

## Exemplos

**PHP**

```php showLineNumbers
<?php

function signedContainer(array $fields, string $formSecret): array {
    $data = rtrim(strtr(base64_encode(json_encode($fields)), '+/', '-_'), '=');

    return [
        'data'      => $data,
        'signature' => hash_hmac('sha256', $data, $formSecret),
    ];
}

// Test values from the reference set. Before going live, 
// replace the secret with the form secret from your project settings
$container = signedContainer([
    'projectId'    => 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
    'amountFiat'   => 10.5,
    'currencyFiat' => 'USD',
    'timeToPay'    => 1,
    'description'  => 'Order #7, delivery',
    'serviceData'  => 'order-7',
], 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');

echo $container['data'], "\n";
echo $container['signature'], "\n";
```

**TypeScript**

```typescript showLineNumbers
import { createHmac } from 'node:crypto';

// Values are JSON strings or numbers, optional keys may be omitted
export interface FormFields {
    projectId: string;
    amountFiat: string | number;
    currencyFiat: string;
    timeToPay: string | number;
    description?: string;
    serviceData?: string;
    output?: string;
}

export interface FormContainer {
    data: string;
    signature: string;
}

export const signedContainer = (fields: FormFields, formSecret: string): FormContainer => {
    const data = Buffer.from(JSON.stringify(fields), 'utf8').toString('base64url');

    return {
        data,
        signature: createHmac('sha256', formSecret).update(data, 'utf8').digest('hex'),
    };
};

// Test values from the reference set. Before going live, 
// replace the secret with the form secret from your project settings
const container = signedContainer({
    projectId:    'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
    amountFiat:   10.5,
    currencyFiat: 'USD',
    timeToPay:    1,
    description:  'Order #7, delivery',
    serviceData:  'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');

console.log(container.data);
console.log(container.signature);
```

**JavaScript**

Este é um código para o servidor da loja, não para o navegador: a assinatura é calculada onde o segredo é guardado.

```js showLineNumbers
import { createHmac } from 'node:crypto';

const signedContainer = (fields, formSecret) => {
    const data = Buffer.from(JSON.stringify(fields), 'utf8').toString('base64url');

    return {
        data,
        signature: createHmac('sha256', formSecret).update(data, 'utf8').digest('hex'),
    };
};

// Test values from the reference set. Before going live, 
// replace the secret with the form secret from your project settings
const container = signedContainer({
    projectId:    'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
    amountFiat:   10.5,
    currencyFiat: 'USD',
    timeToPay:    1,
    description:  'Order #7, delivery',
    serviceData:  'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');

console.log(container.data);
console.log(container.signature);
```

**Python**

```python showLineNumbers
import base64
import hashlib
import hmac
import json

def signed_container(fields: dict, form_secret: str) -> dict:
    # compact separators: the default ones add spaces after ',' and ':'
    body = json.dumps(fields, separators=(',', ':'))
    data = base64.urlsafe_b64encode(body.encode('utf-8')).decode().rstrip('=')

    return {
        'data': data,
        'signature': hmac.new(
            form_secret.encode('utf-8'), data.encode('utf-8'), hashlib.sha256
        ).hexdigest(),
    }

# Test values from the reference set. Before going live, 
# replace the secret with the form secret from your project settings
container = signed_container({
    'projectId':    'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
    'amountFiat':   10.5,
    'currencyFiat': 'USD',
    'timeToPay':    1,
    'description':  'Order #7, delivery',
    'serviceData':  'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa')

print(container['data'])
print(container['signature'])
```

O argumento `separators` é necessário para a correspondência exata com a referência. O receptor também aceita as configurações padrão de JSON.

Os valores de exemplo são ASCII, mas o código funciona com qualquer Unicode sem alterações. PHP e Python escapam não ASCII como `\uXXXX` por padrão, o JavaScript envia UTF-8 puro — o receptor aceita os dois.

## Dois erros comuns

**Assinar o JSON em vez do contêiner.** O HMAC é calculado sobre a string `data`, isto é, sobre o base64url. O erro se manifesta como “tudo corresponde, mas a assinatura não”.

**Remontar o contêiner depois de assinar.** Se um único byte de `data` mudar, a assinatura fica inválida. Monte o par em um único lugar do seu código.

## Se a assinatura não corresponder

Percorra a cadeia JSON → `data` → assinatura; o primeiro elo que divergir da referência é a causa. Verifique `data` primeiro: ela não depende do segredo.
