Подключение магазина
Запрос backend магазина на ручной разбор доступен в текущем сервисе. Создание такого запроса не подтверждает оплату.
Базовый URL: https://pay.coolnik.ru. Версия API /v1. Руководство соответствует выпуску 0.18.3, схема 15; API-ключи test/live разделены. Сервис поддерживает USDT в TON и TRON (TRC20). Доступные вашему ключу методы всегда определяйте через GET /v1/payment-methods: они зависят от окружения и настроек проекта. Тестовое событие не разрешает выдавать реальный товар или начислять реальный депозит.
Быстрый старт
- Владелец создаёт проект в
/admin, добавляет публичные кошельки и выбирает тестовые методы оплаты. Ключи разных магазинов различаются, адреса могут совпадать. - Сервер магазина читает
GET /v1/payment-methods. Используйте ID из ответа, а не произвольное название сети. - Создаёт локальный заказ и сохраняет ключ операции, затем вызывает
POST /v1/invoices. - Сохраняет
invoice.idи создаёт ссылку черезPOST /v1/invoices/{id}/checkout-sessionsс отдельным Idempotency-Key. Тело необязательно:{ "locale": "en-GB" }задаёт язык страницы оплаты.ru/ru-RU→ русский; английский и другие языки → английский. Безlocaleвыбирается язык браузера. В returnedcheckout_urlуже есть нужный параметр, передавайте ссылку целиком. Переключатель RU / EN позволяет клиенту изменить язык; он сохраняется в текущей ссылке и QR. Не меняйте hash ссылки: это доступ к счёту. Нормализованный язык входит в идемпотентность сессии; для другой локали нужен новый ключ. Сумма, сеть и платёжные реквизиты от языка не зависят. - Передаёт клиенту только
checkout_url. Секрет магазина остаётся на сервере. - Сервер магазина опрашивает
GET /v1/events?after=<cursor>с интервалом, например, 30 секунд. Подтверждает счёт авторизованным GET и транзакционно обрабатывает заказ и курсор. В тестовой среде результатом может быть только тестовое исполнение.
Примеры типовых настроенных ID: usdt-nile — TronLink, usdt-nile-manual — обычный TRC20-перевод, usdt-ton-testnet — TUSDT с комментарием. Наличие метода определяется настройками конкретного проекта. Для основных сетей предусмотрены usdt-tron-mainnet (точная сумма), usdt-tron-mainnet-link (TronLink) и usdt-ton-mainnet (комментарий). Включённые сети возвращаются в конфигурации кабинета; список методов магазина берите из API. В mainnet допускается только оригинальный USDT. Поле environment у checkout и событий — test либо live; общий сервер может одновременно обслуживать обе среды.
Доступ
Владелец создаёт магазин в /admin или через локальную утилиту и передаёт project_id, секретный api_key и разрешённые payment_method_id защищённым способом. Для активаций и депозита создаются разные проекты даже при одном кошельке. API-ключ хранится только на сервере магазина. Ключ не передаётся клиентской оболочке GIZMO.
Запросы backend магазина: Authorization: Bearer <api_key>. Все POST этого API также требуют Idempotency-Key: 8–100 символов из букв, цифр, ._:-. Сохраняйте ключ операции до ответа; при таймауте повторяйте то же тело и тот же ключ. Другое тело с прежним ключом даст 409. Повтор возвращает первоначальный ответ; актуальный статус читается GET-запросом.
Ключи тестовой и рабочей среды
Кандидат со схемой 15 привязывает ключ к проекту и окружению. Новый проект получает ключ test: TON Testnet и TRON Nile. Для настоящего USDT владелец отдельно создаёт ключ live в админке после настройки основных сетей. Смена ключа выбранной среды не отзывает ключ другой. При отключении проекта перестают работать оба ключа.
GET /v1/payment-methods возвращает environment: "test" либо "live" и только доступные этому ключу методы. Передать другую среду в теле создания счёта нельзя. Счёт другой среды недоступен этому ключу: чтение, отмена, создание checkout-ссылки и операции разбора возвращают 404. Чужой метод при создании даёт PAYMENT_METHOD_NOT_ALLOWED (422).
Храните заказы, попытки, ключи операций и курсоры событий отдельно по (project_id, environment). Одинаковый external_order_id, attempt_no и Idempotency-Key могут использоваться независимо в test и live. /v1/events возвращает только события среды ключа; курсоры могут иметь пропуски. При переходе на live начинайте отдельный курсор с 0. Ключи подписи webhook настраиваются отдельно от ключей API.
При обновлении до схемы 15 прежний API-ключ становится только тестовым. Существующие счета, checkout-ссылки и история сохраняются. Для прежних live-счетов backend должен использовать новый live-ключ. Не повторяйте создание старого live-заказа с новым ключом: сохраните его invoice.id, получите актуальное состояние и продолжайте работу с ним.
Создание счёта
POST /v1/invoices
Authorization: Bearer <api_key>
Idempotency-Key: order-123-attempt-1
Content-Type: application/json
{
"external_order_id": "order-123",
"attempt_no": 1,
"amount": "10.000000",
"payment_method_id": "usdt-nile"
}Ответ 201 содержит id, project_id, order_id, attempt, route, due, received, excess, reference, created_at, expires_at, cancelled_at, status, version. Суммы в ответе — атомарные целые строки, время — Unix milliseconds UTC. due: "10000000" означает 10 USDT. Поля сети, контракта и адреса берутся из настроек проекта и не принимаются от браузера.
Одна незавершённая попытка на заказ. После истечения/отмены следующая попытка имеет увеличенный attempt_no и новый ключ идемпотентности. Сохраняйте все попытки: старый своевременный перевод может подтвердиться позже.
Реализованные методы
| Метод | Назначение |
|---|---|
| GET /v1/payment-methods | Методы, разрешённые данному магазину |
| POST /v1/invoices | Создать счёт по разрешённому методу |
| GET /v1/invoices/{id} | Прочитать актуальный счёт своего магазина |
| POST /v1/invoices/{id}/cancel | Отменить неоплаченный счёт |
| POST /v1/invoices/{id}/checkout-sessions | Создать checkout_token и checkout_url выбранного счёта |
| GET /v1/events?after=0 | Получить до 100 событий после курсора |
| GET /health/live | Процесс работает |
| GET /health/ready | БД и журнал готовы для текущей стадии |
| GET /health/payments | 503, если mainnet не включён или его наблюдатели не готовы; 200 при их рабочем состоянии. Не заменяет приёмку реального платежа |
Checkout-сессия возвращает invoice_id, checkout_token, expires_at, checkout_url. Открывайте возвращённый URL; токен находится во фрагменте #. Он разрешает доступ только к этому счёту. Доступны TronLink/Nile и обычные переводы Nile/TON Testnet. С версии 0.13.1 краткоживущий токен остаётся во фрагменте ссылки: перезагрузка и возврат из кошелька открывают тот же счёт. Ссылка может остаться в истории браузера; на общем ПК закрывайте checkout и очищайте состояние оболочки при выходе клиента. Сохраните исходный URL на сервере магазина и предоставляйте его только владельцу заказа. После восстановления интернета открытая страница сама обновляет статус; ручное обновление не требуется.
Подтверждение и выдача
Обычный перевод TRC20 с версии 0.3.0
Владелец может включить метод usdt-nile-manual с режимом TRON_EXACT_AMOUNT. Магазин создаёт счёт обычным POST /v1/invoices, передав этот payment_method_id. Ответ содержит base_due — исходную сумму в атомах, и due — итоговую сумму с небольшой добавкой. Покупателю нужно показывать именно due, без округления. Например, запрос 1 USDT может вернуть base_due: "1000000", due: "1000010", то есть к оплате 1.000010 тестового USDT. Конкретная добавка зависит от общих резервов.
Checkout показывает реквизиты обычного перевода и кнопки копирования. Он не требует подключения TronLink и не принимает txID как доказательство оплаты. GET /v1/checkout возвращает matching_mode, manual_payment_available, base_amount и итоговое amount в десятичной форме. Метод /v1/checkout/tron-instruction для обычного перевода недоступен. Пример выше относится к Nile; для настоящего USDT используйте разрешённый вашему live-ключу метод сети tron-mainnet. Не переносите идентификатор метода или контракт тестового токена в основную сеть.
Сервис обнаруживает кандидатов через историю TronGrid, а затем подтверждает Transfer двумя независимыми RPC. Уникальность учитывает сеть, контракт, адрес и полную итоговую сумму между всеми проектами. По умолчанию шаг 0.00001, добавка до 0.01 USDT; после оплаты — 3 суток резерва, после истечения или отмены — 7. Сначала используются ранее не выдававшиеся суммы. Старые комбинации допускаются только после ожидания и успешной актуальной сверки без незавершённых кандидатов или разборов. История сохраняется.
Переплата, недоплата, поздний или повторный перевод не закрывают произвольный счёт на общем адресе. Они попадают в разбор. При повторной выдаче реквизитов нельзя гарантированно отличить сильно задержанный старый платёж от нового: этот режим предполагает согласованный риск сопоставления по сумме. Для строгой предварительной привязки остаётся TronLink.
AMOUNT_RESERVE_EXHAUSTED (409) означает, что допустимые суммы заняты. AMOUNT_REUSE_REQUIRES_SCAN (409) — повторная выдача пока требует сверки или разбора. Не увеличивайте сумму самостоятельно и не отправляйте покупателя на старые реквизиты. Новый запрос после устранения причины можно повторить с прежним Idempotency-Key; успешный повтор всегда возвращает исходный счёт.
API владельца: GET/PUT /v1/admin/amount-reserves и POST /v1/admin/amount-reviews/{id}/close. PUT требует step, max_addition, paid_cooldown_days, closed_cooldown_days и Idempotency-Key. Изменение сроков применяется к новым счетам. Закрытие разбора не подтверждает оплату, не выполняет возврат и не начисляет рубли.
Обычный перевод TON
Метод TON_REFERENCE выдаёт точную сумму без добавки и уникальный комментарий reference. Checkout показывает USDT для основной сети либо отдельно обозначенный тестовый токен для Testnet, адрес получателя, сумму и обязательный комментарий; каждый реквизит можно скопировать. «QR адреса» содержит только адрес: при ручном переводе токен, сумму и комментарий нужно проверить в кошельке. Отдельный раздел «Открыть счёт на телефоне» содержит QR ссылки на весь счёт. Перевод с отсутствующим или чужим комментарием не может автоматически оплатить этот счёт. Комментарии не переиспользуются.
Доступна подготовка через TON Connect: подключение Tonkeeper, подготовка сервером и отдельное подтверждение в кошельке. Сумма, получатель и комментарий берутся из счёта; ответ кошелька не является доказательством оплаты. Автоматическое подтверждение проверено для тестового токена и настоящего USDT. Перед запуском магазина проверьте его собственный сценарий оплаты и однократного исполнения заказа.
TonAPI и Toncenter независимо подтверждают сеть, master, связанные jetton-кошельки, успешное исполнение, сумму и финальность. Первичный перевод должен соответствовать точной сумме счёта. После ручного зачисления части оплаты TON Connect предлагает точный остаток due - received, и наблюдатель принимает его по тому же комментарию. Исходная полная сумма тоже допускается для ранее выданных реквизитов, с сохранением переплаты в excess. Другие суммы и переводы вне срока требуют разбора; произвольные частичные переводы автоматически не суммируются. Нативная монета сети не считается оплатой USDT или тестового токена. Для mainnet сервис проверяет закреплённый оригинальный master USDT; тестовый master задаётся владельцем отдельно. Одинаковое название токена не подтверждает его подлинность.
Споры по оплате
С версии 0.12.0 установлены payment.review_required, payment.excess_recorded, payment.disputed и payment.dispute_resolved. В спорном счёте появляется payment_dispute: case_id, status (OPEN/RESOLVED), opened_at, resolved_at. PAID, received и excess сохраняются; version увеличивается при открытии/завершении спора.
Перед автоматической выдачей проверяйте отсутствие поля payment_dispute в событии и актуальном ответе GET. Даже RESOLVED сохраняет историю исключения: разрешение на услугу или корректировку депозита принимает сервер магазина отдельно. Повтор invoice.paid не создаётся. Подробные причины и ссылки на материалы доступны только сотрудникам в истории разбора.
Спор может возникнуть после ранее успешной выдачи. События спора направляйте в ручной разбор магазина, не списывайте депозит и не выдавайте услугу повторно. GET и транзакция магазина не являются общей атомарной операцией: спор, открытый после GET, обрабатывается следующим событием/сверкой. Пример backend сохраняет такие события как REVIEW без автоматического зачисления или списания.
Получение событий
Наблюдатели TRON Nile и TON Testnet проверены живыми переводами. Подключение выполняется через журнал событий; с версии 0.11.0 можно дополнительно настроить подписанные вебхуки. Ответ: { "events": [{ "cursor": "1", "payload": { "event_id": "...", "schema_version": 1, "environment": "test", "type": "invoice.paid", "invoice": { "...": "..." }, "occurred_at": 0 } }] }. Показанное invoice.paid — формат автоматически подтверждённого тестового события; публичного API для его искусственного создания нет.
Доставка через журнал — повторяемое чтение: сервер не удаляет событие после GET. Пустой ответ означает отсутствие новых событий после указанного курсора. Если возвращены 100 событий, обработайте их и продолжайте читать следующую страницу. Между значениями курсора допустимы пропуски, поскольку последовательность общая, а видимость ограничена проектом. Не считайте пропуск потерей события и не передвигайте курсор по локальному времени.
Курсор хранить отдельно для каждого магазина как строку, без преобразования в JavaScript number. Обработку события, изменение заказа и сохранение курсора выполнять транзакционно. При ошибке повторить обработку. Уникальность event_id защищает от повторной доставки; уникальность результата по заказу защищает от разных оплаченных попыток. Сверяйте проект, заказ, среду, сеть, сумму и статус через авторизованный GET. Страница возврата браузера не является подтверждением.
Для TRON_EXACT_AMOUNT сверяйте исходную стоимость заказа с base_due, а полученную сумму — с сохранённым due: это разные значения. Не пересчитывайте добавку по новым настройкам. Для TON due совпадает с суммой заказа. Проверяйте также закреплённый контракт токена и принадлежность счёта сохранённой попытке заказа. Неизвестное событие сохраняйте для диагностики без выдачи товара; сбой обработчика не должен терять событие.
Статусы счёта: PENDING, PARTIALLY_PAID, PAID, EXPIRED, CANCELLED. События изменения оплаты: invoice.partially_paid, invoice.paid, invoice.expired, invoice.cancelled. Дополнительные события: payment.review_required, payment.disputed, payment.dispute_resolved и refund.confirmed. Событие создания счёта не публикуется. Переводы по отменённым/несвоевременным счетам сохраняются для ручного разбора без автоматического PAID. Разбор платежей и проверка ручных возвратов доступны в кабинете. Роли Owner, Operator и ReadOnly, вход по паролю/MFA и ротация API-ключей уже доступны. Подписанные вебхуки требуют настроенного обработчика магазина.
Уведомления магазина
С версии 0.11.0 сервис поддерживает подписанные вебхуки для тестовых событий. Отправка начинается после настройки разрешённого HTTPS-адреса владельцем. Журнал /v1/events сохраняется и после включения вебхуков; событие не удаляется при доставке.
Владелец настраивает HTTPS-обработчик в кабинете проекта. Домен должен быть предварительно разрешён в конфигурации сервера UCPS; порт 443, действительный сертификат, публичная DNS-запись A (IPv4); AAAA нужна только при использовании IPv6. Перенаправления не допускаются. Секрет подписи отдельный для каждого endpoint/окружения и отличается от API-ключа магазина. Окружения test и live настраиваются отдельно. live доступно только после включения основной сети на сервере. Событие test содержит сеть Nile/Testnet, live — основную сеть TON/TRON; магазин обязан проверить это соответствие и своё project_id. Повторная настройка заменяет endpoint только выбранной среды.
Вебхук отправляется методом POST, Content-Type application/json. Тело содержит исходное событие и дополнительные поля delivery_id, key_id, timestamp. Последнее — Unix seconds в виде строки; даты счёта и occurred_at по-прежнему Unix milliseconds. Один event_id может приходить многократно с новыми delivery_id/временем/подписью.
Обязательные заголовки: X-UCPS-Key-Id, X-UCPS-Timestamp, X-UCPS-Event-Id, X-UCPS-Delivery-Id, X-UCPS-Signature. Подпись — SHA-256 HMAC в нижнем hex. Ключ из 64 hex-символов сначала декодируется в 32 байта. Подписанные байты:
UTF8("v1\n" + timestamp + "\n" + event_id + "\n" + delivery_id + "\n") || raw_bodyПроверяйте исходное тело до JSON-разбора и без пересериализации, допускайте возраст не больше ±300 секунд, сравнивайте подпись за постоянное время. Идентификаторы и timestamp в заголовках должны совпасть с телом. При ротации принимаются только настроенные ключи: текущий и прежний до previous_key_valid_until (10 минут). Не получайте ключ по адресу, присланному в запросе.
Ответ 2xx означает, что событие и задача обработки уже надёжно сохранены в одной транзакции. Он не означает исполнение заказа. При невозможности сохранить верните ошибку: UCPS повторит запрос. Повторы через 30 секунд, 2 минуты, 10 минут, 30 минут, затем час; предел 72 часа или 80 попыток цикла. 3xx не перенаправляется. После исчерпания событие остаётся для разбора, ручной повтор сохраняет event_id.
Рабочий обработчик магазина отдельно читает актуальный счёт через авторизованный GET и применяет те же проверки заказа/проекта/окружения/сети/контракта/суммы, что и обработчик журнала событий. Inbox исключает повтор event_id; уникальный результат по заказу исключает двойное зачисление разных оплаченных попыток. Ключом клиента служит его сохранённый серверный ID, а не номер ПК, URL или значение из вебхука.
Исполняемый пример: examples/merchant-webhook/receiver.ts и http.ts, инструкция — examples/merchant-webhook/README.md в репозитории. Он показывает отдельную SQL-базу магазина с атомарными ledger/балансом и проверен на вымышленных данных. Для внешнего API GIZMO понадобится отдельная идемпотентная операция зачисления: SQL-транзакция магазина не откатывает действия другого сервера.
Счёт из рублей
С версии 0.5.0 магазин может передать amount_rub вместо amount. Эти поля взаимоисключающие. RUB передаётся строкой от 0.01 до 999999999.99, не более двух знаков после точки; USDT — по прежним правилам.
{
"external_order_id": "club-deposit-123",
"attempt_no": 1,
"amount_rub": "150.00",
"payment_method_id": "usdt-ton-testnet"
}UCPS получает официальный USD/RUB из https://www.cbr.ru/scripts/XML_daily.asp, запрашивая дату по Москве. Документация источника: https://www.cbr.ru/development/sxml/. Для расчёта принято 1 USDT ≈ 1 USD: это правило сервиса, а не рыночная котировка USDT от ЦБ. Расчёт: RUB / рублей за USD, округление вверх до 6 знаков. В TRON_EXACT_AMOUNT затем добавляется зарезервированная добавка. Нужная к переводу сумма всегда due; рублёвый депозит остаётся 150.00 ₽.
Поле fiat в счёте, checkout и событии содержит currency: RUB, amount: 150.00, minor_amount: 15000 строками, rate_source: CBR, дату действия rate_date, запрошенную дату rate_requested_for, время получения rate_fetched_at в Unix ms, rub_per_usd, usdt_per_usd: 1, rounding: UP_6_DECIMALS, usdt_amount до добавки и rate_response_sha256. Реквизиты курса фиксируются вместе со счётом и независимым журналом. В обычных USDT-счетах поля fiat нет.
Курс фиксирован до конца счёта (сейчас 30 минут). Успешный повтор с тем же Idempotency-Key возвращает прежний счёт даже при недоступности ЦБ. Новая попытка получает курс заново через актуальный кэш. Кэш действует не более часа и сбрасывается при смене московской даты. Дата действия курса может быть предыдущим рабочим днём: допустимо не более 14 календарных дней, чтобы учитывать праздники. Будущие и более старые даты отвергаются. При недоступности источника без актуального кэша новый RUB-счёт не создаётся; уже открытые счета продолжают проверяться. Автоматической подмены источника или выдачи курса из просроченного кэша нет.
AMOUNT_CURRENCY_REQUIRED и INVALID_RUB_AMOUNT — ошибка запроса (422). FX_UNAVAILABLE, FX_INVALID_RESPONSE, FX_STALE_RATE — временная невозможность создать новый счёт (503). Повтор после восстановления выполняется с тем же сохранённым ключом и телом.
Магазин сверяет fiat.amount и fiat.currency со своим заказом, а base_due (если есть) либо due — с рассчитанной криптосуммой. После подтверждения начисляет исходный рублёвый депозит один раз. Курс на момент подтверждения не используется для перерасчёта.
Возврат к счёту в оболочке GIZMO — контракт будущего подключения
Интеграция оболочки ещё не реализована. Планируемый поток: клиент входит → нажимает «Пополнить» → указывает рубли и выбирает обычный метод или USDT. Сервер клуба создаёт локальный заказ, привязанный к внутреннему ID клиента, и передаёт UCPS external_order_id и amount_rub. Сам UCPS не управляет временем ПК и не начисляет депозит.
Сервер клуба хранит связь: project_id + club_id + gizmo_customer_id → local_order_id → все invoice_id и attempt_no. Для заказа также сохраняются сумма RUB, ключи идемпотентности, исходная checkout-ссылка и результат зачисления. external_order_id должен быть уникальным ID заказа, а не просто ID клиента: у одного клиента бывает несколько пополнений. API-ключ UCPS остаётся на сервере клуба.
При каждом открытии «Пополнить» оболочка запрашивает незавершённое пополнение текущего авторизованного клиента у сервера клуба. Сервер проверяет принадлежность заказа по сессии входа; введённый клиентом ID сам по себе не даёт доступа к чужому счёту. Хранения только в памяти оболочки или localStorage недостаточно.
| Состояние при повторном входе | Поведение кнопки пополнения |
|---|---|
| Счёт действует | Показать сумму и оставшееся время, кнопку «Продолжить оплату» с прежней ссылкой; не создавать дубль |
| Перевод проверяется | Показать проверку и не предлагать повторную отправку |
| Счёт оплачен | Показать результат серверного зачисления; не выставлять оплату снова |
| Счёт истёк или отменён | Сохранить историю и предложить новую попытку по явному действию клиента; курс и реквизиты могут измениться |
| Старый checkout-токен истёк | Читать статус через сервер клуба; история не зависит от срока ссылки |
При истечении времени на ПК закрытие оболочки не отменяет криптосчёт. Его время продолжает идти на сервере. Обработчик событий клуба работает независимо от оболочки, поэтому может зачислить депозит, пока клиент вышел. После нового входа клиент увидит серверный результат. Автоматическое продление игровой сессии за счёт депозита — отдельное правило клуба.
Если checkout-токен истёк, для ещё доступного счёта сервер может создать новую checkout-сессию с новым ключом операции. Сессии доступны не дольше часа после invoice.expires_at; более старая история отображается из серверного GET счёта. Исходную ссылку нельзя публиковать или отдавать другому клиенту общего ПК; при выходе очищайте состояние оболочки.
Старый своевременный перевод может подтвердиться уже после новой попытки. Проверяйте все попытки и начисляйте один раз на local_order_id, а не один раз на событие или счёт. Результат зачисления и обработанный event_id сохраняются надёжно; вызов API депозита должен иметь ключ операции или журнал сверки при тайм-ауте. Если оплачены две попытки, вторую передавайте в разбор — не начисляйте молча второй депозит. Нельзя считать переход клиента на страницу успеха доказательством оплаты.
Диагностика запроса
HTTP-ответ содержит серверный X-Request-ID. Сообщите его владельцу сервиса при ошибке: по нему можно найти запрос в ограниченном диагностическом журнале. Не отправляйте API-ключ или полный checkout URL в сообщения об ошибке. GET /v1/admin/diagnostics доступен только владельцу, не магазину.
Ошибки
Формат { "error": "CODE" }. 401 — неверный доступ; 404 — счёт не найден в вашем проекте; 409 — конфликт операции/состояния; 422 — неверные параметры; 503 — функция или реальная сеть ещё не включена. Сетевые ошибки и 5xx повторять с задержкой и прежним ключом. 409/422 не повторять вслепую. OpenAPI описывает фактически опубликованные маршруты.
Кошельки проекта
При подключении проекта владелец задаёт wallets: ID кошелька, сеть и публичный адрес. Один адрес может использоваться в нескольких проектах. Способ оплаты routes ссылается на кошелёк через wallet_id; менять настройки может только владелец. При смене адреса новые счета используют новый кошелёк, прежние сохраняют реквизиты. Mainnet-адреса можно заранее сохранить, но mainnet-способы оплаты пока выключены в конфигурации рабочего сервера.
API владельца: GET/POST /v1/admin/projects, PUT /v1/admin/projects/{id}, POST /v1/admin/projects/{id}/rotate-key, POST /v1/admin/projects/{id}/test-invoice. Они требуют сессию владельца с MFA и CSRF для изменений; ключ магазина не подходит. Схемы запросов описаны в OpenAPI.
В версии 0.2.1 владелец также видит очередь проверки и состояние RPC в кабинете. GET /v1/admin/observer возвращает состояние без секретных URL; PUT по тому же пути сохраняет общие интервалы (активный 30–300 секунд, простой от активного до 3600 секунд) с Idempotency-Key. POST /v1/admin/observer/diagnostics запускает чтение genesis/solidified-блоков, возвращает 202; результат доступен через GET. Диагностика не подтверждает платёж и не заменяет наблюдение транзакции. API магазина не имеет доступа к этим операциям.
Публичный checkout читает GET /v1/checkout с Bearer checkout_token и создаёт TRON-инструкцию POST /v1/checkout/tron-instruction с телом { "sender": "<адрес>" }. Для подготовки инструкции отдельный Idempotency-Key не требуется: сервер выдаёт ранее связанную инструкцию для того же счёта/отправителя, пока она действует. Транзакционные доказательства и произвольный txID от браузера не принимаются.
Установлен процесс сверки ручных возвратов, описанный в инструкции возвратов. Событие refund.confirmed не отменяет исторический PAID. Наличие invoice.refund требует отдельного решения магазина; пример получателя не выдаёт депозит автоматически и не списывает ранее выданный депозит по такому событию. Сам перевод подписывает владелец; живая проверка исходящего возврата ещё не пройдена.
Своевременный платёж после восстановления
Начиная с 0.15.7, событие invoice.paid для своевременного TON-платежа, найденного после срока счёта, содержит reason: RECOVERED_ON_TIME_PAYMENT. Время поступления проверяется по блокчейну, а occurred_at показывает время события сервиса. Это тот же платёж, не отдельная повторная выдача. Сохраняйте прежние события invoice.expired; перед исполнением проверьте состояние заказа в магазине. Если заказ уже закрыт или его автоматическое исполнение недопустимо, передайте его в ручной разбор. Подтверждённое поступление в UCPS при этом сохраняется.
Переключение сетей и уведомлений
MAINNET_NETWORKS на сервере — JSON-массив из tron-mainnet и/или ton-mainnet, по умолчанию []. Включение требует настроенной MFA владельца; для TRON нужны два независимых RPC основной сети. Наблюдатели прежних testnet-счетов продолжают работать. Смена настройки не меняет реквизиты, суммы, курс и среду существующего счёта. При выключенной сети новые счета и инструкции запрещены; checkout сохраняет просмотр состояния, но не предлагает перевод. Невыясненные переводы сверяются после восстановления наблюдателя.
POST /v1/admin/projects/{id}/webhooks принимает url и необязательный environment (test по умолчанию или live). Секреты и подписки этих сред раздельны. Endpoint начинает с текущего курсора; для более раннего события используйте документированную сверку или явный replay. Пример MerchantReceiver принимает только среду из своей конфигурации и соответствующие сети.
Название старого административного endpoint /test-invoice сохранено для совместимости: фактическая среда определяется выбранным методом. В mainnet такой счёт требует настоящего USDT. Сначала проведите отдельную приёмку; конфигурация и успешный health сами по себе не подтверждают реальный платёж.
Учёт отдельного поступления на оплаченный счёт (с версии 0.16.0)
После учёта дополнительного перевода сервис публикует payment.excess_recorded. Оно содержит обновлённый invoice с прежними due/status PAID, увеличенными received/excess и version. Сохраняйте событие для разбора или согласования возврата; не выдавайте по нему товар и не пополняйте депозит. Пример merchant-webhook проверяет подпись и направляет событие в REVIEW/NO_DEPOSIT, независимо от порядка доставки относительно invoice.paid. Установленным прежним получателям нужно добавить тип в разрешённые события; неизвестные события нельзя терять или трактовать как оплату.
Ручной разбор платежа для магазина
Если перевод не удалось автоматически привязать к счёту, поддержка магазина может создать PaymentClaim. Это заявление о проверенном заказе и отправителе, а не подтверждение поступления. Само создание запроса не меняет баланс и не разрешает выдачу услуги.
Подготовка
- Аутентифицируйте клиента и проверьте принадлежность заказа.
- Проверьте материалы отправления. Для биржевого вывода одного публичного хеша или скриншота недостаточно для доказательства принадлежности платежа.
- Прочитайте актуальный счёт через
GET /v1/invoices/{invoice_id}. Используйте егоversionи сохранённый ID заказа. - В
evidence_refукажите номер защищённого обращения. Не передавайте seed-фразы, ключи или персональные документы.
Создание запроса
Запрос выполняется сервером магазина с API-ключом проекта. Checkout-токен не подходит.
POST /v1/invoices/<invoice_id>/payment-claims
Authorization: Bearer <PROJECT_API_KEY>
Idempotency-Key: support-claim-1042
Content-Type: application/json
{
"invoice_version": 1,
"external_order_id": "order-1042",
"txid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"log_index": 0,
"sender_verification": "PROJECT_SUPPORT",
"evidence_ref": "support-case-1042-sender-verified",
"accept_late_or_cancelled": false
}Ответ 201 содержит идентификатор запроса, принадлежность проекту и счёту, реквизиты заявления, state: OPEN и время created_at в Unix milliseconds. Сеть и ожидаемый токен берутся из сохранённого счёта. Для TRON нужен lowercase hex TxID и индекс Transfer-лога полного receipt от 0 до 4095; для TON — hex/base64 hash транзакции поступления в jetton-wallet и log_index: 0.
Для истёкшего или отменённого счёта требуется явное accept_late_or_cancelled: true. Оно означает согласие магазина на разбор этой версии счёта и само по себе не возобновляет заказ. Для PAID, чужого заказа, устаревшей версии или счёта с историей спора запрос не принимается.
Чтение, отзыв и решение
| Метод | Результат |
|---|---|
| GET /v1/invoices/{invoice_id}/payment-claims/{claim_id} | Актуальный запрос своего проекта; чужой запрос — 404 |
| POST /v1/invoices/{invoice_id}/payment-claims/{claim_id}/revoke | Отзыв OPEN-запроса с телом {"reason":"Поддержка отозвала запрос"} и новым Idempotency-Key |
Причина отзыва — от 5 до 2000 символов. CONSUMED-запрос отозвать нельзя. Отзыв не возвращает деньги. Повтор с прежним ключом возвращает первоначальный ответ операции; актуальное состояние всегда читайте через GET.
Владелец сервиса рассматривает материалы и отдельно подтверждает привязку с MFA после проверки блокчейна. Только фактически сохранённое зачисление отражается в счёте и событиях. Магазин продолжает стандартную обработку событий и авторизованный GET; одного состояния PaymentClaim недостаточно. Если заказ уже закрыт или услуги выданы по другой попытке, направьте результат в разбор своего магазина, исключив повторную выдачу.
Вернуться к основной инструкции. Процесс возврата описан в инструкции возвратов.
Оплата через TON Connect
Магазин создаёт счёт и передаёт клиенту готовый checkout_url. На странице уже закреплены сеть, токен, точная сумма, получатель и комментарий. Передавать клиенту API-ключ магазина или самостоятельно собирать транзакцию не требуется.
Действия клиента
- Открыть счёт и выбрать в кошельке указанную сеть.
- Нажать «Подключить TON-кошелёк». Подключение не отправляет платёж.
- Нажать «Подготовить перевод». Сервер проверяет текущую версию счёта и вычисляет jetton-wallet отправителя через два RPC.
- Проверить сумму и нажать «Подтвердить в кошельке». Подписать перевод в программе кошелька.
- Дождаться автоматического подтверждения поступления на странице. При неопределённом ответе кошелька проверить историю и не отправлять повторно.
В testnet используется TUSDT и тестовый нативный токен; в mainnet — оригинальный USDT и TON. Комиссия кошелька оплачивается отдельно. Показанный резерв выполнения — прикреплённая к транзакции сумма нативного токена, а не фиксированная комиссия; неиспользованный остаток возвращается отправителю.
Комментарий TON добавляется в подготовленную транзакцию автоматически. При ручном переводе комментарий и точную сумму нужно перенести из счёта. Если кошелёк или биржа не поддерживают необходимый комментарий, этот способ автоматической оплаты не подходит.
Возврат к странице и статус
Ссылка содержит краткоживущий токен счёта во фрагменте #; сохраняйте и передавайте её целиком. Перезагрузка и возврат из кошелька открывают тот же счёт. На общем ПК приложение магазина должно закрывать страницу и очищать состояние предыдущего клиента при выходе.
Подключение, подготовка, успешный ответ SDK и переход клиента на страницу не являются подтверждением оплаты. После отправки сервис самостоятельно проверяет поступление, адреса jetton-wallet, исходный токен, сумму, комментарий и финальность по двум источникам. Без поступления счёт остаётся неоплаченным и может истечь. После подтверждения страница обновляет статус автоматически и блокирует повторную отправку.
Смена аккаунта, сети или версии счёта делает прежнюю подготовку непригодной. Явный отказ от подписи позволяет подготовить перевод заново; отсутствие достоверного ответа кошелька требует ожидания проверки. Если платёж не сопоставлен, используйте ручной разбор.
Магазин выдаёт услугу только по серверной обработке проверенного счёта и событий, описанной в основной инструкции. Подтверждение SDK не передаётся магазину как разрешение на выдачу.
Возвраты и действия магазина
Возврат выполняется владельцем кошелька вручную, а UCPS проверяет его по блокчейну. Сервер не хранит приватные ключи и не подписывает отправку. API-ключ магазина не даёт прав одобрять или подтверждать возврат в кабинете владельца.
Порядок возврата
- Согласовать адрес и сумму с клиентом. Адрес отправителя исходного платежа может принадлежать бирже и не является автоматически безопасным адресом возврата.
- Подготовить запрос в кабинете с привязкой к счёту и материалам проверки. Владелец отдельно одобряет запрос с MFA.
- После одобрения владелец отправляет средства из указанного кошелька в согласованной сети и тем же токеном.
- В кабинете указывается TxID и индекс Transfer-лога. Для TON нужен hash транзакции поступления в jetton-wallet получателя, индекс 0.
- UCPS проверяет два RPC и сохраняет подтверждённую сумму. Само одобрение запроса не означает отправку или завершение возврата.
Подтверждённая сумма не может превышать одобренную; несколько одобренных запросов не резервируют больше подтверждённых поступлений по счёту. История запроса и исходные платежи сохраняются.
Событие refund.confirmed
Каждый подтверждённый фрагмент создаёт событие refund.confirmed. Повторная проверка того же перевода не создаёт новое подтверждение. Событие доставляется через обычный журнал событий и настроенные вебхуки; применяются те же проверки подписи, проекта, среды и идемпотентности.
invoice.refund.confirmed — общая подтверждённая сумма возвратов в атомарных целых строках. Версия счёта увеличивается. Исторические received, excess и PAID не уменьшаются: PAID сохраняет факт исходного поступления, а возврат учитывается отдельно.
Магазин читает актуальный счёт через свой API-ключ и передаёт возврат в собственный процесс разбора. Нельзя повторно выдавать товар по историческому PAID или автоматически списывать ранее выданный депозит только из-за события возврата. Изменение заказа, подписки или депозита — отдельная идемпотентная операция магазина. Пример получателя UCPS направляет такие случаи в REVIEW.
Текущая рабочая среда — тестовые сети. Включение mainnet и проверка настоящего исходящего возврата требуют отдельной приёмки. Схемы административных методов опубликованы в OpenAPI; правила выдачи магазина — в основной инструкции.