$ sudo teach IT
Модуль 4 · Checks — крипто-чеки

Урок 4.2 — Управление чеками

Научимся получать список чеков с фильтрацией, получать один чек по ID, обновлять и удалять чеки. Разберём пагинацию и статусы CheckStatus.

В предыдущем уроке мы научились создавать чеки. Но что делать, когда у тебя накопились десятки или сотни активных и активированных чеков? Нужно уметь получать их список, фильтровать, обновлять и удалять. В этом уроке мы разберём все методы управления чеками, которые предоставляет aiosend.

🏷️

CheckStatus — статусы крипто-чека

Прежде чем управлять чеками, нужно понимать их статусы. Каждый чек может находиться в одном из двух состояний:

🟢

CheckStatus.ACTIVE

Чек активен и ожидает активации. Его можно активировать (получить средства), удалить или обновить. У статуса есть псевдоним CheckStatus.ACTIVE.

⚡

CheckStatus.ACTIVATED

Чек уже активирован (средства получены). Такой чек нельзя активировать повторно. Он остаётся в истории для аудита. Псевдоним — CheckStatus.ACTIVATED.

Python · Использование CheckStatus
from aiosend import CheckStatus

# Два возможных статуса:
print(CheckStatus.ACTIVE)       # "active"
print(CheckStatus.ACTIVATED)    # "activated"

# Сравнение со строкой тоже работает:
status = "active"
if status == CheckStatus.ACTIVE:
    print("Чек активен")

# Использование в методах API:
checks = await cp.get_checks(status=CheckStatus.ACTIVE)
checks = await cp.get_checks(status=CheckStatus.ACTIVATED)

⚠️ Важно: статусы — это строки

На уровне API статусы передаются как строки "active" и "activated". aiosend предоставляет enum CheckStatus для удобства. Ты можешь использовать как сам enum, так и строку напрямую — библиотека приведёт тип автоматически.

📋

Метод get_checks() — получение списка чеков

Метод get_checks() возвращает список крипто-чеков с возможностью фильтрации по различным параметрам. Это основной способ получить историю всех чеков твоего приложения.

Python · Сигнатура метода
async def get_checks(
    self,
    asset: str | None = None,
    check_ids: list[int] | None = None,
    status: str | None = None,
    offset: int | None = None,
    count: int | None = None,
) -> list[Check]

Все параметры опциональны — если вызвать get_checks() без аргументов, вернутся все чеки приложения (с учётом ограничения API на максимальное количество).

Python · Таблица параметров get_checks()
Параметр Тип Описание
asset str | None Фильтр по активу (например "USDT", "TON"). Вернутся только чеки в этом активе.
check_ids list[int] | None Фильтр по ID чеков. Передай список ID, и API вернёт только эти чеки. Полезно для получения конкретных чеков.
status str | None Фильтр по статусу: "active" или "activated". Можно передавать строку или CheckStatus.
offset int | None Смещение для пагинации. Сколько чеков пропустить с начала списка.
count int | None Количество чеков для возврата. Максимум за один запрос — 1000.
Python · Различные варианты get_checks()
from aiosend import CryptoPay, CheckStatus

cp = CryptoPay(token="YOUR_TOKEN")

# 1. Все чеки без фильтрации
all_checks = await cp.get_checks()
print(f"Всего чеков: {len(all_checks)}")

# 2. Только активные чеки в USDT
active_usdt = await cp.get_checks(
    asset="USDT",
    status=CheckStatus.ACTIVE,
)
for check in active_usdt:
    print(f"Чек #{check.check_id}: {check.amount} {check.asset}")

# 3. Конкретные чеки по ID
specific = await cp.get_checks(
    check_ids=[1, 2, 3],
)
print(f"Найдено: {len(specific)}")

# 4. Только активированные, с пагинацией
activated = await cp.get_checks(
    status=CheckStatus.ACTIVATED,
    offset=0,
    count=50,
)

# 5. Чеки в TON (активные и активированные)
ton_checks = await cp.get_checks(asset="TON")

🔍 Как работает фильтрация check_ids?

Параметр check_ids позволяет передать список до 1000 ID чеков. API вернёт все найденные чеки из этого списка. Если некоторые ID не существуют или принадлежат другому приложению — они будут просто проигнорированы. Это удобно для массовых операций: например, ты хочешь вывести список конкретных чеков на панели управления.

📄

Пагинация списка чеков

Когда у тебя сотни или тысячи чеков, API не вернёт их все сразу. Нужно использовать пагинацию с параметрами offset и count.

Параметр Значение Результат
offset=0, count=100 Первая страница Чеки с 0 по 100
offset=100, count=100 Вторая страница Чеки со 101 по 200
offset=200, count=100 Третья страница Чеки с 201 по 300
offset=0, count=1000 Максимальная страница Чеки с 0 по 999 (лимит API)

Максимальное значение count — 1000. Если тебе нужно получить все чеки, когда их больше 1000, используй цикл с увеличением offset на каждой итерации.

Python · Получение всех чеков через пагинацию
from aiosend import CryptoPay

async def get_all_checks(cp: CryptoPay) -> list:
    all_checks = []
    offset = 0
    page_size = 500

    while True:
        page = await cp.get_checks(offset=offset, count=page_size)
        if not page:
            break
        all_checks.extend(page)
        offset += len(page)
        print(f"Загружено {len(all_checks)} чеков...")

    print(f"Всего загружено: {len(all_checks)} чеков")
    return all_checks

# Использование:
cp = CryptoPay(token="YOUR_TOKEN")
checks = await get_all_checks(cp)

# Теперь можно анализировать все чеки
active = [c for c in checks if c.status == "active"]
activated = [c for c in checks if c.status == "activated"]
print(f"Активных: {len(active)}, Активированных: {len(activated)}")

⚠️ Когда остановить пагинацию?

Проверяй длину полученной страницы: если она меньше count — значит, это последняя страница. В примере выше мы проверяем if not page, но API всегда возвращает пустой список [] при отсутствии данных, поэтому цикл остановится корректно.

🔍

Метод get_check() — получение одного чека

Если тебе нужно получить полную информацию об одном конкретном чеке, используй get_check(). Он принимает check_id и возвращает объект Check.

Python · Сигнатура и пример
async def get_check(self, check_id: int) -> Check

# Пример использования:
cp = CryptoPay(token="YOUR_TOKEN")

check = await cp.get_check(check_id=42)
print(f"Чек #{check.check_id}")
print(f"Сумма: {check.amount} {check.asset}")
print(f"Статус: {check.status}")
print(f"Создан: {check.created_at}")
if check.activated_at:
    print(f"Активирован: {check.activated_at}")
if check.activated_by:
    print(f"Кем активирован: user #{check.activated_by}")

# У чека также есть метод-сокращение check.update()
# и свойство check.qr (о них позже)

Объект Check содержит следующие поля:

Поле Тип Описание
check_id int Уникальный ID чека в системе Crypto Pay
amount str Сумма чека (строка для сохранения точности)
asset str Актив чека (например "USDT", "TON")
status str Статус: "active" или "activated"
created_at str Дата создания в формате ISO 8601
activated_at str | None Дата активации (если чек активирован)
activated_by int | None User ID активировавшего пользователя
bot_check_url str Ссылка на чек в @CryptoBot
payload str | None Payload, указанный при создании чека

💡 check_id — уникальный идентификатор

Каждый чек получает уникальный check_id от API. Он используется для всех операций: получения, обновления, удаления. ID уникальны в пределах всего Crypto Pay, а не только твоего приложения. Храни ID чеков в своей базе данных для быстрого доступа.

✏️

check.update() — удобный shortcut

Объект Check имеет метод check.update(), который является сокращением для вызова cp.update_check(check_id, ...) на родительском клиенте. Это удобно: тебе не нужно хранить ссылку на клиент отдельно.

Метод check.update() принимает те же параметры, что и update_check() на клиенте — но без check_id (он берётся из самого объекта).

Python · Использование check.update()
from aiosend import CryptoPay

cp = CryptoPay(token="YOUR_TOKEN")

# Получаем чек
check = await cp.get_check(check_id=42)

# Обновляем чек — новый check_id, та же сумма
# check.update(...) эквивалентно cp.update_check(check.check_id, ...)
updated = await check.update()

print(f"Старый ID: {check.check_id}")
print(f"Новый ID: {updated.check_id}")
print(f"Сумма: {updated.amount} {updated.asset}")
print(f"Статус: {updated.status}")

# Можно явно указать клиент, если чек получен не из него:
# updated = await check.update(cp=another_client)
Python · update_check() на клиенте
# То же самое через клиент напрямую
from aiosend import CryptoPay

cp = CryptoPay(token="YOUR_TOKEN")

updated = await cp.update_check(check_id=42)
# или:
updated = await cp.update_check(check_id=42, cp=cp)

# check.update() — это просто syntax sugar для:
# updated = await cp.update_check(check_id=check.check_id)

🔍 Как работает update_check()?

В отличие от многих других API, update_check() не обновляет существующий чек, а создаёт новый с той же суммой и активом, но с новым check_id. Старый чек при этом становится недействительным (его нельзя активировать). Это особенность Crypto Pay API — обновление чека означает его перевыпуск.

🗑️

Удаление чеков: delete_check() и check.delete()

aiosend предоставляет несколько способов удалить чек. Ты можешь удалить один чек по ID, удалить все чеки сразу или использовать shortcut на объекте Check.

delete_check(check_id) — удаление одного чека

Python · Удаление чека
from aiosend import CryptoPay

cp = CryptoPay(token="YOUR_TOKEN")

# Удаляем чек по ID
result = await cp.delete_check(check_id=42)
print(result)  # True — чек удалён

# Удаляем через shortcut на объекте
check = await cp.get_check(check_id=42)
result = await check.delete()
print(result)  # True — чек удалён

# Попытка удалить несуществующий чек
result = await cp.delete_check(check_id=99999)
print(result)  # True — API считает операцию успешной
# (даже если чек уже не существует)

check.delete() — shortcut на объекте

Как и с check.update(), у объекта Check есть метод check.delete(), который вызывает cp.delete_check(check.check_id). Это удобно, когда у тебя есть объект чека, но нет прямого доступа к клиенту.

Python · Обработка ошибок при удалении
from aiosend import CryptoPay
from aiosend.exceptions import APIError

async def safe_delete_check(cp: CryptoPay, check_id: int) -> bool:
    try:
        return await cp.delete_check(check_id=check_id)
    except APIError as e:
        print(f"Ошибка при удалении чека #{check_id}: {e}")
        return False

cp = CryptoPay(token="YOUR_TOKEN")
success = await safe_delete_check(cp, 42)
if success:
    print("Чек успешно удалён")

delete_all_checks() — удаление всех чеков

Метод delete_all_checks() удаляет все активные чеки приложения. Это мощная операция — используй её с осторожностью. Активированные чеки этот метод не затрагивает.

Python · Удаление всех чеков
from aiosend import CryptoPay

cp = CryptoPay(token="YOUR_TOKEN")

# Получаем количество активных чеков
active_checks = await cp.get_checks(status="active")
print(f"Активных чеков: {len(active_checks)}")

# Удаляем все активные чеки
result = await cp.delete_all_checks()
print(result)  # True — все активные чеки удалены

# Проверяем, что активных чеков больше нет
active_checks = await cp.get_checks(status="active")
print(f"Осталось активных: {len(active_checks)}")

# Этот метод возвращает bool, а не список удалённых чеков

‼️ Осторожно: delete_all_checks()

Метод удаляет все активные чеки без возможности восстановления. Убедись, что ты действительно хочешь удалить все чеки, прежде чем вызывать этот метод. Активированные чеки (status="activated") не удаляются — они остаются в истории. Если тебе нужно удалить только некоторые чеки, используй delete_check(check_id) с фильтрацией через get_checks().

📊

Практический пример: дашборд управления чеками

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

Python · Дашборд управления чеками
from collections import Counter
from datetime import datetime, timedelta
from aiosend import CryptoPay, CheckStatus
from aiosend.exceptions import APIError

class CheckDashboard:
    """Дашборд для управления крипто-чеками."""

    def __init__(self, cp: CryptoPay):
        self.cp = cp

    async def show_stats(self):
        """Показывает статистику по всем чекам."""
        all_checks = await self.cp.get_checks()

        active = [c for c in all_checks if c.status == CheckStatus.ACTIVE]
        activated = [c for c in all_checks if c.status == CheckStatus.ACTIVATED]

        print(f"📊 Статистика чеков:")
        print(f"   Всего: {len(all_checks)}")
        print(f"   Активных: {len(active)}")
        print(f"   Активированных: {len(activated)}")

        # Группировка по активам
        asset_counts = Counter(c.asset for c in active)
        print(f"\n   По активам (активные):")
        for asset, count in asset_counts.most_common():
            print(f"     {asset}: {count}")

    async def delete_old_checks(self, days: int = 30):
        """Удаляет активные чеки старше N дней."""
        all_checks = await self.cp.get_checks(status=CheckStatus.ACTIVE)
        threshold = datetime.utcnow() - timedelta(days=days)
        deleted = 0

        for check in all_checks:
            created = datetime.fromisoformat(check.created_at.replace("Z", "+00:00"))
            if created < threshold:
                try:
                    await check.delete()
                    deleted += 1
                    print(f"🗑️ Удалён чек #{check.check_id} от {check.created_at}")
                except APIError as e:
                    print(f"❌ Ошибка удаления #{check.check_id}: {e}")

        print(f"\nУдалено устаревших чеков: {deleted}")
        return deleted

    async def group_by_asset(self) -> dict:
        """Группирует активные чеки по активам."""
        all_checks = await self.cp.get_checks(status=CheckStatus.ACTIVE)
        groups = {}
        for check in all_checks:
            groups.setdefault(check.asset, []).append(check)
        return groups

    async def total_active_amount(self) -> dict[str, float]:
        """Считает сумму активных чеков по каждому активу."""
        all_checks = await self.cp.get_checks(status=CheckStatus.ACTIVE)
        totals = {}
        for check in all_checks:
            amount = float(check.amount)
            totals[check.asset] = totals.get(check.asset, 0) + amount
        return totals


async def main():
    cp = CryptoPay(token="YOUR_TOKEN")
    dashboard = CheckDashboard(cp)

    await dashboard.show_stats()

    print("\n--- Группировка по активам ---")
    groups = await dashboard.group_by_asset()
    for asset, checks in groups.items():
        total = sum(float(c.amount) for c in checks)
        print(f"{asset}: {len(checks)} чеков на сумму {total:.2f}")

    print("\n--- Суммы по активам ---")
    totals = await dashboard.total_active_amount()
    for asset, total in totals.items():
        print(f"{asset}: {total:.4f}")

    # Удаляем чеки старше 60 дней
    # await dashboard.delete_old_checks(days=60)


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

💡 Что делает этот дашборд?

Мы создали класс CheckDashboard, который показывает полную статистику: сколько всего чеков, сколько активных/активированных, группировку по активам. Метод delete_old_checks() чистит устаревшие чеки (полезно для автоматического поддержания порядка). Ты можешь легко расширить этот дашборд — добавить экспорт в CSV, отправку отчёта в Telegram и т.д.

⚡

Массовые операции с чеками

Комбинируя get_checks() с delete_check() и update_check(), ты можешь выполнять массовые операции. Например: удалить все активные чеки в TON, обновить все чеки в USDT или получить чеки по конкретному пользователю.

Python · Массовое обновление чеков по фильтру
from aiosend import CryptoPay, CheckStatus

async def bulk_update_checks(
    cp: CryptoPay,
    asset: str | None = None,
    max_count: int = 100,
) -> list:
    """Обновляет (перевыпускает) активные чеки по фильтру."""
    checks = await cp.get_checks(
        asset=asset,
        status=CheckStatus.ACTIVE,
        count=max_count,
    )
    updated = []
    for check in checks:
        new_check = await check.update()
        updated.append(new_check)
        print(f"Чек #{check.check_id} → #{new_check.check_id} (обновлён)")
    return updated

async def bulk_delete_checks(
    cp: CryptoPay,
    asset: str | None = None,
    status: str = "active",
) -> int:
    """Удаляет чеки по фильтру."""
    checks = await cp.get_checks(asset=asset, status=status)
    deleted = 0
    for check in checks:
        await check.delete()
        deleted += 1
    return deleted

async def main():
    cp = CryptoPay(token="YOUR_TOKEN")

    # Перевыпустить все активные USDT-чеки
    updated = await bulk_update_checks(cp, asset="USDT", max_count=50)
    print(f"Обновлено чеков: {len(updated)}")

    # Удалить все активные TON-чеки
    deleted = await bulk_delete_checks(cp, asset="TON")
    print(f"Удалено чеков: {deleted}")

    # Удалить все активированные чеки (очистка истории)
    # deleted_hist = await bulk_delete_checks(cp, status="activated")
    # print(f"Удалено из истории: {deleted_hist}")


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

⚠️ Ограничения массовых операций

API Crypto Pay не имеет выделенных методов для массовых операций. Все массовые действия нужно делать через циклы. Учитывай rate limiting: между запросами может потребоваться небольшая задержка (0.1-0.3 секунды), если чеков очень много. Рекомендуется использовать asyncio.sleep(0.1) после каждых 10-20 запросов.

⚠️

Граничные случаи и частые ошибки

Удаление активированного чека

Попытка удалить уже активированный чек через delete_check() не вызовет ошибку — API вернёт True. Однако средства, полученные при активации, не вернутся. Если ты хочешь откатить активацию чека — это невозможно средствами API. Активированный чек остаётся в истории навсегда.

Лимит на количество запросов (rate limiting)

Crypto Pay API имеет ограничение на количество запросов в минуту. При частых вызовах get_checks(), delete_check() или update_check() ты можешь получить ошибку TooManyRequestsError. Используй задержки между запросами при массовых операциях (как показано в примере выше).

Что если чек не найден?

При вызове get_check(check_id) с несуществующим ID API вернёт ошибку APIError. Всегда обрабатывай это исключение. Метод delete_check() напротив, не выбрасывает ошибку при удалении несуществующего чека — он возвращает True.

Невозможно изменить сумму или актив

Чек нельзя изменить — его можно только перевыпустить (через update_check()). Новый чек будет иметь ту же сумму и актив, что и старый. Если тебе нужно изменить сумму или актив — создай новый чек через create_check() с нужными параметрами, а старый удали.

📌

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

1️⃣
get_checks() — получение списка чеков с фильтрацией по asset, check_ids, status, offset, count. Все параметры опциональны.
2️⃣
get_check(check_id) — получение одного чека по ID. Выбрасывает APIError, если чек не найден.
3️⃣
check.update() — shortcut для update_check(). Создаёт новый чек с тем же активом и суммой, старый становится недействительным.
4️⃣
delete_check() и check.delete() — удаление чека. delete_all_checks() — удаление всех активных чеков (осторожно!).
5️⃣
CheckStatus — enum со значениями ACTIVE и ACTIVATED. Статусы — строки "active" и "activated".
6️⃣
Пагинация — offset и count. Максимум 1000 чеков за запрос. Используй цикл для получения всех.
7️⃣
Update = перевыпуск — update_check() не изменяет чек, а создаёт новый с тем же количеством и активом. Для изменения суммы или актива создавай новый чек.
🎯

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

Задание: Напиши менеджер чеков с фильтрацией и массовыми операциями

Создай класс CheckManager, который предоставляет удобный интерфейс для управления чеками. Класс должен:

  • Принимать CryptoPay в конструкторе
  • Иметь метод get_active() — возвращает только активные чеки
  • Иметь метод get_activated() — возвращает только активированные чеки
  • Иметь метод get_by_asset(asset) — фильтрация по активу
  • Иметь метод get_by_ids(ids) — получение конкретных чеков по списку ID
  • Иметь метод delete_activated() — удаление всех активированных чеков
  • Иметь метод update_all_active() — перевыпуск всех активных чеков
  • Каждый метод должен выводить подробный лог операций

Подсказка:

Python · Шаблон решения
import asyncio
from aiosend import CryptoPay, CheckStatus

class CheckManager:
    """Менеджер для управления крипто-чеками."""

    def __init__(self, cp: CryptoPay):
        self.cp = cp

    async def get_active(self):
        checks = await self.cp.get_checks(status=CheckStatus.ACTIVE)
        print(f"Активных чеков: {len(checks)}")
        return checks

    async def get_activated(self):
        checks = await self.cp.get_checks(status=CheckStatus.ACTIVATED)
        print(f"Активированных чеков: {len(checks)}")
        return checks

    async def get_by_asset(self, asset: str):
        checks = await self.cp.get_checks(asset=asset)
        print(f"Чеков в {asset}: {len(checks)}")
        return checks

    async def get_by_ids(self, ids: list[int]):
        checks = await self.cp.get_checks(check_ids=ids)
        print(f"Найдено {len(checks)} из {len(ids)} запрошенных")
        return checks

    async def delete_activated(self):
        activated = await self.get_activated()
        for check in activated:
            await check.delete()
            print(f"  Удалён чек #{check.check_id}")
        print(f"Удалено активированных: {len(activated)}")
        return len(activated)

    async def update_all_active(self):
        active = await self.get_active()
        updated = []
        for check in active:
            new_check = await check.update()
            updated.append(new_check)
            print(f"  {check.check_id} → {new_check.check_id}")
        print(f"Обновлено чеков: {len(updated)}")
        return updated


async def main():
    cp = CryptoPay(token="YOUR_TOKEN")
    manager = CheckManager(cp)

    active = await manager.get_active()
    activated = await manager.get_activated()
    usdt_checks = await manager.get_by_asset("USDT")

    # manager.update_all_active()
    # manager.delete_activated()


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

Урок 4.2: Управление чеками

10 вопросов