Прийом платежів USDT у Telegram-боті
Telegram-бот може продавати доступ, підписки чи цифрові товари за USDT без акаунта в платіжному провайдері. Боту потрібні дві речі: показати платнику адресу і мати публічну URL для підтвердження. Цей гайд використовує Python, aiogram 3 і aiohttp.
Схема
- Користувач надсилає
/pay. - Бот створює в ChainPay адресу для депозиту з унікальним
externalReferenceі показує її. - Користувач надсилає USDT.
- Після підтвердження ChainPay переказує кошти на ваш гаманець і викликає вебхук бота; бот пише користувачу.
Реєстрація й API-ключ не потрібні. Ваш гаманець — це forwardAddress.
Бот
import asyncio
import hmac
import os
import uuid
import aiohttp
from aiohttp import web
from aiogram import Bot, Dispatcher
from aiogram.filters import Command
from aiogram.types import Message
API = "https://api.chainpay.site"
SECRET = os.environ["WEBHOOK_SECRET"]
WALLET = os.environ["FORWARD_ADDRESS"]
PUBLIC_URL = os.environ["PUBLIC_URL"] # e.g. https://bot.example.com
bot = Bot(os.environ["BOT_TOKEN"])
dp = Dispatcher()
orders: dict[str, int] = {} # externalReference -> Telegram user id (use a database in production)
seen: set[str] = set() # processed "txHash:status" pairs
@dp.message(Command("pay"))
async def pay(message: Message):
ref = f"tg-{message.from_user.id}-{uuid.uuid4().hex[:8]}"
orders[ref] = message.from_user.id
async with aiohttp.ClientSession() as session:
async with session.post(f"{API}/deposit-address", json={
"ticker": "trc20/usdt",
"callbackUrl": f"{PUBLIC_URL}/crypto-webhook?token={SECRET}",
"forwardAddress": WALLET,
"externalReference": ref,
}) as resp:
data = await resp.json()
await message.answer(
f"Send USDT (TRC20) to:\n<code>{data['address']}</code>", parse_mode="HTML"
)
async def crypto_webhook(request: web.Request):
if not hmac.compare_digest(request.query.get("token", ""), SECRET):
return web.Response(status=403)
event = await request.json()
key = f"{event['txHash']}:{event['status']}"
if event["status"] == "confirmed" and key not in seen:
seen.add(key)
user_id = orders.get(event["externalReference"])
if user_id:
await bot.send_message(
user_id, f"Received {event['amount']} {event['tokenSymbol']}. Thank you!"
)
return web.Response(text="ok")
async def main():
app = web.Application()
app.router.add_post("/crypto-webhook", crypto_webhook)
runner = web.AppRunner(app)
await runner.setup()
await web.TCPSite(runner, "0.0.0.0", 8080).start()
await dp.start_polling(bot)
asyncio.run(main())
Запускайте за HTTPS (зворотний проксі, наприклад Caddy або nginx): ChainPay відхиляє callbackUrl, що вказують на localhost чи приватну IP, тож для локальних тестів потрібен тунель із публічною адресою.
Що доробити перед продакшеном
- Зберігайте
ordersіseenу базі. У прикладі вони губляться при перезапуску. - Одна адреса на замовлення (як вище) — простіша звірка; одна на користувача (
externalReference = tg-<user id>) — для поповнень і балансів. - Задайте мінімальний платіж. У TRON фіксована частина комісії — близько 4 USDT за поточних цін, див. Pricing. У BSC використовуйте
bep20/usdt, там вона нижча. - Якщо вебхук запізнюється, опитуйте
GET /deposit-address/:id/status.