Как подключить оплату через xRocket в бота на aiogram 3: Rocket Pay API с кодом

Приём криптовалюты в Telegram-боте через xRocket (Rocket Pay) на aiogram 3: API-ключ, счёт на оплату, проверка статуса и безопасный вебхук. Полный рабочий код с пояснениями.

7 мин чтения

xRocket (@xRocket) — криптокошелёк и биржа внутри Telegram. Его платёжный сервис Rocket Pay позволяет боту выставлять счета в TON, USDT и других монетах экосистемы: покупатель оплачивает счёт в @xRocket, а деньги приходят на баланс вашего приложения.

Ниже — бот на aiogram 3, который создаёт счёт, проверяет оплату по кнопке и получает уведомления от xRocket через вебхук.

Как устроена оплата через Rocket Pay

  1. Бот создаёт счёт запросом POST /tg-invoices и получает ссылку на оплату.
  2. Пользователь переходит по ссылке и платит в @xRocket.
  3. Бот запрашивает статус счёта GET /tg-invoices/<id> — по кнопке «Я оплатил» или после уведомления от xRocket.
  4. Счёт оплачен — бот выдаёт товар.

Шаг 1. Получите API-ключ

  1. Откройте @xRocket → Rocket Pay → Создать приложение (названия пунктов в меню бота могут немного отличаться).
  2. В настройках приложения получите API-ключ.
  3. Сохраните ключ в переменную окружения XROCKET_API_KEY. С ключом можно управлять счетами и средствами приложения — храните его как пароль.

Шаг 2. Установите aiogram

pip install -U aiogram

Шаг 3. Клиент Rocket Pay

API доступно по адресу https://pay.xrocket.exchange, ключ передаётся в заголовке Rocket-Pay-Key. Ответ всегда содержит поле success, а данные — в поле data. Файл xrocket.py:

import aiohttp

API_URL = "https://pay.xrocket.exchange"


class XRocketPay:
    # Минимальный клиент Rocket Pay API (@xRocket) — счета tg-invoices

    def __init__(self, api_key: str, api_url: str = API_URL) -> None:
        self.headers = {"Rocket-Pay-Key": api_key}
        self.api_url = api_url

    async def _request(self, method: str, path: str, body: dict | None = None) -> dict:
        async with aiohttp.ClientSession(headers=self.headers) as session:
            async with session.request(method, f"{self.api_url}{path}", json=body) as resp:
                data = await resp.json()
        if not data.get("success"):
            raise RuntimeError(f"xRocket: {data.get('message') or data.get('errors')}")
        return data["data"]

    async def create_invoice(self, amount: float, currency: str, description: str, payload: str) -> dict:
        return await self._request("POST", "/tg-invoices", {
            "amount": amount,
            "currency": currency,     # монета: USDT, TONCOIN...
            "numPayments": 1,         # счёт можно оплатить один раз
            "description": description,
            "payload": payload,       # ваша метка заказа, вернётся вместе со счётом
            "expiredIn": 3600,        # счёт действует 1 час
        })

    async def get_invoice(self, invoice_id: int) -> dict:
        return await self._request("GET", f"/tg-invoices/{invoice_id}")

Шаг 4. Бот с оплатой и кнопкой проверки

Файл bot.py:

import asyncio
import os

from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import Command
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message

from xrocket import XRocketPay

PRICE = 5
CURRENCY = "USDT"
PAID_STATUSES = {"paid", "completed"}  # счёт с numPayments=1 после оплаты получает статус completed

router = Router()
rocket = XRocketPay(os.environ["XROCKET_API_KEY"])
delivered: set[int] = set()  # счета, по которым товар уже выдан (в реальном боте — база данных)


def is_paid(invoice: dict) -> bool:
    return str(invoice.get("status", "")).lower() in PAID_STATUSES


async def deliver(bot: Bot, invoice: dict) -> bool:
    # Выдаёт товар по оплаченному счёту — строго один раз
    if not is_paid(invoice) or invoice["id"] in delivered:
        return False
    if invoice.get("currency") != CURRENCY or float(invoice.get("amount", 0)) != PRICE:
        return False
    delivered.add(invoice["id"])
    user_id = int(invoice["payload"])
    # Здесь выдайте товар: откройте доступ, отправьте файл, начислите баланс
    await bot.send_message(user_id, "Оплата получена! Доступ открыт ✅")
    return True


@router.message(Command("buy"))
async def buy(message: Message) -> None:
    invoice = await rocket.create_invoice(
        amount=PRICE, currency=CURRENCY,
        description="Доступ к курсу",
        payload=str(message.from_user.id),  # кому выдать товар после оплаты
    )
    keyboard = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text=f"💳 Оплатить {PRICE} {CURRENCY}", url=invoice["link"])],
        [InlineKeyboardButton(text="✅ Я оплатил", callback_data=f"xr:{invoice['id']}")],
    ])
    await message.answer("Счёт на оплату действует 1 час:", reply_markup=keyboard)


@router.callback_query(F.data.startswith("xr:"))
async def check(callback: CallbackQuery, bot: Bot) -> None:
    invoice_id = int(callback.data.split(":")[1])
    try:
        invoice = await rocket.get_invoice(invoice_id)
    except RuntimeError:
        invoice = None
    # Счёт должен принадлежать тому, кто нажал кнопку
    if not invoice or str(invoice.get("payload")) != str(callback.from_user.id):
        await callback.answer("Счёт не найден", show_alert=True)
        return
    if not is_paid(invoice):
        await callback.answer("Оплата пока не поступила", show_alert=True)
        return
    await deliver(bot, invoice)
    await callback.message.edit_text("Счёт оплачен ✅")
    await callback.answer()


async def main() -> None:
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Запуск:

export BOT_TOKEN="токен_от_BotFather"
export XROCKET_API_KEY="ключ_Rocket_Pay"
python bot.py

Отправьте боту /buy, оплатите счёт в @xRocket и нажмите «Я оплатил».

Шаг 5. Уведомления об оплате (вебхук)

Чтобы бот выдавал товар сразу после оплаты, укажите в настройках приложения Rocket Pay адрес вебхука, например https://ваш-домен/webhooks/xrocket. Нужен сервер с доменом и HTTPS.

Главное правило: уведомлению не верим на слово. Из него берём только номер счёта, а статус, сумму и payload запрашиваем у xRocket своим API-ключом. Поддельный запрос ничего не даст — API ответит, что счёт не оплачен.

Добавьте в bot.py обработчик и замените функцию main. Импорт перенесите в начало файла:

from aiohttp import web


async def xrocket_webhook(request: web.Request) -> web.Response:
    try:
        update = await request.json()
        invoice_id = int(update["data"]["id"])
        # Статус узнаём у xRocket своим ключом, а не из тела уведомления
        invoice = await rocket.get_invoice(invoice_id)
    except (ValueError, KeyError, TypeError, RuntimeError):
        return web.Response(status=400)
    await deliver(request.app["bot"], invoice)
    return web.Response(text="ok")


async def main() -> None:
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)

    app = web.Application()
    app["bot"] = bot
    app.router.add_post("/webhooks/xrocket", xrocket_webhook)
    runner = web.AppRunner(app)
    await runner.setup()
    await web.TCPSite(runner, "127.0.0.1", 8080).start()  # снаружи — через nginx с HTTPS

    try:
        await dp.start_polling(bot)
    finally:
        await runner.cleanup()

Веб-сервер слушает только 127.0.0.1:8080, а наружу его открывает nginx (или Caddy) с HTTPS — проксируйте путь /webhooks/xrocket на этот адрес. Кнопка «Я оплатил» и вебхук используют одну функцию deliver, поэтому товар не будет выдан дважды.

Безопасность: что проверить перед запуском

  • API-ключ — только в переменных окружения или .env, не в коде и не в репозитории.
  • Статус — запрашивайте через API. Ни нажатие кнопки, ни тело вебхука не доказывают оплату.
  • Сумма и монета — сверяйте с заказом.
  • Принадлежность счёта — payload должен совпадать с покупателем или номером его заказа.
  • Однократная выдача — храните обработанные id счетов в базе данных: множество в памяти очищается при перезапуске.

Частые ошибки

  • Неверное название монеты. В xRocket TON передаётся как TONCOIN. Список монет и минимальные суммы смотрите в документации Rocket Pay API.
  • Сумма меньше минимальной. У каждой монеты свой минимум для счёта — API вернёт ошибку.
  • Ждут статус paid, а приходит completed. Счёт с одним платежом после оплаты может перейти в completed — проверяйте оба статуса, как в примере.
  • Вебхук не приходит. Адрес должен быть доступен из интернета по HTTPS, а сервер — отвечать 200.

Другие способы оплаты — Telegram Stars и CryptoBot; сравнение всех вариантов — в статье «Как принимать оплату в Telegram-боте».

Частые вопросы

Чем xRocket отличается от CryptoBot?

Оба — криптокошельки внутри Telegram с API для приёма оплаты. Отличаются списком монет, комиссиями и интерфейсом. Многие магазины подключают оба, чтобы покупатель платил из привычного кошелька.

В какой монете выставить счёт?

Чаще всего в USDT — у него стабильный курс. TON указывается как TONCOIN.

Можно ли доверять вебхуку xRocket?

Используйте его только как сигнал: берите номер счёта и запрашивайте статус у API своим ключом. Так поддельное уведомление ничего не даст.

Куда приходят деньги?

На баланс приложения Rocket Pay в @xRocket. Оттуда их можно вывести на свой кошелёк.

Готовые решения по теме

⭐️ Auto Buy Stars WEB APPБОТ

⭐️ Auto Buy Stars WEB APP

Бот для покупки Stars/Premium/Пополнением Steam в автоматическом режиме с Fragment Установка производится покупателем

240,00 $≈ 19 333,75 ₽