Урок 4.2 — Управление чеками
Научимся получать список чеков с фильтрацией, получать один чек по ID, обновлять и удалять чеки. Разберём пагинацию и статусы CheckStatus.
В предыдущем уроке мы научились создавать чеки. Но что делать, когда у тебя накопились десятки или сотни активных и активированных чеков? Нужно уметь получать их список, фильтровать, обновлять и удалять. В этом уроке мы разберём все методы управления чеками, которые предоставляет aiosend.
CheckStatus — статусы крипто-чека
Прежде чем управлять чеками, нужно понимать их статусы. Каждый чек может находиться в одном из двух состояний:
CheckStatus.ACTIVE
Чек активен и ожидает активации. Его можно активировать (получить средства), удалить или обновить. У статуса есть псевдоним CheckStatus.ACTIVE.
CheckStatus.ACTIVATED
Чек уже активирован (средства получены). Такой чек нельзя активировать повторно. Он остаётся в истории для аудита. Псевдоним — CheckStatus.ACTIVATED.
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() возвращает список крипто-чеков с возможностью фильтрации по различным параметрам. Это основной способ получить историю всех чеков твоего приложения.
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 на максимальное количество).
| Параметр | Тип | Описание |
|---|---|---|
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. |
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 на каждой итерации.
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.
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 (он берётся из самого объекта).
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)
# То же самое через клиент напрямую
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) — удаление одного чека
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). Это удобно, когда у тебя есть объект чека, но нет прямого доступа к клиенту.
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() удаляет все активные чеки приложения. Это мощная операция — используй её с осторожностью. Активированные чеки этот метод не затрагивает.
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().
Практический пример: дашборд управления чеками
Давай соберём всё вместе и напишем функцию — дашборд для управления чеками. Она будет показывать статистику по всем чекам, позволять удалять просроченные и группировать по активам.
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 или получить чеки по конкретному пользователю.
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() с нужными параметрами, а старый удали.
Что важно запомнить
Практическое задание
Задание: Напиши менеджер чеков с фильтрацией и массовыми операциями
Создай класс CheckManager, который предоставляет удобный интерфейс для управления чеками. Класс должен:
- Принимать
CryptoPayв конструкторе - Иметь метод
get_active()— возвращает только активные чеки - Иметь метод
get_activated()— возвращает только активированные чеки - Иметь метод
get_by_asset(asset)— фильтрация по активу - Иметь метод
get_by_ids(ids)— получение конкретных чеков по списку ID - Иметь метод
delete_activated()— удаление всех активированных чеков - Иметь метод
update_all_active()— перевыпуск всех активных чеков - Каждый метод должен выводить подробный лог операций
Подсказка:
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 вопросов