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