Урок 4.1 — Создание чеков
Научимся создавать крипто-чеки через aiosend: разберём метод create_check(), все его параметры, типы чеков, публичные и привязанные чеки, активацию и полный цикл работы.
Крипто-чек (Check) — это специальный инструмент Crypto Pay, который позволяет отправить фиксированную сумму криптовалюты другому пользователю. Получатель активирует чек и средства зачисляются на его баланс. В отличие от инвойсов (запрос на оплату), чек — это предложение средств. В этом уроке мы научимся создавать чеки, настраивать их параметры и управлять их статусом.
Что такое крипто-чек в Crypto Pay
Крипто-чек — это предоплаченный ваучер на определённую сумму криптовалюты. Ты создаёшь чек, резервируя средства со своего баланса, и отправляешь ссылку получателю. Получатель активирует чек и получает средства на свой баланс в @CryptoBot.
Чеки бывают двух типов:
🌐 Публичный чек
- Может активировать ЛЮБОЙ пользователь
- Параметры
pin_to_user_idиpin_to_usernameне указаны - Подходит для розыгрышей, бонусов, конкурсов
- Кто первый активировал — тому и достались средства
📌 Привязанный чек
- Может активировать ТОЛЬКО указанный пользователь
- Привязка по
pin_to_user_id(Telegram ID) илиpin_to_username - Подходит для выплат, возвратов, наград конкретному пользователю
- Никто другой не сможет забрать чек
Схема работы с чеками:
1. Включи функцию createCheck в настройках приложения Crypto Pay
2. Создай чек через await cp.create_check(amount=10, asset="USDT")
3. Получи объект Check со ссылкой и хешем
4. Отправь ссылку bot_check_url получателю
5. Получатель активирует чек в @CryptoBot
6. Средства списываются с твоего баланса и зачисляются получателю
Важное отличие от инвойсов: при создании чека средства сразу резервируются на твоём балансе. Ты не сможешь потратить эти деньги, пока чек не будет активирован или отменён. Поэтому убедись, что на балансе достаточно средств.
Включение createCheck в настройках
Прежде чем создавать чеки через API, нужно включить соответствующее разрешение в настройках твоего приложения Crypto Pay. По умолчанию метод createCheck отключён из соображений безопасности.
Как включить:
- Открой @CryptoBot (или @CryptoTestnetBot для тестов)
- Перейди в раздел Crypto Pay
- Выбери своё приложение (или создай новое)
- Перейди в Security → Checks
- Включи флаг createCheck
- Сохрани изменения
После этого метод create_check() станет доступен. Если попытаться создать чек без включённого разрешения, API вернёт ошибку.
⚠️ Проверь перед созданием!
Перед вызовом create_check() убедись, что:
• Функция createCheck включена в Security → Checks
• На балансе твоего приложения достаточно средств
• Ты используешь правильный токен (MAINNET/TESTNET)
import asyncio
from aiosend import CryptoPay
async def check_balance(cp: CryptoPay, asset: str = "USDT"):
"""Проверяет баланс приложения перед созданием чека."""
me = await cp.get_me()
print(f"Приложение: {me.name}")
print(f"Балансы: {me.balances}")
for balance in me.balances:
if balance.currency_code == asset:
available = float(balance.available)
print(f"Доступно {asset}: {available}")
return available
print(f"Актив {asset} не найден на балансе")
return 0
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
balance = await check_balance(cp, "USDT")
if balance > 10:
print("✅ Баланса достаточно для создания чека")
else:
print("❌ Недостаточно средств")
asyncio.run(main())
Метод create_check() — создание чека
Метод create_check() — основной способ создания крипто-чеков. Он доступен у экземпляра CryptoPay и у Network.
async def create_check(
self,
amount: str,
asset: str,
pin_to_user_id: int | None = None,
pin_to_username: str | None = None,
) -> Check
Параметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
amount |
str |
✅ Да | Сумма чека в криптовалюте. Передаётся строкой для сохранения точности (например "10.50"). |
asset |
str |
✅ Да | Криптовалюта чека (например "USDT", "TON"). |
pin_to_user_id |
int | None |
Нет | Telegram ID пользователя, который сможет активировать чек. Если указан — чек привязан к этому пользователю. |
pin_to_username |
str | None |
Нет | Username получателя (без @). Чек сможет активировать только пользователь с этим username. |
Важно: amount передаётся строкой!
В отличие от create_invoice(), где amount может быть float | str, в create_check() сумма принимается только как строка. Это связано с высокой точностью криптовалютных сумм — float может дать погрешность. Передавай сумму строкой: "10.50", "0.000001".
import asyncio
from aiosend import CryptoPay
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
# Публичный чек на 10 USDT — любой может активировать
check = await cp.create_check(amount="10", asset="USDT")
print(f"Чек #{check.check_id} создан!")
print(f"Сумма: {check.amount} {check.asset}")
print(f"Статус: {check.status}")
print(f"Ссылка: {check.bot_check_url}")
print(f"Хеш: {check.hash}")
print(f"Тип: публичный (никому не привязан)")
asyncio.run(main())
Привязанные чеки (pin_to_user_id / pin_to_username)
Если ты хочешь отправить чек конкретному пользователю (например, выплату или возврат), используй параметры pin_to_user_id или pin_to_username. Такой чек сможет активировать только указанный пользователь.
pin_to_user_id
Привязывает чек к Telegram ID пользователя. Это числовой идентификатор (например, 123456789). Самый надёжный способ — ID не меняется.
pin_to_username
Привязывает чек к username (без @). Например, "ivanov". Менее надёжно — пользователь может сменить username, и чек станет недоступным.
import asyncio
from aiosend import CryptoPay
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
# 1. Чек, привязанный к Telegram ID
check_by_id = await cp.create_check(
amount="25",
asset="USDT",
pin_to_user_id=123456789, # ID получателя
)
print(f"Чек для user_id=123456789: {check_by_id.bot_check_url}")
# 2. Чек, привязанный к username
check_by_username = await cp.create_check(
amount="50",
asset="TON",
pin_to_username="ivanov", # username без @
)
print(f"Чек для @ivanov: {check_by_username.bot_check_url}")
# 3. Можно указать оба параметра (но достаточно одного)
check_both = await cp.create_check(
amount="100",
asset="USDT",
pin_to_user_id=987654321,
pin_to_username="petrov",
)
print(f"Чек для user_id или @petrov: {check_both.bot_check_url}")
asyncio.run(main())
⚠️ Что если привязанный чек не активировали?
Средства остаются зарезервированными на твоём балансе, пока чек не будет активирован. Чек нельзя отменить или удалить через API (на момент написания aiosend). Средства вернутся на баланс только после активации чека получателем. Учитывай это при планировании выплат.
Тип Check — все поля объекта
Метод create_check() возвращает объект Check (Pydantic-модель). Вот все его поля:
| Поле | Тип | Описание |
|---|---|---|
check_id |
int |
Уникальный ID чека в системе Crypto Pay |
amount |
str |
Сумма чека (строка для сохранения точности) |
asset |
str |
Криптовалюта чека (например "USDT") |
status |
str |
Статус чека: "active" (активен) или "activated" (активирован) |
hash |
str |
Уникальный хеш чека. Используется в ссылке для активации |
bot_check_url |
str |
Ссылка на чек в @CryptoBot. Отправь её получателю |
created_at |
str |
Дата создания чека в формате ISO 8601 |
activated_at |
str | None |
Дата активации чека (если активирован) |
pin_to_user_id |
int | None |
Telegram ID пользователя, к которому привязан чек (если есть) |
pin_to_username |
str | None |
Username, к которому привязан чек (если есть) |
import asyncio
from aiosend import CryptoPay
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
check = await cp.create_check(amount="15", asset="USDT")
print(f"ID чека: {check.check_id}")
print(f"Сумма: {check.amount} {check.asset}")
print(f"Статус: {check.status}")
print(f"Хеш: {check.hash}")
print(f"Ссылка: {check.bot_check_url}")
print(f"Создан: {check.created_at}")
print(f"Активирован: {check.activated_at or '—'}")
print(f"Привязан к ID: {check.pin_to_user_id or '—'}")
print(f"Привязан к @: {check.pin_to_username or '—'}")
asyncio.run(main())
Сравнение: публичный vs привязанный чек
| Характеристика | Публичный | Привязанный (pin) |
|---|---|---|
| Кто может активировать | Любой пользователь | Только указанный пользователь |
| Параметры привязки | pin_to_user_id=None |
pin_to_user_id=123 или pin_to_username="ivan" |
| Сценарий использования | Розыгрыши, бонусы, конкурсы, крауддропы | Выплаты, возвраты, зарплата, награды |
| Риски | Кто угодно может забрать (нужно быстро отправить ссылку) | При смене username чек становится недоступным |
| Рекомендация | Для массовых раздач и промо-акций | Для адресных выплат конкретным пользователям |
import asyncio
from aiosend import CryptoPay
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
# Публичный чек — любой может активировать
public = await cp.create_check(amount="5", asset="USDT")
print(f"Публичный: {public.bot_check_url}")
print(f" pin_to_user_id: {public.pin_to_user_id}")
print(f" pin_to_username: {public.pin_to_username}")
# → pin_to_user_id = None, pin_to_username = None
# Привязанный чек — только для user_id=123456
pinned = await cp.create_check(
amount="100",
asset="USDT",
pin_to_user_id=123456,
)
print(f"Привязанный: {pinned.bot_check_url}")
print(f" pin_to_user_id: {pinned.pin_to_user_id}")
# → pin_to_user_id = 123456
# Привязанный по username
pinned_u = await cp.create_check(
amount="25",
asset="TON",
pin_to_username="crypto_user",
)
print(f"Привязанный к @: {pinned_u.bot_check_url}")
print(f" pin_to_username: {pinned_u.pin_to_username}")
# → pin_to_username = "crypto_user"
asyncio.run(main())
Активация чека получателем
После создания чека ты отправляешь получателю ссылку bot_check_url. Процесс активации выглядит так:
- Получатель открывает ссылку
t.me/CryptoBot?start=check_XXXX - @CryptoBot показывает информацию о чеке: сумма, валюта, отправитель
- Получатель нажимает «Активировать»
- Средства списываются с баланса отправителя и зачисляются получателю
- Статус чека меняется с
"active"на"activated" - В поле
activated_atпоявляется дата активации
Ты можешь отслеживать статус чека через метод get_checks(), который мы подробно рассмотрим в следующем уроке.
import asyncio
from aiosend import CryptoPay
async def monitor_check(cp: CryptoPay, check_id: int, timeout: int = 300):
"""Ожидает активации чека в течение timeout секунд."""
import time
start = time.time()
while time.time() - start < timeout:
checks = await cp.get_checks(check_ids=[check_id])
if checks:
check = checks[0]
if check.status == "activated":
print(f"✅ Чек #{check_id} активирован!")
print(f" Активирован в: {check.activated_at}")
return check
elif check.status == "active":
print(f"⏳ Чек ещё активен, ждём...")
await asyncio.sleep(2)
print(f"❌ Таймаут ожидания активации чека #{check_id}")
return None
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
check = await cp.create_check(amount="10", asset="USDT")
print(f"Отправьте ссылку получателю: {check.bot_check_url}")
# Ждём активации (до 5 минут)
activated = await monitor_check(cp, check.check_id)
asyncio.run(main())
💡 Разница с инвойсами
В инвойсах ты получаешь средства от покупателя. В чеках ты отдаёшь средства получателю. Чеки всегда создаются с твоего баланса. Комиссия за создание чека обычно ниже, чем комиссия за перевод (transfer).
Практические примеры
Пример 1: Массовая раздача (airdrop)
Создаём несколько публичных чеков для розыгрыша среди подписчиков.
import asyncio
from aiosend import CryptoPay
async def create_airdrop(cp: CryptoPay, total_amount: str, asset: str, count: int):
"""
Создаёт несколько чеков на равные суммы для розыгрыша.
total_amount — общая сумма на все чеки.
"""
total = float(total_amount)
per_check = str(round(total / count, 8))
checks = []
for i in range(count):
check = await cp.create_check(
amount=per_check,
asset=asset,
)
checks.append(check)
print(f"Чек #{i+1}: {check.bot_check_url}")
print(f"\nСоздано {len(checks)} чеков по {per_check} {asset}")
print(f"Общая сумма: {total_amount} {asset}")
return checks
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
# Раздаём 100 USDT: 10 чеков по 10 USDT
await create_airdrop(cp, "100", "USDT", 10)
asyncio.run(main())
Пример 2: Выплата пользователю
Отправляем привязанный чек конкретному пользователю по его Telegram ID.
import asyncio
from aiosend import CryptoPay
async def payout(cp: CryptoPay, user_id: int, amount: str, asset: str):
"""Создаёт привязанный чек для выплаты пользователю."""
check = await cp.create_check(
amount=amount,
asset=asset,
pin_to_user_id=user_id,
)
result = {
"check_id": check.check_id,
"amount": check.amount,
"asset": check.asset,
"status": check.status,
"url": check.bot_check_url,
"user_id": check.pin_to_user_id,
"created_at": check.created_at,
}
print(f"💸 Выплата {amount} {asset} пользователю #{user_id}")
print(f" Чек: {check.bot_check_url}")
print(f" Статус: {check.status}")
return result
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
# Выплата бонуса пользователю с ID 123456789
await payout(
cp=cp,
user_id=123456789,
amount="50",
asset="USDT",
)
asyncio.run(main())
Пример 3: Система бонусов с проверкой баланса
Проверяем баланс, создаём чек, логируем результат.
import asyncio
import json
from aiosend import CryptoPay
from aiosend.exceptions import APIError, MethodValuesError
class BonusSystem:
"""Система бонусов через крипто-чеки."""
def __init__(self, token: str):
self.cp = CryptoPay(token=token)
async def get_asset_balance(self, asset: str) -> float:
"""Возвращает доступный баланс по активу."""
me = await self.cp.get_me()
for balance in me.balances:
if balance.currency_code == asset:
return float(balance.available)
return 0.0
async def send_bonus(
self,
user_id: int,
amount: str,
asset: str,
) -> dict:
"""Отправляет бонус пользователю. Проверяет баланс."""
# 1. Проверка баланса
balance = await self.get_asset_balance(asset)
if balance < float(amount):
return {
"success": False,
"error": f"Недостаточно {asset}. Доступно: {balance}",
}
# 2. Создание чека
try:
check = await self.cp.create_check(
amount=amount,
asset=asset,
pin_to_user_id=user_id,
)
result = {
"success": True,
"check_id": check.check_id,
"amount": check.amount,
"asset": check.asset,
"status": check.status,
"url": check.bot_check_url,
"user_id": check.pin_to_user_id,
"created_at": check.created_at,
"balance_after": await self.get_asset_balance(asset),
}
return result
except MethodValuesError as e:
return {"success": False, "error": f"Ошибка параметров: {e}"}
except APIError as e:
return {"success": False, "error": f"Ошибка API: {e}"}
# Использование
async def main():
bonus = BonusSystem("YOUR_TOKEN")
result = await bonus.send_bonus(
user_id=123456789,
amount="25",
asset="USDT",
)
print(json.dumps(result, indent=2, ensure_ascii=False))
asyncio.run(main())
Граничные случаи и ограничения
Недостаточно средств на балансе
При создании чека средства резервируются с твоего баланса. Если на балансе недостаточно средств, API вернёт ошибку. Всегда проверяй баланс перед созданием чека, особенно если сумма большая.
createCheck не включён
Если функция createCheck отключена в настройках приложения (Security → Checks), API вернёт ошибку при попытке создания чека. Включи её через @CryptoBot перед использованием.
Смена username после привязки
Если ты привязал чек к username (pin_to_username), а пользователь сменил свой username, чек станет недоступным для активации. Рекомендуется использовать pin_to_user_id — Telegram ID не меняется.
Минимальная сумма чека
Каждый актив имеет минимальную сумму чека. Например, для USDT это обычно 1 USDT, для TON — 0.1 TON. Точные лимиты можно получить через get_currencies(). При попытке создать чек на меньшую сумму API вернёт ошибку.
Чек нельзя отменить
На данный момент API Crypto Pay не предоставляет метод для отмены или удаления чека. После создания чек будет висеть в статусе active до активации (или «навсегда»). Средства заблокированы на балансе до активации. Учитывай это при создании чеков.
Что важно запомнить
amount только строка! Обязательные параметры: amount и asset.pin_to_user_id или pin_to_username).Практическое задание
Задание: Система бонусов для подписчиков
Создай класс BonusDistributor, который:
- Принимает список Telegram ID и сумму бонуса для каждого
- Проверяет баланс перед созданием всех чеков
- Создаёт привязанные чеки для каждого пользователя
- Ведёт лог: успешные и неуспешные создания
- Возвращает словарь с результатами: user_id → check_url или ошибка
- Обрабатывает исключения для каждого пользователя отдельно
- Подсчитывает итоговую сумму и комиссию
Подсказка:
import asyncio
import json
from aiosend import CryptoPay
from aiosend.exceptions import APIError, MethodValuesError
class BonusDistributor:
"""Распределитель бонусов для списка пользователей."""
def __init__(self, token: str):
self.cp = CryptoPay(token=token)
async def distribute(
self,
user_ids: list[int],
amount: str,
asset: str,
) -> dict:
"""
Создаёт привязанные чеки для каждого пользователя.
Возвращает словарь с результатами.
"""
# 1. Проверка баланса
me = await self.cp.get_me()
balances = {b.currency_code: float(b.available) for b in me.balances}
total_needed = float(amount) * len(user_ids)
available = balances.get(asset, 0)
if available < total_needed:
return {
"success": False,
"error": (
f"Недостаточно {asset}. "
f"Нужно: {total_needed}, доступно: {available}"
),
}
# 2. Создание чеков
results = {}
success_count = 0
fail_count = 0
for uid in user_ids:
try:
check = await self.cp.create_check(
amount=amount,
asset=asset,
pin_to_user_id=uid,
)
results[str(uid)] = {
"success": True,
"check_id": check.check_id,
"url": check.bot_check_url,
"status": check.status,
}
success_count += 1
except (APIError, MethodValuesError) as e:
results[str(uid)] = {
"success": False,
"error": str(e),
}
fail_count += 1
# 3. Итог
summary = {
"success": True,
"asset": asset,
"amount_per_user": amount,
"total_users": len(user_ids),
"success_count": success_count,
"fail_count": fail_count,
"total_amount": str(total_needed),
"results": results,
}
print(json.dumps(summary, indent=2, ensure_ascii=False))
return summary
# Использование
async def main():
distributor = BonusDistributor("YOUR_TOKEN")
# Раздаём бонусы по 10 USDT трем пользователям
await distributor.distribute(
user_ids=[111111, 222222, 333333],
amount="10",
asset="USDT",
)
asyncio.run(main())
Метод get_checks() — получение списка чеков
Для получения информации о созданных чеках используется метод get_checks(). Он позволяет фильтровать чеки по статусу, активам и ID. Мы подробно рассмотрим его в следующем уроке, но базовое использование полезно знать уже сейчас.
async def get_checks(
self,
asset: str | None = None,
check_ids: list[int] | None = None,
status: str | None = None,
offset: int = 0,
count: int = 100,
) -> list[Check]
import asyncio
from aiosend import CryptoPay, CheckStatus
async def main():
cp = CryptoPay(token="YOUR_TOKEN")
check = await cp.create_check(amount="10", asset="USDT")
print(f"Чек #{check.check_id}: статус={check.status}")
checks = await cp.get_checks(check_ids=[check.check_id])
if checks:
c = checks[0]
print(f"Получен чек #{c.check_id}: {c.amount} {c.asset}, статус: {c.status}")
active = await cp.get_checks(status=CheckStatus.ACTIVE)
print(f"Активных чеков: {len(active)}")
activated = await cp.get_checks(status=CheckStatus.ACTIVATED)
print(f"Активированных: {len(activated)}")
usdt_checks = await cp.get_checks(asset="USDT")
print(f"Чеков в USDT: {len(usdt_checks)}")
asyncio.run(main())
Статусы чеков (enum CheckStatus)
Чек может находиться в одном из двух статусов: CheckStatus.ACTIVE (ожидает активации) или CheckStatus.ACTIVATED (активирован). В отличие от инвойсов, у чеков нет статуса EXPIRED или PAID.
Безопасность при работе с чеками
Чеки оперируют реальными средствами. Соблюдай несколько правил безопасности:
Не передавай bot_check_url третьим лицам
Ссылка на чек — это ваучер. Любой может активировать публичный чек. Для адресных выплат используй pin_to_user_id.
Проверяй баланс перед созданием
Создание чека резервирует средства. Проверяй баланс перед массовыми операциями.
Тестируй в TESTNET перед MAINNET
Используй тестовую сеть для отладки. Когда логика отлажена — смени токен на MAINNET.
Логируй все операции
Веди журнал всех созданных чеков: ID, сумма, актив, получатель, статус. Это поможет при аудите.
import asyncio
import logging
from aiosend import CryptoPay
from aiosend.exceptions import APIError, MethodValuesError
logger = logging.getLogger("check_safe")
async def create_check_safe(
cp: CryptoPay,
amount: str,
asset: str,
pin_to_user_id: int | None = None,
) -> dict:
result = {"success": False, "check_id": None, "url": None, "error": None}
try:
amount_float = float(amount)
if amount_float <= 0:
result["error"] = "Сумма должна быть положительной"
return result
except ValueError:
result["error"] = "Некорректный формат суммы"
return result
try:
me = await cp.get_me()
balances = {b.currency_code: float(b.available) for b in me.balances}
available = balances.get(asset, 0)
if available < amount_float:
result["error"] = f"Недостаточно {asset}: нужно {amount}, доступно {available}"
return result
except APIError as e:
result["error"] = f"Ошибка проверки баланса: {e}"
return result
try:
kwargs = {"amount": amount, "asset": asset}
if pin_to_user_id is not None:
kwargs["pin_to_user_id"] = pin_to_user_id
check = await cp.create_check(**kwargs)
result.update({
"success": True, "check_id": check.check_id, "url": check.bot_check_url,
"amount": check.amount, "asset": check.asset, "status": check.status,
})
logger.info(f"Чек создан: #{check.check_id} {check.amount} {check.asset}")
except MethodValuesError as e:
result["error"] = f"Ошибка параметров: {e}"
except APIError as e:
result["error"] = f"Ошибка API: {e}"
return result
Урок 4.1: Создание чеков
5 вопросов