$ sudo teach IT
Модуль 8 · Интеграции с Telegram-ботами

Урок 8.2 — Интеграция с aiogram 2.x

Учимся интегрировать aiosend с aiogram 2.x: executor.start_polling, параллельный запуск и обработка платежей.

aiogram 2.x — более старая, но всё ещё популярная версия библиотеки. Интеграция с aiosend строится по тому же принципу, что и с 3.x, но с использованием executor.start_polling() и декораторов @dp.message_handler(). Основное отличие — синхронный запуск через executor.

⚡

Параллельный запуск с executor

В aiogram 2.x используется executor.start_polling(), который сам управляет event loop. Для интеграции с aiosend нужно запустить aiosend polling внутри aiogram-функции on_startup или через asyncio.ensure_future().

Python · Параллельный запуск aiogram 2.x + aiosend
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiosend import CryptoPay

BOT_TOKEN = "123456:ABCdefGHIjklmNOP"
CP_TOKEN = "1234:ABCDefGHIJklmnOPQRST"

bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(bot)
dp.middleware.setup(LoggingMiddleware())
cp = CryptoPay(token=CP_TOKEN)

@dp.message_handler(commands=["start"])
async def start(message: types.Message):
    await message.answer(
        "Привет! Я бот на aiogram 2.x + aiosend!\n"
        "Используй /pay 10 USDT для оплаты."
    )

@dp.message_handler(commands=["pay"])
async def pay(message: types.Message):
    args = message.text.split()[1:]  # ["10", "USDT"]
    if len(args) < 2:
        await message.answer("Использование: /pay 10 USDT")
        return

    amount, asset = float(args[0]), args[1].upper()
    invoice = await cp.create_invoice(
        amount=amount,
        asset=asset,
        payload=str(message.from_user.id),
    )

    await message.answer(
        f"🧾 Счёт на {amount} {asset}:\n"
        f"{invoice.bot_invoice_url}\n\n"
        f"Ожидаем оплату..."
    )

@cp.invoice_paid()
async def on_paid(invoice):
    user_id = int(invoice.payload)
    await bot.send_message(
        user_id,
        f"✅ Оплата {invoice.amount} {invoice.asset} получена!"
    )

async def on_startup(_):
    """Запускает aiosend polling при старте aiogram."""
    asyncio.ensure_future(cp.start_polling())

if __name__ == "__main__":
    from aiogram import executor
    executor.start_polling(
        dp,
        on_startup=on_startup,
        skip_updates=True,
    )

💡 Как это работает?

executor.start_polling() запускает aiogram и вызывает on_startup. Внутри on_startup мы через asyncio.ensure_future() запускаем aiosend polling в том же event loop. Оба polling-цикла работают параллельно.

🧾

Создание инвойса и ожидание оплаты

В aiogram 2.x invoice.poll() тоже поддерживает параметр message, но обрати внимание: объект Message в 2.x API имеет немного другую структуру. aiosend корректно работает с обеими версиями.

Python · invoice.poll с сообщением в aiogram 2.x
@dp.message_handler(commands=["buy"])
async def buy(message: types.Message):
    # Создаём инвойс
    invoice = await cp.create_invoice(
        amount=15,
        asset="USDT",
        description="Премиум-доступ",
        payload=str(message.from_user.id),
    )

    # Отправляем сообщение
    msg = await message.answer(
        f"💳 Премиум-доступ\n"
        f"Сумма: 15 USDT\n\n"
        f"🔗 {invoice.bot_invoice_url}\n\n"
        f"⏳ Ожидаем оплату...",
        parse_mode="HTML",
    )

    # Ожидаем оплату — сообщение будет отредактировано
    try:
        paid = await invoice.poll(message=msg)
        await msg.edit_text(
            f"✅ Оплачено!\n"
            f"{paid.paid_amount} {paid.paid_asset}\n"
            f"Спасибо!",
            parse_mode="HTML",
        )
    except Exception as e:
        await msg.edit_text(f"❌ Ошибка: {e}")

⚠️ Отличие от aiogram 3.x

В aiogram 2.x используется @dp.message_handler() вместо @dp.message(), а executor.start_polling() — вместо dp.start_polling(). Остальная логика интеграции с aiosend идентична.

🚀

Полный пример: магазин на aiogram 2.x

Python · Полный бот с платежами (aiogram 2.x)
import asyncio
import logging
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiosend import CryptoPay

logging.basicConfig(level=logging.INFO)

BOT_TOKEN = "123456:ABCdefGHIjklmNOP"
CP_TOKEN = "1234:ABCDefGHIJklmnOPQRST"

bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(bot)
dp.middleware.setup(LoggingMiddleware())
cp = CryptoPay(token=CP_TOKEN)

PRODUCTS = {
    "premium": {"name": "Премиум", "price": 15, "asset": "USDT"},
    "vip": {"name": "VIP-доступ", "price": 50, "asset": "USDT"},
    "boost": {"name": "Буст", "price": 5, "asset": "TON"},
}

@dp.message_handler(commands=["start"])
async def start(message: types.Message):
    kb = InlineKeyboardMarkup(row_width=2).add(
        InlineKeyboardButton("💎 Премиум", callback_data="buy_premium"),
        InlineKeyboardButton("⭐ VIP", callback_data="buy_vip"),
        InlineKeyboardButton("🚀 Буст", callback_data="buy_boost"),
    )
    await message.answer(
        "🛒 Добро пожаловать в магазин!\n"
        "Выберите товар:",
        reply_markup=kb,
    )

@dp.callback_query_handler(lambda c: c.data.startswith("buy_"))
async def process_buy(callback: types.CallbackQuery):
    product_key = callback.data.replace("buy_", "")
    product = PRODUCTS.get(product_key)
    if not product:
        await callback.answer("Товар не найден")
        return

    await callback.answer("Создаём счёт...")

    invoice = await cp.create_invoice(
        amount=product["price"],
        asset=product["asset"],
        description=product["name"],
        payload=f"{callback.from_user.id}:{product_key}",
        expires_in=600,
    )

    msg = await callback.message.answer(
        f"💳 {product['name']}\n"
        f"Сумма: {product['price']} {product['asset']}\n\n"
        f"🔗 {invoice.bot_invoice_url}\n\n"
        f"⏳ Ожидаем оплату...",
        parse_mode="HTML",
    )

    try:
        paid = await invoice.poll(message=msg)
        await msg.edit_text(
            f"✅ Оплачено!\n"
            f"{paid.paid_amount} {paid.paid_asset}\n"
            f"Товар: {product['name']}",
            parse_mode="HTML",
        )
    except Exception as e:
        await msg.edit_text(f"❌ Ошибка: {e}")

@cp.invoice_paid()
async def on_paid(invoice):
    user_id, product_key = invoice.payload.split(":")
    product = PRODUCTS.get(product_key, {})
    print(
        f"Платёж #{invoice.invoice_id}: "
        f"user={user_id} product={product.get('name')}"
    )
    await bot.send_message(
        int(user_id),
        f"✅ Ваш {product.get('name', 'товар')} активирован!"
    )

async def on_startup(_):
    asyncio.ensure_future(cp.start_polling())

async def on_shutdown(_):
    await cp.stop_polling()

if __name__ == "__main__":
    from aiogram import executor
    executor.start_polling(
        dp,
        on_startup=on_startup,
        on_shutdown=on_shutdown,
        skip_updates=True,
    )

🔍 Ключевые отличия от aiogram 3.x

  • @dp.message_handler() вместо @dp.message()
  • @dp.callback_query_handler() вместо @dp.callback_query()
  • executor.start_polling() вместо dp.start_polling()
  • Запуск aiosend через asyncio.ensure_future() в on_startup
  • Остановка через on_shutdown
🛑

Корректная остановка polling

При завершении работы бота важно корректно остановить оба polling-цикла. Для этого используй on_shutdown:

Python · Корректная остановка
async def on_startup(_):
    """Запуск aiosend при старте."""
    asyncio.ensure_future(cp.start_polling())

async def on_shutdown(_):
    """Остановка aiosend при завершении."""
    await cp.stop_polling()
    print("aiosend polling остановлен")

if __name__ == "__main__":
    from aiogram import executor
    executor.start_polling(
        dp,
        on_startup=on_startup,
        on_shutdown=on_shutdown,
    )
📌

Что важно запомнить

1️⃣
executor.start_polling() — основной способ запуска aiogram 2.x.
2️⃣
asyncio.ensure_future() в on_startup запускает aiosend в том же event loop.
3️⃣
on_shutdown — корректно останавливай aiosend через cp.stop_polling().
4️⃣
API хендлеров отличается от 3.x: message_handler, callback_query_handler.
🎯

Практическое задание

Задание: Бот для приёма платежей на aiogram 2.x

Портиpyй пример магазина из урока 8.1 на aiogram 2.x:

  • Используй executor.start_polling()
  • Запусти aiosend через asyncio.ensure_future() в on_startup
  • Замени декораторы на @dp.message_handler() и @dp.callback_query_handler()
  • Добавь on_shutdown для остановки aiosend

Подсказка:

Python · Шаблон решения
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiosend import CryptoPay

bot = Bot(token="BOT_TOKEN")
dp = Dispatcher(bot)
cp = CryptoPay(token="CP_TOKEN")

@dp.message_handler(commands=["start"])
async def start(message: types.Message):
    await message.answer(
        "Напиши /buy 10 USDT для покупки"
    )

@dp.message_handler(commands=["buy"])
async def buy(message: types.Message):
    args = message.text.split()
    amount, asset = float(args[1]), args[2].upper()
    invoice = await cp.create_invoice(
        amount=amount, asset=asset,
        payload=str(message.from_user.id),
    )
    msg = await message.answer(
        f"Счёт: {invoice.bot_invoice_url}"
    )
    paid = await invoice.poll(message=msg)
    await msg.edit_text(f"✅ Оплачено: {paid.paid_amount}")

@cp.invoice_paid()
async def on_paid(invoice):
    uid = int(invoice.payload)
    await bot.send_message(uid, "Спасибо!")

async def on_startup(_):
    asyncio.ensure_future(cp.start_polling())

async def on_shutdown(_):
    await cp.stop_polling()

if __name__ == "__main__":
    from aiogram import executor
    executor.start_polling(dp, on_startup=on_startup, on_shutdown=on_shutdown)

Урок 8.2: Интеграция с aiogram 2.x

5 вопросов