Сервер для Tilda
Принимает оплату в USDC или SOL на Solana за заказ, оформленный на сайте на платформе Tilda, с пересчётом суммы из тенге. Деньги идут напрямую с кошелька покупателя на кошелёк продавца — сервер их не хранит и не пересылает.
Зачем здесь вообще нужен сервер
У Tilda нет собственного бэкенда, который продавец мог бы запустить сам, а API самой Tilda работает только на чтение — она не даёт стороннему коду самостоятельно пометить заказ оплаченным. Единственный способ сообщить Tilda «этот заказ оплачен» — прислать POST-запрос с верной подписью на адрес, который она сама выдаёт после настройки интеграции.
Значит, что-то должно принять заказ от Tilda, посчитать сумму в токенах, показать покупателю QR-код, проверить платёж в блокчейне и отправить это самое уведомление обратно. Это и есть роль сервера — он посредничает в уведомлении, а не в платеже. Разница существенная: перевод всё равно идёт напрямую с кошелька покупателя на кошелёк продавца, сервер лишь докладывает Tilda о факте, который уже произошёл в блокчейне.
Что нужно продавцу до начала
- Свой узел (RPC) Solana. Публичный узел (
https://api.devnet.solana.com) годится для проверки, но ненадёжен и жёстко ограничен по частоте запросов для боевой работы. На mainnet нужен платный RPC-провайдер (Helius, QuickNode, Triton и подобные). - Кошелёк-получатель — публичный адрес (base58), на который будут приходить платежи.
- Валюта магазина — тенге. Сервер понимает только её: заказы в другой валюте отклоняются.
- Почта (SMTP), на которую сервер будет слать письма о новых и просроченных заказах.
- Сервер или виртуальная машина с Docker и доменом (или поддоменом), на который уже выпущен TLS-сертификат.
Развёртывание
Сервер работает как отдельный контейнер Docker, не пересекаясь с другими сервисами на том же хосте ни сетью, ни томами.
cd tilda-server
# 1. Настройки — из примера, свой файл. config.json в .gitignore, в
# репозиторий не попадает.
cp config.example.json config.json
# заполнить config.json (см. таблицу ниже)
chmod 600 config.json # только владелец файла может его прочитать
# 2. nginx — из примера, под свой домен.
sudo cp nginx.example.conf /etc/nginx/sites-available/ваш-домен
sudo ln -s /etc/nginx/sites-available/ваш-домен /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# 3. Сертификат.
sudo certbot --nginx -d ваш-домен
# 4. Сборка и запуск.
docker compose up -d --build
docker compose logs -f
Порт сервера публикуется наружу только на 127.0.0.1 хоста
(ports: "127.0.0.1:8081:8081" в docker-compose.yml) — снаружи он доступен
только через nginx с уже выпущенным сертификатом. Из-за этого счётчик
попыток входа в /admin доверяет заголовку X-Forwarded-For только с
адресов из config.json → trustedProxyAddresses, а запрос от nginx до
процесса внутри сети моста Docker приходит с адреса шлюза этой подсети, а
не с 127.0.0.1. В примере настроек этот адрес (172.21.0.1 для подсети
172.21.0.0/24) уже вписан — при смене подсети в docker-compose.yml
поменяйте и его.
Заполнение config.json
Полный список полей — в config.example.json (с комментарием у каждого
поля). Вот что легко перепутать:
| Поле | Что туда идёт |
|---|---|
recipient |
публичный адрес кошелька-получателя (не приватный ключ!). Сервер откажется запускаться, если здесь известный системный адрес Solana или адрес из одного повторённого символа — платёж на такой адрес пропал бы безвозвратно |
rpcUrl |
адрес узла Solana, обязательно https:// |
cluster |
devnet для проверки, mainnet для боевой работы |
orderSecret |
секрет для проверки подписи заказа — придумайте сами, не короче 8 символов, впишите то же значение в настройки интеграции Tilda |
notifySecret |
ОТДЕЛЬНЫЙ секрет для подписи уведомлений — не тот же, что orderSecret. Сервер откажется запускаться, если они совпадают |
tildaNotifyUrl |
адрес, который САМА Tilda покажет вам после сохранения интеграции — см. следующий раздел |
publicUrl |
публичный адрес самого этого сервера, например https://pay.ваш-домен.kz |
successUrl / failureUrl |
необязательные адреса возврата покупателя на Tilda: страница «спасибо» и страница отказа. Без них покупатель остаётся на странице итога этого сервера. Не путайте с полями success_url/failure_url формы Tilda (см. таблицу ниже) — те не заверены подписью и для переадресации не используются |
adminPassword |
пароль входа в список заказов (/admin) |
merchantEmail |
куда слать письма о заказах |
trustedProxyAddresses |
адреса, которым доверяем заголовок X-Forwarded-For у /admin — по умолчанию только loopback; при развёртывании через docker-compose добавьте адрес шлюза моста |
Секреты (orderSecret, notifySecret, adminPassword, smtp.pass) можно
сгенерировать, например, командой openssl rand -hex 24.
Настройка интеграции в кабинете Tilda
В личном кабинете Tilda: Настройки сайта → Платежные системы → Универсальная платежная система → Новая платежная система (для разработчиков).
Адреса
| Поле формы Tilda | Значение |
|---|---|
| Название | SolanaPay-KZ (или своё) |
| API URL | https://ваш-сервер/tilda/pay |
| Тестовый API URL | тот же адрес — тестовый режим различается полем test_mode в теле запроса |
| Валюта | KZT |
Своего поля «URL для уведомлений» в этой форме нет — адрес, куда Tilda
пришлёт уведомление об оплате, назначает сама Tilda и показывает его
после того, как вы сохраните интеграцию. Скопируйте показанный адрес в
config.json → tildaNotifyUrl и перезапустите сервер
(docker compose restart).
Список соответствия полей
Имена параметров задаём мы — впишите точно так, строчными буквами, с подчёркиванием:
| Роль поля (по Tilda) | Имя параметра |
|---|---|
| Номер заказа | order_id |
| Сумма | amount — в тенге (целых единицах, не в тиынах) |
| Валюта | currency |
| Метка времени | timestamp — Unix-время (секунды) |
| Тестовый режим | test_mode — 1/0 |
| Описание заказа | description (до 255 символов) |
| Состав корзины | products — массив в JSON (не в base64) |
| Email / телефон / имя покупателя | email / phone / customer_name |
| Страница успеха / отказа | success_url / failure_url — сервер их принимает, но для переадресации не использует (не заверены подписью); адрес возврата укажите отдельно в config.json |
| URL уведомлений (если Tilda присылает его в заказе) | notify_url — сервер только сверяет его с config.tildaNotifyUrl и пишет расхождение в журнал |
| Подпись | signature |
Поля login, lang, country, receipt этот сервер не использует — можно
оставить как предлагает Tilda по умолчанию.
Подпись — для заказа и для уведомления отдельно
Настраивается дважды: один раз для заказа (секрет orderSecret), второй раз
для уведомления (галочку «использовать те же правила» — выключить,
указать секрет отдельно notifySecret). Строки для подписи различаются
меткой роли:
order|||||
notify|||||
order / notify) обязательна, даже
если секреты разные. Без неё, при случайном совпадении секретов, подписанный
заказ, который Tilda присылает через браузер покупателя, становится готовым
уведомлением об оплате — достаточно дописать status=paid. Метка
делает две подписи разными независимо от настройки секретов — это вторая,
самостоятельная линия обороны сверх запрета на совпадение
orderSecret/notifySecret.
Остальные настройки правила:
- Тип: «Особые правила».
- Исключать поля с пустыми значениями: выключить — наша сторона трактует пустое поле как пустую строку между разделителями, а не как повод сдвинуть остальные поля; если Tilda вырежет пустые поля целиком, разделители разъедутся и подпись перестанет сходиться.
- Алгоритм: SHA-256.
- Секрет как ключ алгоритма (HMAC): включить — обязательно, иначе секрет попадёт в строку как текст, а не как ключ HMAC, и проверка не примет такую подпись.
- Секрет не добавлять ни первым, ни последним элементом строки — он уже участвует как ключ HMAC.
- Привести к верхнему регистру: выключить.
- Итоговую подпись в base64: выключить — используется шестнадцатеричная запись.
Ответ на уведомление
| Поле формы Tilda | Значение |
|---|---|
| Поле признака успеха | status |
| Значение признака успеха | paid |
| Поле номера транзакции | transaction |
| Ответ при успехе | OK |
| Ответ при ошибке | ERROR |
| Формат JSON | выключить |
Запасной вход через вебхук формы
Пока заявка на «Новую платёжную систему» не одобрена модератором Tilda (или
если интеграция недоступна по другой причине), можно принимать заказы через
обычную форму на сайте и её вебхук — POST /tilda/webhook.
Этот вход выключен по умолчанию (config.json → "enableFormWebhook":
false). Включайте его явно, только если он действительно нужен: продавцу,
который уже пользуется одобренной платёжной интеграцией, лишний неподписанный
вход не нужен.
- В настройках формы на сайте: Формы → Webhook, адрес —
https://ваш-сервер/tilda/webhook. - Самый надёжный способ передать сумму — добавить в форму своё поле с
именем переменной
Payment, куда покупатель (или скрипт на странице) кладёт сумму заказа в тенге. - Сразу после сохранения адреса Tilda пришлёт проверочный запрос
(
test=test) — сервер отвечает на него200мгновенно. - Номер заявки хранится в базе с приставкой
form:— отдельное пространство номеров от платёжной интеграции, чтобы заявка этого входа не заняла случайно номер будущего заказа основного входа.
/admin), а не по
статусу в Tilda.
- У обычной формы Tilda нет документированного поля суммы — сервер
перебирает несколько вероятных вариантов по очереди, и этот разбор **не
проверен настоящей заявкой Tilda**, только предположениями о формате.
Первую реальную заявку стоит внимательно сверить со списком заказов и с
журналом сервера (docker compose logs).
- Используйте его как временную меру, а не как замену одобренной интеграции.
Проверка тестовым платежом
GET https://ваш-сервер/admin— должен спросить пароль (форма входа), а не показать список заказов.POST https://ваш-сервер/tilda/payбез поляsignature— должен ответить400.- В личном кабинете Tilda, после сохранения интеграции — оформите тестовый
заказ (или отправьте тестовую форму, если используете запасной вход).
Откройте выданную ссылку
/pay/<токен>, оплатите по QR кошельком с тестовой сети (приcluster: "devnet"вconfig.json) и убедитесь, что заказ дошёл до состояния «оплачен» (список/admin), пришло письмо продавцу, а после успешного уведомления — до «уведомлён» (для основного входа; запасной вход дальше «оплачен» не идёт).
Для проверки без настоящей Tilda в репозитории есть tools/fake-tilda.ts —
независимая реализация протокола, которая умеет оформить подписанный заказ
на сервер и принять от него уведомление, включая случаи, которые в живом
кабинете не воспроизвести: неверную подпись, повторный запрос, отказ принять
уведомление.
Что сервер не делает
Возвраты, частичные оплаты, приём в других валютах, кроме тенге, многопользовательский режим и личный кабинет с регистрацией в него не входят. Подробнее о том, что делать при спорном платеже и что проект вообще не берёт на себя — на странице «Безопасность».
Лицензия
MIT.