# HTML формы

Один из способов добавить оплату криптовалютой на сайт, в интернет-магазин или в бота. Покупатель нажимает кнопку, открывается платежная страница со счетом.

Форма отправляется только методом POST на `https://dash.bitsby.app/invoices/form` и содержит два поля:

| Поле        | Что в нем                                  |
| ----------- | ------------------------------------------ |
| `data`      | base64url от JSON с полями счета           |
| `signature` | HMAC-SHA256 от строки `data`, 64 знака hex |

Одновременно у проекта может быть не более 50 неоплаченных счетов, созданных этим способом.

## Пример формы

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

Оба значения подставляет сервер магазина при отрисовке страницы. Секрет в разметку не попадает.

Атрибут `target="_blank"` необязателен: с ним корзина остается открытой в исходной вкладке.

## Как собирается контейнер

1. Собрать JSON с полями счета.
2. Закодировать его в base64url. Это строка `data`.
3. Посчитать `signature = HMAC-SHA256(data, formSecret)`.

Подписывается строка `data` целиком, поэтому порядок ключей в JSON, отступы и способ экранирования юникода значения не имеют. Стандартный base64 приемник тоже принимает, с паддингом или без.

Пересобирать `data` после подписи нельзя: изменился хоть байт — подпись считается заново.

Как считать подпись и готовые примеры на четырех языках — в разделе «[Подпись HTML-формы](./form-signature.md)».

## Ключи внутри data

| Ключ           | Обязательный | Что в нем                                                                                                                                                          |
| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projectId`    | да           | ID проекта из настроек в личном кабинете, uuid                                                                                                                     |
| `amountFiat`   | да           | Сумма от 1 до 100000, не более двух знаков после точки. Строкой `"10.50"` или числом `10.5`                                                                        |
| `currencyFiat` | да           | Валюта фиата: USD, EUR или RUB                                                                                                                                     |
| `timeToPay`    | да           | Срок оплаты в часах: 0.5, 1, 3, 6, 12. Строкой или числом                                                                                                          |
| `description`  | нет          | Описание для покупателя, до 1000 знаков                                                                                                                            |
| `serviceData`  | нет          | Идентификатор заказа на стороне магазина, до 1000 знаков. Покупателю не показывается, но виден в исходном коде формы — не передавайте здесь ничего чувствительного |
| `output`       | нет          | `errors` — показывать подробности ошибок заполнения                                                                                                                |

Необязательный ключ можно не класть в JSON — это то же самое, что пустая строка. Порядок ключей любой, незнакомые ключи приемник не читает.

Значения — строки или числа JSON. Логические значения, массивы и вложенные объекты значениями полей не считаются и приходят как пустая строка.

Пример содержимого `data`:

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

Здесь сумма и срок оплаты переданы числами, строкой они тоже принимаются. Необязательный `output` не задан вовсе.

## Формат суммы

Только цифры и точка, не более двух знаков после точки: `10`, `10.00`, `1000.50`. Пробелы по краям обрезаются.

Любой другой формат отклоняется — разделители тысяч, запятая вместо точки, экспоненциальная запись, знак перед числом, символ валюты. Округления нет: третий знак после точки не отбрасывается, а дает отказ.

## Секрет формы

Секрет лежит в настройках проекта в личном кабинете, строка «Секрет формы». Его выдает сервер при создании проекта. Задать свое значение нельзя, секрет можно только перевыпустить — например, если он скомпрометирован.

Секрет должен оставаться на сервере магазина. Если он попадет в HTML витрины, подпись потеряет смысл.

Секрет формы и секрет вебхука — разные ключи, смешивать их нельзя.

## Идентификатор заказа

Заполняйте `serviceData` всегда: кладите туда идентификатор заказа на своей стороне. Значение приходит обратно в [уведомлении об оплате](../../webhook-url/index.md) — по нему магазин находит заказ.

Заполненный `serviceData` защищает от дублей. Повторная отправка формы и двойной клик по кнопке не создают второй счет: покупателя направит на уже существующий неоплаченный. Совпадение ищется по полям заказа — проекту, сумме, валюте, описанию и `serviceData`, а не по байтам `data`.

Значение должно быть уникальным для каждого заказа. При одинаковом значении второй покупатель попадет на неоплаченный счет первого.

Пустой `serviceData` этой защиты лишает: распознать повтор нечем, каждая отправка создает новый счет. Дубли расходуют лимит в 50 неоплаченных счетов, а при оплате магазину нечем связать платеж с заказом.

## Ошибки и режим отладки

Форма проходит две проверки: сначала подпись и контейнер, потом заполнение полей.

| Что произошло                                                             | Что покажет сервис                                       |
| ------------------------------------------------------------------------- | -------------------------------------------------------- |
| Подпись не сошлась, контейнер не читается, проект чужой или не существует | Общая ошибка, без причины                                |
| Подпись сошлась, но поле заполнено неверно                                | Общая ошибка. С `"output":"errors"` — детализация ошибок |
| Обе проверки пройдены                                                     | Платежная страница со счетом                             |

На первой проверке причина не называется никогда и ни при каких настройках.

Пара `"output":"errors"` в JSON включает подробности второй проверки: сервис назовет поле, перечислит допустимые значения и сообщит, если достигнут потолок в 50 неоплаченных счетов. Ключ живет внутри подписанного контейнера, поэтому подставить его снаружи нельзя.

На время настройки формы кладите `"output":"errors"` в JSON, после настройки уберите ключ и пересоберите контейнер.

Если не сходится сама подпись, сверьте свои `data` и подпись с эталонным набором в разделе «[Подпись HTML-формы](./form-signature.md)».

## Что дальше

Счет, созданный формой, дальше обрабатывается так же, как счет из API или личного кабинета:

* **Подтверждение оплаты** приходит на [Webhook URL](../../webhook-url/index.md). Проверяйте [подпись уведомления](../../webhook-url/signature-verification.md) секретом вебхука и выдавайте товар только по нему.
* **Возврат покупателя на сайт** настраивается через Successful URL и Unsuccessful URL — раздел «Возврат покупателя на сайт магазина» на странице [Жизненный цикл счета](../../invoice-lifecycle.md). Переход на эти адреса оплату не подтверждает.
* **Статусы счета и срок поиска платежа** описаны в разделе [Жизненный цикл счета](../../invoice-lifecycle.md).
* **Покупатель перевел не ту сумму** — см. [Привязка платежей и несовпадение сумм](../../payment-matching.md).
