Перейти к содержимому
Разделы документации

Документация / Рецепты

Оплата в Telegram-боте

Готовый флоу для бота: платёж, кнопка, вебхук, выдача товара

Типовой сценарий интеграции TabPay - продажи в телеграм-боте: цифровые товары, подписки, доступы. Интеграция состоит из двух шагов: бот создаёт платёж и отправляет покупателю кнопку со ссылкой на оплату, а после оплаты получает вебхук и выдаёт товар. Поле telegramId избавляет от необходимости вести собственную таблицу соответствий «заказ - пользователь»: идентификатор покупателя возвращается в уведомлении об оплате.

Понадобится API-ключ активного магазина (раздел Аутентификация) и сохранённый Webhook URL в настройках магазина.

Шаг 1. Кнопка «Купить» создаёт платёж

По нажатию кнопки бот создаёт платёж одним POST-запросом и отправляет пользователю кнопку со ссылкой на payUrl. На платёжной странице покупатель выбирает СБП или карту.

import aiohttp
from aiogram import Bot, Dispatcher, F
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup

API_KEY = "tp_ваш_ключ"  # API-ключ магазина из кабинета
bot = Bot("ТОКЕН_БОТА")
dp = Dispatcher()


@dp.callback_query(F.data == "buy")
async def buy(callback: CallbackQuery):
    # Создаём платёж и передаём telegramId покупателя -
    # он вернётся в вебхуке, и бот сразу поймёт, кому выдавать товар
    async with aiohttp.ClientSession() as session:
        response = await session.post(
            "https://tabpay.org/api/v1/payments",
            headers={"X-Api-Key": API_KEY},
            json={
                "amountKopecks": 19900,
                "orderId": f"tg-{callback.id}",
                "description": "Подписка на месяц",
                "telegramId": callback.from_user.id,
            },
        )
        payment = await response.json()

    keyboard = InlineKeyboardMarkup(inline_keyboard=[[
        InlineKeyboardButton(text="Оплатить 199,00 ₽", url=payment["payUrl"])
    ]])
    await callback.message.answer("Счёт готов - оплатите по кнопке:", reply_markup=keyboard)
    await callback.answer()

Полное описание полей - в разделе Создание платежа. orderId должен быть уникальным в рамках магазина - в примерах используется идентификатор колбэка.

Шаг 2. Вебхук выдаёт товар

После оплаты TabPay отправляет POST-запрос на Webhook URL магазина; тело уведомления содержит telegramId покупателя:

Тело вебхука
{
  "id": "6b9d2c88-4f1a-4c0e-9b7d-2f8e5a1c3d90",
  "orderId": "tg-4382920412471814230",
  "status": "SUCCESS",
  "amountKopecks": 19900,
  "telegramId": "987654321"
}

Обработчик проверяет подпись X-Signature (HMAC-SHA256 от сырого тела секретом магазина, подробнее - в разделе Проверка подписи) и выдаёт товар:

import hashlib
import hmac
import json

from aiohttp import web

WEBHOOK_SECRET = "секрет_подписи_из_кабинета"


async def tabpay_webhook(request: web.Request):
    # Подпись считается от СЫРОГО тела - читаем байты до разбора JSON
    raw = await request.read()
    expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
    received = request.headers.get("X-Signature", "")
    if not hmac.compare_digest(expected, received):
        return web.Response(status=400)

    event = json.loads(raw)

    # Тестовое уведомление из кабинета: подтверждаем, но товар не выдаём
    if event.get("test"):
        return web.Response(status=200)

    if event["status"] == "SUCCESS" and event["telegramId"]:
        # Идемпотентность: пометьте event["id"] обработанным,
        # повторный вебхук с тем же id - просто отвечайте 200
        await bot.send_message(
            int(event["telegramId"]),
            "Оплата получена - выдаём товар.",
        )
    return web.Response(status=200)


app = web.Application()
app.router.add_post("/tabpay/webhook", tabpay_webhook)
web.run_app(app, port=8080)

Проверка интеграции без платежа

В настройках магазина на вкладке Webhook доступна кнопка «Отправить тестовый вебхук»: она отправляет на ваш URL уведомление, идентичное боевому, - с корректной подписью, но с дополнительным полем test: true. Обработчик должен ответить 200 и не выдавать товар - как в примерах выше. Это позволяет проверить подпись и доставку уведомлений без проведения реального платежа.

На что обратить внимание

  • Выдавайте товар только при status: "SUCCESS" - другие статусы (справочник) означают, что деньги не получены.
  • Вебхук может прийти повторно (доставка повторяется до ответа 2xx) - помечайте платёж по id обработанным и не выдавайте товар дважды.
  • telegramId возвращается строкой - при необходимости приведите его к числу перед отправкой сообщения.
  • Если уведомление не было получено (например, сервер бота был недоступен), статус платежа можно сверить запросом Получение платежа.

Если интеграцию выполняет ИИ-ассистент, передайте ему материалы со страницы ИИ и инструменты - готовый промпт и llms.txt включают этот рецепт.