$ sudo teach IT
Модуль 6 · Polling, Webhook & Filters

Урок 6.5 — PollingConfig и PayloadData

Детально разберём PollingConfig, PollingManager, PayloadData и PayloadData.filter() — инструменты для точной настройки polling и структурирования внутренних данных.

В этом завершающем уроке Модуля 6 мы объединим знания о polling с продвинутой конфигурацией и структурированными данными. PollingConfig позволяет точно настроить поведение polling, а PayloadData — удобно упаковывать и распаковывать данные в payload инвойсов/чеков с возможностью фильтрации через MagicFilter.

⚙️

PollingConfig — глубокая настройка polling

PollingConfig — это dataclass, который определяет параметры polling-цикла. Он передаётся в конструктор CryptoPay или в метод start_polling().

Python · Структура PollingConfig
from dataclasses import dataclass

@dataclass
class PollingConfig:
    timeout: int = 300   # максимальное время ожидания события (сек)
    delay: float = 2.0  # задержка между запросами (сек)
    
    # Можно добавить свои поля (при наследовании)
    ...
Параметр По умолчанию Описание Когда менять
timeout 300 Сколько секунд ждать оплаты/активации. Если событие не наступило за это время — PollingTimeoutError. Увеличьте, если инвойсы живут дольше 5 минут
delay 2.0 Задержка между последовательными запросами к API. Меньше — быстрее реакция, но больше нагрузка. Увеличьте при rate limiting (5-10 сек). Уменьшите для быстрой реакции (0.5-1 сек).

PollingConfig можно передать двумя способами:

Python · Два способа передачи PollingConfig
from aiosend import CryptoPay
from aiosend.polling import PollingConfig

# Способ 1: в конструкторе CryptoPay (используется по умолчанию)
cp1 = CryptoPay(
    token="YOUR_TOKEN",
    polling_config=PollingConfig(
        timeout=600,   # ждём 10 минут
        delay=3,       # проверяем каждые 3 секунды
    ),
)

# Способ 2: в start_polling (переопределяет на время запуска)
@cp1.invoice_paid()
async def on_paid(invoice, **kwargs):
    print(f"Оплачено: {invoice.invoice_id}")

async def main():
    # Переопределяем конфиг для этого запуска
    await cp1.start_polling(
        polling_config=PollingConfig(timeout=120, delay=1),
    )

💡 Как выбрать delay?

delay=1 — быстрая реакция, но ~86400 запросов в сутки. delay=5 — экономичный режим, ~17280 запросов. Crypto Pay не документирует лимиты на количество запросов, но слишком частые запросы могут привести к временной блокировке. Рекомендуем delay=2-3 секунды.

📋

PollingManager — внутреннее устройство

PollingManager — это класс, который непосредственно управляет циклом polling. Он создаётся внутри CryptoPay при вызове start_polling().

Ключевые методы PollingManager:

Метод Описание
run() Запускает основной polling-цикл. Работает до отмены (CancelledError) или ошибки.
stop() Останавливает polling-цикл.
process_invoices() Проверяет новые оплаченные/просроченные инвойсы.
process_checks() Проверяет новые активированные/просроченные чеки.

Внутренний цикл PollingManager выглядит так:

Python · Упрощённая схема работы PollingManager
class PollingManager:
    def __init__(self, client: CryptoPay, config: PollingConfig):
        self.client = client
        self.config = config
        self._is_running = False
    
    async def run(self):
        self._is_running = True
        last_invoice_id = 0
        last_check_id = 0
        
        try:
            while self._is_running:
                # 1. Проверяем новые оплаченные инвойсы
                paid = await self.client.get_invoices(
                    status="paid",
                    offset=last_invoice_id,
                )
                for invoice in paid:
                    await self._process_invoice_paid(invoice)
                    last_invoice_id = max(last_invoice_id, invoice.invoice_id)
                
                # 2. Проверяем новые просроченные инвойсы
                expired = await self.client.get_invoices(
                    status="expired",
                    offset=last_invoice_id,
                )
                for invoice in expired:
                    await self._process_invoice_expired(invoice)
                    last_invoice_id = max(last_invoice_id, invoice.invoice_id)
                
                # 3. Проверяем новые чеки (если есть обработчики)
                activated = await self.client.get_checks(
                    status="activated",
                    offset=last_check_id,
                )
                for check in activated:
                    await self._process_check_activated(check)
                    last_check_id = max(last_check_id, check.check_id)
                
                # 4. Задержка между циклами
                await asyncio.sleep(self.config.delay)
                
        except asyncio.CancelledError:
            pass
        finally:
            self._is_running = False
    
    async def _process_invoice_paid(self, invoice):
        # Применяет фильтры и вызывает обработчики
        for handler in self.client._paid_handlers:
            if await self._check_filters(handler.filters, invoice):
                await handler.func(invoice, **handler.kwargs)
    
    async def stop(self):
        self._is_running = False

PollingManager использует offset для отслеживания, какие инвойсы/чеки уже обработаны. Это гарантирует, что каждое событие будет обработано ровно один раз.

⚠️ PollingManager и offset

PollingManager запоминает ID последнего обработанного инвойса/чека. При перезапуске он продолжит с того же места. Если вы хотите переобработать все события (например, после добавления новых обработчиков), удалите файл состояния или пересоздайте клиент.

📦

PayloadData — типизированный payload

PayloadData — это Pydantic-модель для создания структурированных payload'ов. Она решает проблему «сырых» строковых payload'ов, позволяя упаковывать данные в формат prefix:sep:data и легко извлекать их обратно.

Python · Структура PayloadData
from pydantic import BaseModel

class PayloadData(BaseModel):
    """Базовый класс для типизированных payload."""
    
    prefix: str = ""     # префикс для идентификации типа payload
    sep: str = ":"       # разделитель между частями
    
    def pack(self) -> str:
        """Упаковывает данные в строку payload."""
        ...
    
    @classmethod
    def unpack(cls, payload: str) -> "PayloadData | None":
        """Распаковывает строку payload в объект PayloadData."""
        ...
    
    @classmethod
    def filter(cls, condition) -> "PayloadData":
        """Создаёт MagicFilter для фильтрации по payload."""
        ...

Основные возможности PayloadData:

  • pack() — упаковывает поля модели в строку вида prefix:field1:field2:...
  • unpack() — распаковывает строку обратно в экземпляр модели
  • filter() — создаёт MagicFilter для поиска инвойсов/чеков с определённым payload
Python · Базовое использование PayloadData
from aiosend.payload import PayloadData

# Создаём данные и упаковываем
data = PayloadData(prefix="order", sep=":")
payload_str = data.pack()
print(payload_str)  # "order::"

# Можно передать кастомные поля (Pydantic это допускает)
# Но лучше создать подкласс (см. ниже)

# Распаковываем обратно
parsed = PayloadData.unpack("order::")
print(parsed.prefix)  # "order"

# Создаём фильтр для поиска инвойсов с этим payload
filter_ = PayloadData.filter(prefix="order")
# filter_ теперь можно использовать в @cp.invoice_paid(filter_)
🧬

Подкласс PayloadData — кастомные модели

Настоящая сила PayloadData раскрывается при создании своих подклассов с типизированными полями.

Python · Создание подкласса PayloadData
from aiosend.payload import PayloadData
from typing import Optional

class OrderPayload(PayloadData):
    """Payload для заказов."""
    prefix: str = "order"
    user_id: int = 0
    product_id: str = ""
    quantity: int = 1
    
    def description(self) -> str:
        return f"Заказ {self.product_id} x{self.quantity} для user #{self.user_id}"

class BonusPayload(PayloadData):
    """Payload для бонусов."""
    prefix: str = "bonus"
    user_id: int = 0
    reason: str = ""
    
    def description(self) -> str:
        return f"Бонус для user #{self.user_id}: {self.reason}"

class SubscriptionPayload(PayloadData):
    """Payload для подписок."""
    prefix: str = "sub"
    user_id: int = 0
    plan: str = "monthly"
    auto_renew: bool = False
    sep: str = "|"  # кастомный разделитель

Теперь используем эти модели на практике:

Python · Использование кастомных PayloadData
import asyncio
from aiosend import CryptoPay
from aiosend.payload import PayloadData

cp = CryptoPay(token="YOUR_TOKEN")

# ─── Создание инвойса с PayloadData ─────────────────────

# Упаковываем данные заказа
order = OrderPayload(
    user_id=12345,
    product_id="premium_month",
    quantity=1,
)
payload_str = order.pack()
print(f"Payload: {payload_str}")  # "order:12345:premium_month:1"

# Создаём инвойс с этим payload
invoice = await cp.create_invoice(
    amount=25,
    asset="USDT",
    payload=payload_str,
)

# ─── Обработка оплаты с распаковкой ─────────────────────

@cp.invoice_paid()
async def on_paid(invoice, **kwargs):
    # Распаковываем payload обратно в модель
    parsed = OrderPayload.unpack(invoice.payload)
    if parsed:
        print(f"✅ {parsed.description()}")
        print(f"   Сумма: {invoice.amount} {invoice.asset}")
        # Здесь бизнес-логика по заказу
        await process_order(parsed)
    else:
        print(f"⚠️ Неизвестный формат payload: {invoice.payload}")

async def process_order(order: OrderPayload):
    await asyncio.sleep(0.3)
    print(f"   Заказ обработан: {order.product_id} x{order.quantity}")

async def main():
    await cp.start_polling()

asyncio.run(main())
🔍

PayloadData.filter() — фильтрация по payload

Метод PayloadData.filter() создаёт MagicFilter, который можно передать в декоратор для фильтрации событий по содержимому payload. Это работает в паре с методом pack() — фильтр проверяет, начинается ли строка payload с определённого префикса.

Python · Использование PayloadData.filter()
# Создаём фильтр для каждого типа payload
order_filter = OrderPayload.filter()  # проверяет prefix="order"
bonus_filter = BonusPayload.filter()  # проверяет prefix="bonus"
sub_filter = SubscriptionPayload.filter()  # проверяет prefix="sub"

# Используем фильтры в декораторах
@cp.invoice_paid(order_filter)
async def on_order_paid(invoice, **kwargs):
    parsed = OrderPayload.unpack(invoice.payload)
    print(f"📦 Оплачен заказ: {parsed.description()}")

@cp.invoice_paid(bonus_filter)
async def on_bonus_paid(invoice, **kwargs):
    parsed = BonusPayload.unpack(invoice.payload)
    print(f"🎁 Бонус оплачен: {parsed.description()}")

@cp.invoice_paid(sub_filter)
async def on_sub_paid(invoice, **kwargs):
    parsed = SubscriptionPayload.unpack(invoice.payload)
    print(f"💳 Подписка {parsed.plan} оплачена (auto_renew={parsed.auto_renew})")

# Можно комбинировать с другими фильтрами
@cp.invoice_paid(order_filter, F.amount > 100)
async def on_large_order(invoice, **kwargs):
    parsed = OrderPayload.unpack(invoice.payload)
    print(f"💰 Крупный заказ: {parsed.description()} на {invoice.amount} USDT")

🔍 Как работает PayloadData.filter()?

Метод filter() возвращает MagicFilter, который проверяет, что payload объекта начинается с prefix + sep. Например, для OrderPayload(prefix="order") фильтр проверяет F.payload.startswith("order:"). Это эффективно и не требует распаковки всего payload.

Вы также можете создать фильтр с произвольным префиксом:

Python · PayloadData.filter() с кастомными параметрами
# Фильтр для заказов с конкретным product_id
order_filter_custom = OrderPayload.filter(product_id="premium_month")

# Фильтр для бонусов с определённой причиной
bonus_filter_custom = BonusPayload.filter(reason="welcome")

# Фильтр для подписок с auto_renew=True
sub_filter_renew = SubscriptionPayload.filter(auto_renew=True)

# Использование:
@cp.invoice_paid(order_filter_custom)
async def on_premium_order(invoice, **kwargs):
    print(f"🌟 Премиум-заказ оплачен!")
    # parsed = OrderPayload.unpack(invoice.payload)
    # гарантированно product_id="premium_month"

Обратите внимание: filter() с кастомными параметрами проверяет только префикс и разделитель. Он не проверяет содержимое полей (это сделает unpack() в обработчике). Фильтр лишь гарантирует, что payload имеет правильный формат.

📎

Дополнительные примеры PayloadData

Рассмотрим более сложные и специализированные сценарии использования PayloadData в реальных проектах.

Пример: Вложенные PayloadData модели

Вы можете использовать вложенные Pydantic-модели внутри PayloadData для сложных структур данных:

Python · Вложенные модели в PayloadData
from pydantic import BaseModel
from aiosend.payload import PayloadData
from typing import Optional, List

class Address(BaseModel):
    """Адрес доставки."""
    city: str = ""
    street: str = ""
    house: str = ""
    apartment: str = ""

class CartItem(BaseModel):
    """Товар в корзине."""
    product_id: str = ""
    name: str = ""
    price: float = 0.0
    quantity: int = 1

class OrderPayload(PayloadData):
    """Сложный заказ с вложенными моделями."""
    prefix: str = "order"
    user_id: int = 0
    address: Optional[Address] = None
    items: List[CartItem] = []
    promo_code: str = ""
    
    def total(self) -> float:
        return sum(item.price * item.quantity for item in self.items)
    
    def __str__(self) -> str:
        return f"Заказ #{self.user_id}: {len(self.items)} товаров на ${self.total():.2f}"

# Использование:
order = OrderPayload(
    user_id=12345,
    address=Address(city="Москва", street="Тверская", house="1"),
    items=[
        CartItem(product_id="p1", name="Футболка", price=29.99, quantity=2),
        CartItem(product_id="p2", name="Кепка", price=19.99, quantity=1),
    ],
    promo_code="WELCOME10",
)

payload_str = order.pack()
print(f"Payload: {payload_str}")
print(f"Описание: {order}")
print(f"Сумма: ${order.total():.2f}")

# Распаковка
parsed = OrderPayload.unpack(payload_str)
if parsed:
    for item in parsed.items:
        print(f"  - {item.name} x{item.quantity} = ${item.price * item.quantity:.2f}")

Пример: PayloadData с сериализацией datetime

Можно использовать поля Pydantic с кастомными типами:

Python · PayloadData с datetime
from datetime import datetime
from aiosend.payload import PayloadData

class EventPayload(PayloadData):
    """Payload для мероприятий."""
    prefix: str = "event"
    user_id: int = 0
    event_id: str = ""
    booked_at: str = ""  # ISO формат даты
    
    @classmethod
    def create(cls, user_id: int, event_id: str) -> "EventPayload":
        return cls(
            user_id=user_id,
            event_id=event_id,
            booked_at=datetime.utcnow().isoformat(),
        )
    
    @property
    def booking_time(self) -> datetime | None:
        try:
            return datetime.fromisoformat(self.booked_at)
        except (ValueError, TypeError):
            return None

class PromoPayload(PayloadData):
    """Payload для промо-акций."""
    prefix: str = "promo"
    user_id: int = 0
    promo_id: str = ""
    expires_at: str = ""
    
    @property
    def is_expired(self) -> bool:
        if not self.expires_at:
            return False
        try:
            exp = datetime.fromisoformat(self.expires_at)
            return datetime.utcnow() > exp
        except (ValueError, TypeError):
            return False

# Использование:
event = EventPayload.create(user_id=123, event_id="conf_2024")
print(f"Бронирование: {event.booking_time}")

promo = PromoPayload(
    user_id=456,
    promo_id="SUMMER24",
    expires_at="2025-01-01T00:00:00",
)
print(f"Промо истекло: {promo.is_expired}")

# Использование с инвойсом
invoice = await cp.create_invoice(
    amount=100,
    asset="USDT",
    payload=promo.pack(),
)

# В обработчике:
@cp.invoice_paid(PromoPayload.filter())
async def on_promo_paid(invoice, **kwargs):
    parsed = PromoPayload.unpack(invoice.payload)
    if parsed and not parsed.is_expired:
        print(f"🎉 Промо {parsed.promo_id} активировано!")
    elif parsed and parsed.is_expired:
        print(f"⏰ Промо {parsed.promo_id} истекло")

Пример: PayloadData с валидацией

Pydantic-валидация работает и в PayloadData:

Python · PayloadData с Pydantic-валидацией
from pydantic import field_validator, Field
from aiosend.payload import PayloadData

class PaymentPayload(PayloadData):
    """Payload с валидацией полей."""
    prefix: str = "pay"
    user_id: int = Field(..., ge=1)
    amount: float = Field(..., gt=0)
    currency: str = "USDT"
    
    @field_validator("currency")
    @classmethod
    def validate_currency(cls, v: str) -> str:
        allowed = ["USDT", "TON", "BTC", "ETH", "USDC"]
        if v not in allowed:
            raise ValueError(f"Валюта {v} не поддерживается")
        return v
    
    @field_validator("amount")
    @classmethod
    def validate_amount(cls, v: float) -> float:
        if v > 100000:
            raise ValueError("Сумма превышает лимит")
        return v

# При попытке создать с неверными данными — ошибка валидации
try:
    bad = PaymentPayload(
        prefix="pay",
        user_id=0,  # ошибка: ge=1
        amount=-10,  # ошибка: gt=0
        currency="INVALID",
    )
except Exception as e:
    print(f"Ошибка валидации: {e}")

# Правильное использование
good = PaymentPayload(
    prefix="pay",
    user_id=123,
    amount=99.99,
    currency="USDT",
)
print(f"Payload: {good.pack()}")
print(f"Валидация пройдена!")

Пример: Миграция PayloadData между версиями

Если структура данных меняется со временем, можно поддерживать несколько версий:

Python · Версионирование PayloadData
from aiosend.payload import PayloadData

class UserPayloadV1(PayloadData):
    """Версия 1: базовые поля."""
    prefix: str = "user"
    user_id: int = 0
    name: str = ""

class UserPayloadV2(PayloadData):
    """Версия 2: добавлен email."""
    prefix: str = "user"
    user_id: int = 0
    name: str = ""
    email: str = ""

# Функция для обработки payload любой версии
def process_user_payload(payload_str: str) -> dict | None:
    """Пытается распарсить payload любой версии."""
    # Сначала пробуем V2
    v2 = UserPayloadV2.unpack(payload_str)
    if v2:
        return {
            "user_id": v2.user_id,
            "name": v2.name,
            "email": v2.email,
        }
    
    # Затем V1
    v1 = UserPayloadV1.unpack(payload_str)
    if v1:
        return {
            "user_id": v1.user_id,
            "name": v1.name,
            "email": "unknown@example.com",  # значение по умолчанию
        }
    
    return None

@cp.invoice_paid()
async def on_paid(invoice, **kwargs):
    data = process_user_payload(invoice.payload)
    if data:
        print(f"Пользователь {data['name']} (id={data['user_id']}) "
              f"email={data['email']} оплатил {invoice.amount} USDT")
🧩

Полный пример: интернет-магазин с PayloadData

Создадим систему для интернет-магазина, которая использует PayloadData для идентификации заказов и PollingConfig для настройки поведения polling.

Python · Интернет-магазин на aiosend
import asyncio
import logging
from dataclasses import dataclass, field
from typing import Optional
from magic_filter import F
from aiosend import CryptoPay
from aiosend.polling import PollingConfig
from aiosend.payload import PayloadData

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# ─── Payload модели ──────────────────────────────────────

class ProductPayload(PayloadData):
    prefix: str = "product"
    product_id: str = ""
    user_id: int = 0
    
    def __str__(self):
        return f"Товар #{self.product_id} для user #{self.user_id}"

class SubscriptionPayload(PayloadData):
    prefix: str = "sub"
    user_id: int = 0
    months: int = 1
    sep: str = "|"
    
    def __str__(self):
        return f"Подписка на {self.months} мес. для user #{self.user_id}"

# ─── Магазин ──────────────────────────────────────────────

@dataclass
class Store:
    cp: CryptoPay
    orders: dict = field(default_factory=dict)
    
    def register_handlers(self):
        # Обработчик оплаты товара
        @self.cp.invoice_paid(ProductPayload.filter())
        async def on_product_paid(invoice, **kwargs):
            parsed = ProductPayload.unpack(invoice.payload)
            if parsed:
                logger.info(f"🛒 {parsed} — {invoice.amount} USDT")
                self.orders[parsed.product_id] = "paid"
                await self.deliver_product(parsed)
        
        # Обработчик оплаты подписки
        @self.cp.invoice_paid(SubscriptionPayload.filter())
        async def on_sub_paid(invoice, **kwargs):
            parsed = SubscriptionPayload.unpack(invoice.payload)
            if parsed:
                logger.info(f"💳 {parsed} — {invoice.amount} USDT")
                await self.activate_subscription(parsed)
        
        # Обработчик просрочки
        @self.cp.invoice_expired()
        async def on_expired(invoice, **kwargs):
            logger.warning(f"⏰ Инвойс #{invoice.invoice_id} просрочен")
    
    async def deliver_product(self, payload: ProductPayload):
        await asyncio.sleep(0.5)
        logger.info(f"   📦 Товар #{payload.product_id} доставлен user #{payload.user_id}")
    
    async def activate_subscription(self, payload: SubscriptionPayload):
        await asyncio.sleep(0.3)
        logger.info(f"   ✅ Подписка активирована на {payload.months} мес.")
    
    async def create_product_order(self, product_id: str, user_id: int,
                                    price: float):
        payload = ProductPayload(product_id=product_id, user_id=user_id)
        invoice = await self.cp.create_invoice(
            amount=price,
            asset="USDT",
            payload=payload.pack(),
            description=str(payload),
        )
        logger.info(f"📄 Счёт #{invoice.invoice_id}: {payload}")
        return invoice
    
    async def create_subscription(self, user_id: int, months: int, price: float):
        payload = SubscriptionPayload(user_id=user_id, months=months)
        invoice = await self.cp.create_invoice(
            amount=price,
            asset="USDT",
            payload=payload.pack(),
            description=str(payload),
            expires_in=3600,
        )
        logger.info(f"📄 Счёт #{invoice.invoice_id}: {payload}")
        return invoice

async def main():
    cp = CryptoPay(
        token="YOUR_TOKEN",
        polling_config=PollingConfig(timeout=600, delay=2),
    )
    
    store = Store(cp)
    store.register_handlers()
    
    # Создаём тестовые заказы
    await store.create_product_order("game_pass", 101, 15)
    await store.create_product_order("vip_badge", 102, 50)
    await store.create_subscription(103, 3, 75)
    
    logger.info("🚀 Запуск polling...")
    try:
        await cp.start_polling()
    except KeyboardInterrupt:
        logger.info("Магазин закрыт")

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

Совместное использование PollingConfig и PayloadData

В реальном проекте PollingConfig и PayloadData используются вместе. PollingConfig настраивает механизм доставки событий, а PayloadData структурирует данные внутри этих событий.

Python · Фабрика с PollingConfig и PayloadData
import os
from aiosend import CryptoPay
from aiosend.polling import PollingConfig
from aiosend.payload import PayloadData

class PaymentService:
    """Сервис для управления платежами с поллинтом."""
    
    def __init__(self, token: str | None = None):
        self.cp = CryptoPay(
            token=token or os.getenv("CRYPTOPAY_TOKEN"),
            polling_config=PollingConfig(
                timeout=int(os.getenv("POLLING_TIMEOUT", "300")),
                delay=float(os.getenv("POLLING_DELAY", "2.0")),
            ),
        )
        self._register_handlers()
    
    def _register_handlers(self):
        @self.cp.invoice_paid()
        async def handle_paid(invoice, **kwargs):
            # Распаковываем payload в любую известную модель
            for model in [OrderPayload, BonusPayload, SubPayload]:
                parsed = model.unpack(invoice.payload)
                if parsed:
                    await self._process(invoice, parsed)
                    break
            else:
                # Неизвестный формат
                print(f"⚠️ Неизвестный payload: {invoice.payload}")
        
        @self.cp.invoice_expired()
        async def handle_expired(invoice, **kwargs):
            print(f"⏰ Просрочен инвойс #{invoice.invoice_id}")
    
    async def _process(self, invoice, payload):
        print(f"Обработка: {payload}")
    
    async def start(self):
        await self.cp.start_polling()

Такой сервис можно использовать как основу для любого проекта. Он гибко настраивается через переменные окружения, поддерживает любые типы PayloadData и корректно обрабатывает все события.

📌

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

1️⃣
PollingConfig(timeout, delay). timeout — сколько ждать событие, delay — пауза между запросами. По умолчанию 300/2.
2️⃣
PollingManager — внутренний двигатель. Управляет циклом, использует offset для избежания дубликатов.
3️⃣
PayloadData — Pydantic-модель для payload. pack() упаковывает, unpack() распаковывает, filter() создаёт MagicFilter.
4️⃣
Создавайте подклассы PayloadData. Для каждого типа данных (заказ, бонус, подписка) — свой класс с типизированными полями.
5️⃣
PayloadData.filter() + декораторы = мощь. Автоматически направляйте события в нужные обработчики на основе типа payload.
💻

Практическая задача

Задача: Платёжная система с PayloadData и кастомным PollingConfig

Напишите скрипт payment_system_v2.py, который объединяет всё изученное в Модуле 6:

  1. Определите три PayloadData-модели:
    • DonationPayload(prefix="donation"): user_id, message
    • ShopPayload(prefix="shop"): user_id, item_id, quantity
    • WithdrawPayload(prefix="withdraw"): user_id, wallet_address
  2. Создайте клиента CryptoPay с кастомным PollingConfig(timeout=600, delay=1.5).
  3. Зарегистрируйте три обработчика через PayloadData.filter(), каждый для своего типа.
  4. В каждом обработчике распакуйте payload через unpack() и выполните соответствующую логику.
  5. Добавьте обработчик invoice_expired для логирования просрочек.
  6. Создайте по одному тестовому инвойсу каждого типа и запустите polling.

Ожидаемый вывод при оплате:

🎁 Donation: User #123: "Спасибо за ваш труд!"
🛒 Shop: User #456 купил item_789 x2
💸 Withdraw: User #789 запросил вывод на wallet_abc
⏰ Инвойс #99999 просрочен

Урок 6.5: PollingConfig и PayloadData

8 вопросов