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 — раздел «Возврат покупателя на сайт магазина» на странице Жизненный цикл счета. Переход на эти адреса оплату не подтверждает.
- Статусы счета и срок поиска платежа описаны в разделе Жизненный цикл счета.
- Покупатель перевел не ту сумму — см. Привязка платежей и несовпадение сумм.