Адреса для приёма платежей
Этот документ описывает адреса для приёма платежей: два режима адресов, как 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 не нужна активация, и своей аренды он не держит: комиссию перевода с него платит отдельный аккаунт сервиса, поэтому никакой монеты на адрес приёма заранее отправлять не нужно.
Что приходит на адрес
Сервис следит за каждым выданным адресом всегда, в том числе после дедлайна инвойса. Перевод на адрес обрабатывается по этим правилам, в этом порядке:
- Токен, которого сервис не знает (контракт, которого нет в его каталоге активов), игнорируется и не записывается. Среди таких токенов бывают контракты-ловушки: не взаимодействуйте с ними.
- Сумма меньше минимального депозита своего актива — пыль: записывается, мерчанту не зачисляется, вебхука нет. Исключение — остаток инвойса на адресе
per_invoice: перевод в активе инвойса больше нуля — платёж, если по инвойсу уже пришёл платёж, аamount_expectedещё не набран (см. частичная оплата). - Адрес
per_invoice:- перевод в активе инвойса — платёж по этому инвойсу, до дедлайна или после — см. статусы;
- перевод в другом активе из каталога — например
USDC_TRONна адрес инвойса вUSDT_TRONилиTRX_TRON— записывается какunexpected_asset: не зачисляется, инвойс не меняется, вебхука нет.
- Адрес
per_user:- перевод в активе, который сервис принимает к оплате (
USDT_TRON,USDC_TRON, аTRX_TRON— после того как платформа включила приём в нём), оплачивает подходящий инвойс или пополняет баланс мерчанта — см. режим per_user. Он зачисляется, даже если оператор или настройки проекта выключили актив или платформа приостановила режим: выключатели останавливают новые инвойсы, адреса и проекты и убирают актив изassets, но не зачисление; - перевод в активе, который сервис к оплате не принимает (
TRX_TRON, пока платформа не включила приём в нём), записывается какunexpected_asset: не зачисляется, вебхука нет.
- перевод в активе, который сервис принимает к оплате (
- Нативная монета EVM-сети подчиняется этим правилам, только пока платформа принимает её к оплате, отправил её кошелёк или смарт-контракт (смарт-кошелёк, мультиподпись, вывод с биржи). Перевод из контракта находится трейсом его блока и записывается, только если трейс объясняет весь рост баланса адреса. Перевод, который объяснить не удалось, не зачисляется и не попадает в ответ
GET /v1/deposits/{tx_hash}, вебхука нет: его разбирает оператор вручную. Пока платформа нативную монету EVM-сети не принимает, её переводы не записываются вовсе. - 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).