HTML форми
Один зі способів додати оплату криптовалютою на сайт, в інтернет-магазин або в бота. Покупець натискає кнопку, відкривається платіжна сторінка з рахунком.
Форма надсилається лише методом POST на https://dash.bitsby.app/invoices/form і містить два поля:
| Поле | Що в ньому |
|---|---|
data | base64url від JSON із полями рахунку |
signature | HMAC-SHA256 від рядка data, 64 знаки hex |
Одночасно у проєкта може бути не більше ніж 50 неоплачених рахунків, створених у цей спосіб.
Приклад форми
<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" необов’язковий: із ним кошик залишається відкритим у вихідній вкладці.
Як збирається контейнер
- Зібрати JSON із полями рахунку.
- Закодувати його в base64url. Це рядок
data. - Обчислити
signature = HMAC-SHA256(data, formSecret).
Підписується рядок data цілком, тому порядок ключів у JSON, відступи та спосіб екранування юнікоду не мають значення. Стандартний base64 приймач теж приймає, з падингом або без.
Перезбирати data після підпису не можна: змінився хоч байт — підпис обчислюється заново.
Як обчислити підпис і готові приклади чотирма мовами — у розділі «Підпис HTML-форми».
Ключі всередині 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:
{
"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 завжди: кладіть туди ідентифікатор замовлення на своєму боці. Значення повертається у сповіщенні про оплату — за ним магазин знаходить замовлення.
Заповнений serviceData захищає від дублів. Повторне надсилання форми та подвійний клік по кнопці не створюють другий рахунок: покупця спрямує на вже наявний неоплачений. Збіг шукається за полями замовлення — проєктом, сумою, валютою, описом і serviceData, а не за байтами data.
Значення має бути унікальним для кожного замовлення. За однакового значення другий покупець потрапить на неоплачений рахунок першого.
Порожній serviceData позбавляє цього захисту: розпізнати повтор нічим, кожне надсилання створює новий рахунок. Дублі витрачають ліміт у 50 неоплачених рахунків, а після оплати магазину нічим пов’язати платіж із замовленням.
Помилки й режим налагодження
Форма проходить дві перевірки: спочатку підпис і контейнер, потім заповнення полів.
| Що сталося | Що покаже сервіс |
|---|---|
| Підпис не зійшовся, контейнер не читається, проєкт чужий або не існує | Загальна помилка, без причини |
| Підпис зійшовся, але поле заповнено неправильно | Загальна помилка. З "output":"errors" — деталізація помилок |
| Обидві перевірки пройдено | Платіжна сторінка з рахунком |
На першій перевірці причина не називається ніколи й за жодних налаштувань.
Пара "output":"errors" у JSON вмикає подробиці другої перевірки: сервіс назве поле, перелічить допустимі значення й повідомить, якщо досягнуто стелю в 50 неоплачених рахунків. Ключ живе всередині підписаного контейнера, тому підставити його ззовні не можна.
На час налаштування форми кладіть "output":"errors" у JSON, після налаштування приберіть ключ і перезберіть контейнер.
Якщо не сходиться сам підпис, звірте свої data і підпис з еталонним набором у розділі «Підпис HTML-форми».
Що далі
Рахунок, створений формою, далі обробляється так само, як рахунок з API або особистого кабінету:
- Підтвердження оплати надходить на Webhook URL. Перевіряйте підпис сповіщення секретом вебхука й видавайте товар лише за ним.
- Повернення покупця на сайт налаштовується через Successful URL і Unsuccessful URL — розділ «Повернення покупця на сайт магазину» на сторінці Життєвий цикл рахунку. Перехід на ці адреси оплату не підтверджує.
- Статуси рахунку та строк пошуку платежу описано в розділі Життєвий цикл рахунку.
- Покупець переказав не ту суму — див. Прив’язка платежів і розбіжність сум.