Перейти к содержанию
STONS

Адреса для приёма платежей

Этот документ описывает адреса для приёма платежей: два режима адресов, как POST /v1/addresses выдаёт постоянный адрес пользователю мерчанта в режиме per_user и что происходит с каждым видом перевода, пришедшего на адрес. Прочитайте его до выбора режима адресов и до того, как показывать адрес покупателю.

Режимы

Режим выбирает владелец при создании проекта (мерчанта) в кабинете: там предлагается per_invoice. per_user — ограниченный режим, его разрешает оператор по проектам; после разрешения владелец переключается на него в кабинете (ADR-0066). Смена решает, где будет собран следующий платёж: уже выданные адреса остаются при своём режиме, зачисляются и переносятся как раньше.

Режим Адрес Когда подходит
per_invoice Новый адрес под каждый инвойс, выдаётся при создании инвойса; у инвойса без токена — когда покупатель выбрал токен. Адрес однозначно определяет инвойс Оплата конкретного заказа
per_user Один постоянный адрес на каждого пользователя мерчанта (external_user_id), выдаётся первым POST /v1/addresses для этого пользователя или когда адрес получает его первый инвойс Пополнение баланса и повторные платежи на один адрес
  • В режиме per_invoice адрес приходит в ответе на POST /v1/invoices, а POST /v1/addresses отклоняется с 409 address_mode_mismatch.
  • В режиме per_user адрес пользователя и адрес каждого его инвойса — один и тот же адрес. Какой инвойс оплачивает перевод, решает сумма — см. режим per_user.
  • Платёж никогда не сопоставляется с заказом по «уникальной сумме» с копейками: решает адрес.

Режим на паузе

Платформа может на время приостановить режим адресов. Режим на паузе останавливает только новое в нём, и каждый такой запрос получает 422 address_mode_disabled:

  • новый проект в этом режиме;
  • у проекта в этом режиме: POST /v1/addresses — и для нового постоянного адреса, и для адреса, выданного раньше; POST /v1/invoices — с asset и без него; выбор токена на странице оплаты, POST /checkout/v1/invoices/{id}/select. Пока режим на паузе, страница не предлагает такому инвойсу ни одного токена.

Повтор POST /v1/invoices с order_id инвойса, созданного до паузы, возвращает этот инвойс. Всё, что касается денег в пути, идёт как обычно: перевод на выданный раньше адрес, в том числе постоянный, записывается, зачисляется и приходит вебхуками; инвойсы, у которых уже есть адрес, оплачиваются и закрываются; GET /v1/invoices/{id} и GET /v1/deposits/{tx_hash} отвечают. Сообщите покупателю, что оплата сейчас недоступна, и повторите позже: ответ не изменится, пока платформа не включит режим обратно.

Постоянный адрес пользователя

POST /v1/addresses возвращает постоянный адрес пользователя мерчанта в режиме per_user и создаёт его при первом запросе.

Запрос

Тело — JSON-объект с Content-Type: application/json, не больше 16 КиБ. Поле, которого нет в таблице, отклоняется с 400 validation_failed.

Поле Тип Обязательно Описание
external_user_id string да Идентификатор пользователя на стороне мерчанта: 1–200 видимых ASCII-символов (0x21–0x7E) без пробелов
network string да Сеть адреса: идентификатор включённой сети из реестра сервиса, в которой сервис выдаёт адреса. Сети предлагаемых активов перечисляет GET /v1/assets — см. список активов. Сейчас это только tron

Ответ

200 — и для нового адреса, и для существующего.

Поле Тип Описание
address string Адрес целиком: в TRON — base58, в EVM-сетях — 0x и 40 шестнадцатеричных цифр в нижнем регистре, в TON — user-friendly форма: 48 символов base64url, начинается с UQ (0Q в тестовой сети). EVM-адреса сравнивайте без учёта регистра, а людям показывайте с контрольной суммой EIP-55. Адрес TON показывайте людям ровно таким, каким он пришёл, — см. адреса в TON
network string Сеть из запроса
assets array of string Активы, которые мерчант может предлагать пользователю для оплаты на этот адрес в момент запроса, отсортированные по идентификатору. Актив попадает в список, если сервис принимает его к оплате, оператор не выключил в нём приём и он не выключен в настройках проекта. В боевой сети в списке для tron сейчас может быть только USDT_TRON: приём в USDC_TRON выключен; для ton — USDT_TON, а GRAM_TON — после того как платформа включила приём в нём — см. активы
  • Повторный запрос с тем же external_user_id возвращает тот же адрес. Одновременные запросы для одного пользователя тоже получают один адрес.
  • assets показывает, что предлагать, а не что зачисляется: перевод в активе, выпавшем из списка, всё равно зачисляется — см. что приходит на адрес.

Ошибки

HTTP error.code Когда
400 validation_failed Поле отсутствует, лишнее, неверного типа или формата; network — не сеть, в которой сервис выдаёт адреса
400, 413, 415 bad_request Тело — не JSON или пустое (400), больше 16 КиБ (413) или с неподдерживаемым Content-Type (415)
401 unauthorized Нет ключа API или он неверный — см. аутентификацию
403 merchant_disabled Мерчант отключён
403 merchant_not_approved Мерчант ещё не одобрен после проверки или отклонён: постоянный адрес — это способ оплаты, и до одобрения он не выдаётся. Проверяется после режима
409 address_mode_mismatch Мерчант работает в режиме per_invoice
422 address_mode_disabled Платформа приостановила режим per_user — см. режим на паузе. Проверяется после одобрения
429 rate_limited Превышен лимит запросов
500 internal_error Необработанная ошибка сервиса
503 addresses_exhausted Закончились индексы деривации адресов: заняты все 2^31. Практически недостижимо
503 service_unavailable Сервис не может вывести новый адрес в текущей конфигурации; сообщение называет причину

Что делать клиенту при каждом коде — см. коды ошибок.

Пример

curl -s -X POST https://api.stons.io/v1/addresses \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "user_42", "network": "tron"}'

200:

{"address":"TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH","network":"tron","assets":["USDT_TRON"]}

Адрес в примере условный: у каждой установки свои адреса, выведенные из её собственного ключа.

Откуда берутся адреса

  • Каждый адрес выводится из мастер-ключа сервиса по стандарту BIP44: по пути m/44'/195'/0'/0/{index} в TRON и m/44'/60'/0'/0/{index} в EVM-сетях. В TON адрес — контракт кошелька Wallet V5R1 одного публичного ключа пути m/44'/607'/0', а индекс записан в идентификатор кошелька; адрес зависит ещё и от сети — mainnet или testnet. В Solana адрес — это сам публичный ключ пути m/44'/501'/0'/{index}', в котором каждый шаг hardened: SLIP-0010 выводит ключи ed25519 только по hardened-путям.
  • Адрес принадлежит семейству сетей, а не одной сети: один EVM-адрес принимает во всех EVM-сетях. Поэтому у пользователя per_user один адрес на все EVM-сети и отдельный в TRON, а повторный POST /v1/addresses для другой EVM-сети возвращает тот же адрес.
  • Индексы выдаются строго по порядку внутри семейства и никогда не используются повторно, даже если адрес так и не получил платежа. Два пользователя или два инвойса никогда не получают один адрес.
  • Сервер API знает только публичную часть мастер-ключа: он умеет выводить адреса, но не может потратить средства с них. В Solana он знает ещё меньше: расширенного публичного ключа для такого ключа не существует, поэтому сервер API только выдаёт публичные ключи, которые сервер подписи вывел и опубликовал ему заранее, и сам вычислить ни один адрес не может.
  • Адреса выдаются в сетях, которые включил сервис, — см. сети и токены. Показывайте покупателю сеть вместе с адресом.

Адреса в TON

У аккаунта TON две записи: raw — 0: и 64 шестнадцатеричные цифры — и user-friendly — 48 символов base64url с контрольной суммой. API везде отдаёт в address user-friendly форму — в POST /v1/addresses, в инвойсах, в GET /v1/deposits/{tx_hash} и в вебхуках deposit.*: её принимают и проверяют кошельки.

  • Адрес без флага bounce: начинается с UQ, в тестовой сети — с 0Q. Адрес приёма не развёрнут, пока сервис не перенесёт с него деньги, и на такой адрес кошелёк отправляет монеты так, чтобы они остались; сообщение с флагом bounce на неразвёрнутый адрес возвращает их отправителю, и инвойс не оплачивается.
  • Показывайте адрес покупателю и кладите в QR-код ровно в том виде, в каком его вернул API: в другую форму не переводите.
  • Платежу в TON не нужен комментарий (memo): чей это платёж, говорит сам адрес. Комментарий, если покупатель его добавит, ничего не меняет.
  • Токен в TON лежит на отдельном жетон-кошельке владельца. Покупатель отправляет токен на адрес приёма как обычно, а сервис находит его на жетон-кошельке этого адреса.

Адреса в Solana

Адрес Solana — публичный ключ, записанный в base58: от 32 до 44 знаков, и регистрозависимо. В base58 строчные и заглавные буквы латиницы — разные символы, поэтому адрес, отличающийся только регистром, — это другой адрес, ключа к которому ни у кого нет.

  • Никогда не приводите адрес этой сети к нижнему или верхнему регистру и не нормализуйте его иначе: ни в своей базе, ни в URL, ни перед сравнением, ни в QR-коде. Храните и сравнивайте его побайтово таким, каким его вернул API, и показывайте целиком.
  • Подпись транзакции Solana — тоже base58: 86–88 знаков, а не 64 шестнадцатеричных знака. Сервис отдаёт её в tx_hash — в GET /v1/deposits/{tx_hash} и в вебхуках deposit.*, — и в колонку, рассчитанную на шестнадцатеричный хэш, она не поместится. Подписи сравнивайте с учётом регистра.
  • Токен в Solana лежит не на адресе владельца. Он лежит в associated token account — отдельном аккаунте на пару (владелец, mint), адрес которого выводится детерминированно. Этот аккаунт сервис создаёт сам при выдаче адреса, поэтому кошелёк покупателя отправляет токен на адрес приёма как обычно и знать о нём ничего не должен.
  • Следствие, важное, если вы проверяете платёж сами: getSignaturesForAddress на адрес приёма перевод токена SPL не покажет вовсе. Подписи индексируются по аккаунтам, которые затронула транзакция, а перевод токена затрагивает associated token account, а не адрес владельца. Сам адрес показывает только переводы нативной монеты SOL, которая действительно меняет его собственный баланс. Это свойство метода, а не провайдера. Вместо чтения цепи своими руками используйте GET /v1/deposits/{tx_hash} — см. проверить перевод.
  • Адресу Solana не нужна активация, и своей аренды он не держит: комиссию перевода с него платит отдельный аккаунт сервиса, поэтому никакой монеты на адрес приёма заранее отправлять не нужно.

Что приходит на адрес

Сервис следит за каждым выданным адресом всегда, в том числе после дедлайна инвойса. Перевод на адрес обрабатывается по этим правилам, в этом порядке:

  1. Токен, которого сервис не знает (контракт, которого нет в его каталоге активов), игнорируется и не записывается. Среди таких токенов бывают контракты-ловушки: не взаимодействуйте с ними.
  2. Сумма меньше минимального депозита своего актива — пыль: записывается, мерчанту не зачисляется, вебхука нет. Исключение — остаток инвойса на адресе per_invoice: перевод в активе инвойса больше нуля — платёж, если по инвойсу уже пришёл платёж, а amount_expected ещё не набран (см. частичная оплата).
  3. Адрес per_invoice:
    • перевод в активе инвойса — платёж по этому инвойсу, до дедлайна или после — см. статусы;
    • перевод в другом активе из каталога — например USDC_TRON на адрес инвойса в USDT_TRON или TRX_TRON — записывается как unexpected_asset: не зачисляется, инвойс не меняется, вебхука нет.
  4. Адрес per_user:
    • перевод в активе, который сервис принимает к оплате (USDT_TRON, USDC_TRON, а TRX_TRON — после того как платформа включила приём в нём), оплачивает подходящий инвойс или пополняет баланс мерчанта — см. режим per_user. Он зачисляется, даже если оператор или настройки проекта выключили актив или платформа приостановила режим: выключатели останавливают новые инвойсы, адреса и проекты и убирают актив из assets, но не зачисление;
    • перевод в активе, который сервис к оплате не принимает (TRX_TRON, пока платформа не включила приём в нём), записывается как unexpected_asset: не зачисляется, вебхука нет.
  5. Нативная монета EVM-сети подчиняется этим правилам, только пока платформа принимает её к оплате, отправил её кошелёк или смарт-контракт (смарт-кошелёк, мультиподпись, вывод с биржи). Перевод из контракта находится трейсом его блока и записывается, только если трейс объясняет весь рост баланса адреса. Перевод, который объяснить не удалось, не зачисляется и не попадает в ответ GET /v1/deposits/{tx_hash}, вебхука нет: его разбирает оператор вручную. Пока платформа нативную монету EVM-сети не принимает, её переводы не записываются вовсе.
  6. GRAM в TON подчиняется этим правилам, только если пришёл обычным переводом с кошелька — сообщением без тела или с текстовым комментарием — и только пока платформа принимает его к оплате. GRAM, пришедший с сообщением другого вида, например тот, что перевод токена несёт на оплату уведомления, не зачисляется, вебхука нет.

Переводы unexpected_asset владелец проекта видит в кабинете. Сервис их не зачисляет; чтобы разобраться с таким переводом, обратитесь к оператору.

Проверить перевод

GET /v1/deposits/{tx_hash} возвращает все переводы одной транзакции на адреса мерчанта, как их записал сервис. По нему действуют, получив вебхук deposit.*, в том числе о пополнении per_user без инвойса, вместо того чтобы доверять телу вебхука, — см. главное правило. Маршрут работает в обоих режимах адресов, и когда платформа приостановила режим, тоже.

Запрос

tx_hash в пути — хэш транзакции так, как его пишет сеть, в любом регистре: в TRON и TON — 64 шестнадцатеричных символа без 0x, в EVM-сетях — 0x и 64 шестнадцатеричных символа. Берите его из data.tx_hash вебхука: сервис ищет ровно эту форму, 0x не добавляет и не отрезает. Тела у запроса нет.

В TON перевод — это цепочка транзакций, по одной на контракт. tx_hash — хэш транзакции, в которой деньги получил адрес приёма, а у токена — жетон-кошелёк этого адреса; это не хэш транзакции, который показывает кошелёк отправителя. Обозреватели TON пишут его шестнадцатеричными цифрами; если API отдаёт его в base64, его нужно перевести.

Ответ

200 с массивом deposits: переводы транзакции на адреса этого мерчанта по порядку log_index. В одной транзакции бывает несколько переводов: нужный из вебхука ищите по log_index. Переводы той же транзакции на адреса других мерчантов в список не попадают.

Поле Тип Описание
tx_hash string Хэш транзакции в нижнем регистре
log_index integer Номер перевода внутри транзакции: вместе с tx_hash определяет перевод. У перевода нативной монеты события нет, и номер отрицательный: -1 — сумма самой транзакции, а в TRON и EVM-сетях -2, -3 и далее — переводы, сделанные контрактами внутри неё. В TON у перевода токена 0, у перевода GRAM -1
asset string Актив
network string Сеть актива, например tron
amount string Сумма перевода со всеми знаками актива
address string Адрес мерчанта для приёма, целиком, в том же виде, что в POST /v1/addresses
invoice_id string (UUID) или null Инвойс, который оплачивает перевод; null у пополнения per_user и у перевода, который не является платежом
external_user_id string или null Пользователь адреса per_user; null у адреса per_invoice
kind string Чем сервис счёл перевод: payment — оплата инвойса или пополнение per_user; unexpected_asset — актив, которого адрес не ждёт; dust — меньше минимального депозита и не остаток начатой оплаты инвойса. См. что приходит на адрес
status string seen — перевод в блоке, подтверждений ещё не хватает; confirmed — набрано столько подтверждений, сколько требует актив; orphaned — перевод исчез из цепи при реорганизации или его транзакция оказалась неуспешной
confirmations integer Сколько блоков набрано над блоком перевода, как сервис видел это последний раз
block_number string Номер блока с переводом; в TON — номер (seqno) блока мастерчейна, в который вошла транзакция
block_time string Время этого блока, ISO 8601 в UTC
credited boolean true, если реестр держит сумму перевода на балансе мерчанта. Комиссия сервиса удерживается с баланса отдельной проводкой
explorer_url string или null Страница транзакции в обозревателе сети; null, если у сети нет ссылки на обозреватель
  • tx_hash, log_index, asset, amount, address, invoice_id, external_user_id и block_time называются и заполняются так же, как в данных вебхуков deposit.*. confirmations в ответе — текущая глубина, а не глубина в момент события.
  • Зачисляйте пополнение пользователю, только когда у его перевода kind = payment, status = confirmed и credited = true. Товар по заказу отдавайте по статусу инвойса из GET /v1/invoices/{id}, а не по отдельному переводу.
  • Перевода в блоке, который сервис ещё не просканировал, в ответе нет: ответ 404, а запрос позже его найдёт.
  • Мерчант, который ещё не одобрен, читать свои переводы может: этот маршрут merchant_not_approved не возвращает.

Ошибки

HTTP error.code Когда
400 validation_failed tx_hash — не хэш транзакции: ни 64 шестнадцатеричных символа, ни 0x и 64 символа
401 unauthorized Нет ключа API или он неверный — см. аутентификацию
403 merchant_disabled Мерчант отключён
404 deposit_not_found В транзакции нет перевода на адрес этого мерчанта: сервис её не знает, она шла только на адреса других мерчантов или её блок ещё не просканирован. Ответ во всех трёх случаях одинаковый
429 rate_limited Превышен лимит запросов
500 internal_error Необработанная ошибка сервиса

Пример

curl -s https://api.stons.io/v1/deposits/50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c \
  -H "X-Api-Key: $API_KEY"

200:

{
  "deposits": [
    {
      "tx_hash": "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c",
      "log_index": 0,
      "asset": "USDT_TRON",
      "network": "tron",
      "amount": "25.500000",
      "address": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
      "invoice_id": null,
      "external_user_id": "user_42",
      "kind": "payment",
      "status": "confirmed",
      "confirmations": 20,
      "block_number": "75231904",
      "block_time": "2026-09-11T20:04:38.000Z",
      "credited": true,
      "explorer_url": "https://tronscan.org/#/transaction/50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c"
    }
  ]
}

Хэш, адрес и блок в примере условные.

Показ адреса

Показывайте адрес целиком, без сокращений вида «первые и последние символы через многоточие». Злоумышленники рассылают переводы с адресов, у которых совпадают первые и последние символы, в расчёте на то, что человек сверит только их (address poisoning).