Подготовьте проект
- Создайте аккаунт и подтвердите emailСоздать аккаунт
- Включите 2FA и добавьте проектУкажите HTTPS-адрес, на который будут приходить уведомления.
- Подтвердите доменДобавьте TXT-запись, указанную в кабинете, и дождитесь одобрения проекта.
- Получите API-ключ и секрет подписи уведомленийХраните их на сервере своего приложения.
Ключ и заголовки
API-ключ передаётся в заголовке Authorization: Bearer …. Запросы, которые создают счёт или меняют адрес, передают ключ идемпотентности Idempotency-Key.
Authorization: Bearer <ключ проекта> Content-Type: application/json Idempotency-Key: <ключ операции>
Ключ проекта не следует помещать в JavaScript сайта, Mini App или клиентское приложение. Запросы к API отправляет только ваш сервер.
Создайте ссылку на оплату
Передайте сумму в рублях, а валюту и сеть клиент выберет на странице AstraPay. При создании ссылки кошелёк и валютный счёт ещё не выдаются.
https://astrapay.cc/api/v4/checkouts
external_user_idстрока- Постоянный ID клиента внутри вашего проекта.
rub_amountстрока- Сумма пополнения в рублях, например
"1200.00".
POST https://astrapay.cc/api/v4/checkouts Authorization: Bearer <ключ проекта> Idempotency-Key: order-123-checkout Content-Type: application/json {"external_user_id":"customer-42","rub_amount":"1200.00"}
Как работает ссылка
- Не удаляйте фрагмент
#token=…и не записывайте ссылку в публичные логи. - Повторный запрос с тем же ключом и данными возвращает ту же ссылку.
- Двойное нажатие при выборе сети возвращает уже выбранные реквизиты.
- За один платёж выдаётся один валютный счёт. Существующий протокол подписанных зачислений v3 сохраняется.
Заранее выбранная сеть: совместимый API v3
Если сеть заранее определена вашим собственным интерфейсом, прежний API остаётся доступен. Новый платёж создавайте с новым ключом.
https://astrapay.cc/api/v3/customer/quotes
POST https://astrapay.cc/api/v3/customer/quotes Authorization: Bearer <API_KEY> Content-Type: application/json Idempotency-Key: topup-customer1042-0001 { "external_user_id": "1042", "asset_key": "ton_usdt", "rub_amount": "1200.00" }
checkout_url- Ссылка для перехода клиента к оплате.
expected_amount- Точная сумма перевода.
deposit_address- Адрес для перевода.
network- Сеть перевода.
expires_at- Срок действия условий.
Курс действует 30 минут, комиссия 3%. Суммы в минимальных единицах приходят строками, чтобы избежать потери точности.
Идентификаторы активов
ethereum_usdtUSDT, Ethereum ERC20ethereum_ethETH, Ethereumton_usdtUSDT, TONton_tonToncoin, TONlitecoin_ltcLTC, LitecoinСписок идентификаторов не означает, что все сети включены. Проверяйте текущую доступность и обрабатывайте отказ создания счёта при остановке сети.
/api/v3/customer/methodsТекущая доступность способов оплаты
/api/v3/customer/quotes/{quote_id}Исходный счёт, с API-ключом своего проекта
Адрес клиента в каждой сети
Монета сети и токен в ней используют один адрес клиента. Адреса разных проектов изолированы.
- ETH
- USDT ERC20
- Toncoin
- USDT TON
Перед каждой оплатой создавайте счёт: именно он фиксирует курс и ожидаемую сумму.
Для клиента допускаются несколько активных счетов на конкретный актив и сеть, если точные суммы переводов различимы. При создании нового счёта всегда используйте выданную сумму: она может отличаться в последних минимальных единицах при той же сумме пополнения в рублях.
Когда платёж направляется на проверку
Если поступление нельзя однозначно связать со счётом, платёж направляется на проверку. Например, когда перевод:
- пришёл поздно;
- пришёл частично;
- совпал с суммой другого неоплаченного счёта.
Зачисление относится к балансу клиента. Распределение баланса по заказам выполняет ваш сервис.
Смена адреса
Смену следует выполнять по явному запросу авторизованного клиента. При незавершённой оплате или спорном поступлении операция отклоняется.
/api/v3/customer/wallets/rotate
POST /api/v3/customer/wallets/rotate Authorization: Bearer <API_KEY> Idempotency-Key: rotate-customer1042-0001 Content-Type: application/json { "external_user_id": "1042", "network": "ton", "previous_wallet_id": "<текущий wallet_id>", "confirm_retire_previous": true }
Ответ содержит новый wallet_id и поколение адреса, а сам адрес появится в следующем зафиксированном счёте. Старый адрес остаётся связан с тем же клиентом и продолжает отслеживаться.
Подтвердите зачисление
О зачислении ваш сервер узнаёт из подписанного события и отвечает подписанной квитанцией.
customer_balance.creditedпротокол 3- credit_id
- quote_id
- customer_id
- merchant_id
- wallet_id
- rub_kopecks
- payload_hash
- параметры перевода и сети
Проверка подписи
Проверьте подпись по исходным байтам HTTP-тела. Время в X-AstraPay-Timestamp должно отличаться от текущего не более чем на 300 секунд, X-AstraPay-Delivery-Id содержит UUID. Подпись: HMAC-SHA256 от трёх строк.
timestamp + "\n" + delivery_id + "\n" + sha256(raw_body).hexdigest()
X-AstraPay-Signature: <HMAC-SHA256, hex> X-AstraPay-Timestamp: <Unix timestamp> X-AstraPay-Delivery-Id: <UUID>
import hashlib, hmac, time, uuid def _sign(secret: str, ts: str, delivery_id: str, body: bytes) -> str: message = ts + "\n" + delivery_id + "\n" + hashlib.sha256(body).hexdigest() return hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() def verify(raw_body: bytes, headers, secret: str) -> bool: ts = headers["X-AstraPay-Timestamp"] delivery_id = headers["X-AstraPay-Delivery-Id"] if abs(time.time() - int(ts)) > 300: return False uuid.UUID(delivery_id) # ValueError, если это не UUID expected = _sign(secret, ts, delivery_id, raw_body) return hmac.compare_digest(expected, headers["X-AstraPay-Signature"]) def receipt_headers(body: bytes, delivery_id: str, secret: str) -> dict: ts = str(int(time.time())) return { "X-AstraPay-Signature": _sign(secret, ts, delivery_id, body), "X-AstraPay-Timestamp": ts, "X-AstraPay-Delivery-Id": delivery_id, }
Обработка и квитанция
- Одна транзакция в вашей базеПроверьте уникальность
credit_id, привязку клиента и проекта, добавьтеrub_kopecksна баланс и сохраните квитанцию. - Повтор не меняет балансПри повторе верните ту же квитанцию. При несовпадении тела для уже известного
credit_idостановите обработку для проверки. - Ответ: HTTP 200 и подписанная квитанцияПодпишите байты тела квитанции тем же секретом и теми же заголовками. Delivery-Id должен совпасть с текущим входящим запросом.
{
"protocol_version": 3,
"status": "credited",
"receipt_id": "<постоянный ID вашей квитанции>",
"credit_id": "<из события>",
"quote_id": "<из события>",
"customer_id": "<из события>",
"merchant_id": "<из события>",
"wallet_id": "<из события>",
"rub_kopecks": "<точная строка из события>",
"payload_hash": "<из события>"
}Одного HTTP 200 без корректной подписанной квитанции недостаточно. После потери ответа шлюз повторяет доставку с тем же credit_id.
В production принимайте только ожидаемые environment=mainnet, test_mode=false, сеть и контракт. События старых протоколов обрабатываются отдельно: не преобразуйте исторические счета в новый формат.
Состояния и ошибки
- Ответ
- Действие интеграции
- 401 / 403
- Проверьте ключ, полномочия проекта или сессию. Не создавайте новый платёж автоматически.
- 409
- Проверьте активный счёт, незавершённый перевод или повтор ключа с другими данными.
- 409customer_quote_amount_ambiguous
- Не удалось безопасно различить новый и прежний расчёт. Проверьте состояние исходной ссылки и не отправляйте повторную оплату. Новый выбор сети или новая попытка допустимы только после подтверждения шлюзом, что прежние реквизиты не выдавались.
- 422
- Исправьте формат и сумму запроса.
- 429
- Сделайте паузу перед повтором.
- 503
- Сеть или проверка временно недоступна. Повторяйте исходный запрос с прежним
Idempotency-Key.
Смена адреса доступна только без действующего счёта и нерешённых поступлений.
Состояния, которые не подтверждают зачисление
manual_reviewexpireddetectedНе исполняйте заказ только по скриншоту перевода, хешу транзакции или факту открытия страницы оплаты. Основание для зачисления: подписанное событие customer_balance.credited.