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

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

Учимся интегрировать aiosend с aiogram 3.x: параллельный запуск polling, создание инвойсов из сообщений бота и полный цикл оплаты.

aiogram 3.x — это асинхронная библиотека для работы с Telegram Bot API. Интеграция с aiosend строится на параллельном запуске двух polling-циклов: бот слушает команды пользователей, aiosend ожидает оплаты инвойсов. Оба цикла работают одновременно через asyncio.gather().

⚡

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

Для одновременной работы aiogram и aiosend используется asyncio.gather(). Оба polling-цикла запускаются параллельно в одном event loop.

Python · Параллельный запуск aiogram + aiosend
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiosend import CryptoPay

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

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

@dp.message(Command("start"))
async def start(message: types.Message):
    await message.answer(
        "Привет! Я бот с приёмом крипто-платежей!\n"
        "Напиши /pay 10 USDT для создания счёта."
    )

@dp.message(Command("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"Mini App: {invoice.mini_app_invoice_url}"
    )

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

async def main():
    await asyncio.gather(
        dp.start_polling(bot),  # aiogram polling
        cp.start_polling(),      # aiosend polling
    )

asyncio.run(main())

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

aiogram слушает команды от Telegram и отвечает пользователям. aiosend параллельно опрашивает Crypto Pay API на предмет оплаты созданных инвойсов. Когда платёж подтверждён, aiosend вызывает хендлер, который через aiogram отправляет пользователю уведомление.

🧾

Создание инвойса из сообщения

Метод invoice.poll(message=message) позволяет привязать ожидание оплаты к конкретному сообщению в Telegram. Когда пользователь оплачивает инвойс, бот автоматически редактирует сообщение.

Python · invoice.poll с message
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiosend import CryptoPay

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

@dp.message(Command("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"💳 Счёт на 15 USDT\n"
        f"Ссылка: {invoice.bot_invoice_url}\n\n"
        f"⏳ Ожидаем оплату..."
    )

    # Ожидаем оплату с привязкой к сообщению
    paid = await invoice.poll(message=msg)

    # После оплаты сообщение автоматически обновится
    # Можно отредактировать вручную:
    await msg.edit_text(
        f"✅ Оплачено {paid.paid_amount} {paid.paid_asset}!\n"
        f"Спасибо за покупку!"
    )

async def main():
    await asyncio.gather(
        dp.start_polling(bot),
        cp.start_polling(),
    )

asyncio.run(main())

⚠️ Особенности message в poll()

Параметр message должен быть объектом aiogram Message. aiosend использует его для вызова message.bot.edit_message_text() при успешной оплате. Убедись, что у бота есть права на редактирование сообщений.

🚀

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

Python · Полный бот с платежами
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiosend import CryptoPay

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

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

# Товары
PRODUCTS = {
    "premium": {"name": "Премиум-подписка", "price": 15, "asset": "USDT"},
    "vip": {"name": "VIP-доступ", "price": 50, "asset": "USDT"},
    "ton_pack": {"name": "Набор TON", "price": 10, "asset": "TON"},
}

@dp.message(Command("start"))
async def start(message: types.Message):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🛒 Купить Премиум", callback_data="buy_premium")],
        [InlineKeyboardButton(text="⭐ Купить VIP", callback_data="buy_vip")],
        [InlineKeyboardButton(text="💎 Набор TON", callback_data="buy_ton_pack")],
    ])
    await message.answer("Добро пожаловать в магазин!", reply_markup=kb)

@dp.callback_query(lambda c: c.data.startswith("buy_"))
async def handle_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}",
    )

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

    paid_invoice = await invoice.poll(message=msg)

    await msg.edit_text(
        f"✅ Оплачено!\n"
        f"{paid_invoice.paid_amount} {paid_invoice.paid_asset}\n"
        f"Товар: {product['name']}\n"
        f"Спасибо за покупку!",
        parse_mode="HTML",
    )

@cp.invoice_paid()
async def on_paid(invoice):
    """Логируем все оплаты."""
    user_id, product_key = invoice.payload.split(":")
    product = PRODUCTS.get(product_key, {"name": "Неизвестный"})
    print(
        f"Платёж #{invoice.invoice_id}: "
        f"user={user_id}, "
        f"product={product['name']}, "
        f"amount={invoice.paid_amount} {invoice.paid_asset}"
    )

async def main():
    print("Бот запущен...")
    await asyncio.gather(
        dp.start_polling(bot),
        cp.start_polling(),
    )

asyncio.run(main())

🔍 Ключевые моменты

  • asyncio.gather() запускает два polling-цикла параллельно
  • payload хранит user_id и название товара для обработки
  • invoice.poll(message=msg) автоматически обновляет сообщение при оплате
  • @cp.invoice_paid() — глобальный хендлер для логирования
  • Inline-кнопки с callback_data для выбора товара
⚠️

Обработка ошибок и таймаутов

При ожидании оплаты через invoice.poll() может возникнуть таймаут (пользователь не оплатил). Обрабатывай это исключение и уведомляй пользователя.

Python · Обработка таймаута
from aiosend.exceptions import APITimeoutError

@dp.message(Command("buy"))
async def buy_safe(message: types.Message):
    invoice = await cp.create_invoice(
        amount=10, asset="USDT",
        expires_in=300,  # 5 минут
    )

    msg = await message.answer(
        f"Счёт на 10 USDT\n{invoice.bot_invoice_url}"
    )

    try:
        paid = await invoice.poll(message=msg)
        await msg.edit_text(f"✅ Оплачено: {paid.paid_amount} USDT")
    except APITimeoutError:
        await msg.edit_text("⏱ Время оплаты истекло. Попробуйте снова.")
    except Exception as e:
        await msg.edit_text(f"❌ Ошибка: {e}")
📌

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

1️⃣
asyncio.gather() — основной способ запуска двух polling-циклов одновременно.
2️⃣
invoice.poll(message=message) — привязывает ожидание к сообщению Telegram.
3️⃣
payload — храни user_id и данные заказа для обработки после оплаты.
4️⃣
Обрабатывай APITimeoutError — пользователь может не оплатить инвойс вовремя.
🎯

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

Задание: Бот для приёма донатов

Создай бота на aiogram 3.x + aiosend, который:

  • По команде /donate [сумма] [валюта] создаёт инвойс
  • Отправляет ссылку на оплату
  • Ожидает оплату через invoice.poll(message=msg)
  • Благодарит пользователя после оплаты
  • Логирует все донаты через @cp.invoice_paid()

Подсказка:

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

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

@dp.message(Command("donate"))
async def donate(message: types.Message):
    args = message.text.split()
    if len(args) < 3:
        await message.answer("Пример: /donate 10 USDT")
        return
    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"💛 Спасибо за поддержку!\n"
        f"Счёт на {amount} {asset}\n"
        f"{invoice.bot_invoice_url}"
    )
    paid = await invoice.poll(message=msg)
    await msg.edit_text(
        f"✅ Получено {paid.paid_amount} {paid.paid_asset}!\n"
        f"Огромное спасибо за донат! ❤️"
    )

@cp.invoice_paid()
async def log_donate(invoice):
    print(f"Донат: user={invoice.payload} сумма={invoice.amount}")

async def main():
    await asyncio.gather(dp.start_polling(bot), cp.start_polling())

asyncio.run(main())

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

5 вопросов