Платежи
Invoices и Payments
Полное руководство по приему платежей в Telegram-ботах на PHP. Разбираем методы sendInvoice, pre_checkout_query и успешную оплату с валидацией payload через HMAC.
Telegram Bot API предоставляет полноценный встроенный механизм для приема платежей. Бот может продавать физические товары, цифровой контент или подписки напрямую в интерфейсе мессенджера. Пользователь вводит данные карты один раз и в дальнейшем платит в один клик через Apple Pay, Google Pay или встроенные платежные шлюзы.
Процесс оплаты состоит из трех шагов:
- Бот отправляет пользователю счет (инвойс) с помощью метода
sendInvoice. - Пользователь нажимает кнопку оплаты. Telegram присылает боту запрос
pre_checkout_query. У бота есть ровно 10 секунд, чтобы подтвердить наличие товара и готовность провести сделку. - После успешного списания средств Telegram присылает обновление с объектом
successful_payment. Это финальный триггер, по которому бот выдает товар.
Шаг 0: Получение токена провайдера
Telegram сам не обрабатывает деньги. Он выступает безопасной витриной между вашим ботом, пользователем и платежным шлюзом (Stripe, YooKassa, CloudPayments, PayMaster и др.). Чтобы принимать реальные платежи, вам нужно подключить платежного провайдера:
- Откройте бот @BotFather.
- Отправьте команду
/mybotsи выберите вашего бота. - Перейдите в раздел Bot Settings → Payments.
- Выберите подходящего провайдера и следуйте инструкциям для авторизации.
- В результате вы получите
provider_token. Для разработки и тестирования всегда выбирайте тестовый режим (в названии токена будет словоTEST).
Шаг 1: Отправка инвойса (sendInvoice)
Инвойс отправляется как специальный тип сообщения. Важнейший параметр здесь — payload. Это скрытая строка (до 128 байт), которую Telegram вернет вам на этапе проверки и после оплаты. Никогда не передавайте туда просто ID товара. Пользователь может перехватить или подделать запрос. Всегда подписывайте payload с помощью HMAC-SHA256, используя секретную соль вашего сервера.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Здесь только вызываем её — реализацию не копируем.
<?php
// Токен платежного провайдера (тестовый или боевой)
$providerToken = getenv('TELEGRAM_PROVIDER_TOKEN');
$chatId = 987654321; // ID чата с пользователем
$userId = 987654321; // ID пользователя
$itemId = 'premium_sub_30';
// Формируем безопасный payload с подписью
$secretKey = getenv('PAYLOAD_SECRET'); // Секретный ключ вашего сервера
$hmac = hash_hmac('sha256', "{$userId}:{$itemId}", $secretKey);
$payload = json_encode([
'user_id' => $userId,
'item_id' => $itemId,
'hmac' => $hmac
]);
$response = telegramApi('sendInvoice', [
'chat_id' => $chatId,
'title' => 'Подписка Premium',
'description' => 'Доступ к закрытым функциям бота на 30 дней',
'payload' => $payload,
'provider_token' => $providerToken,
'currency' => 'USD',
'prices' => [
[
'label' => 'Premium 30 дней',
'amount' => 499, // Сумма в минимальных единицах валюты (4.99 USD)
]
],
'start_parameter' => 'premium-30-days', // Параметр для deep-linking
'need_email' => true, // Запросить email при оплате
'send_email_to_provider' => true // Передать email провайдеру для чека
]);
// Посмотреть результат в терминале или логах
file_put_contents('php://stdout', print_r($response, true));
Пример успешного ответа API на метод sendInvoice:
{
"ok": true,
"result": {
"message_id": 4501,
"from": {
"id": 12345678,
"is_bot": true,
"first_name": "My Shop Bot",
"username": "my_shop_bot"
},
"chat": {
"id": 987654321,
"first_name": "Alex",
"type": "private"
},
"date": 1700000000,
"invoice": {
"title": "Подписка Premium",
"description": "Доступ к закрытым функциям бота на 30 дней",
"start_parameter": "premium-30-days",
"currency": "USD",
"total_amount": 499
}
}
}
Поле ok подтверждает, что сообщение-инвойс успешно доставлено пользователю. Внутри result содержится стандартный объект Message, но с дополнительным полем invoice. Сохраните message_id, если в будущем вам потребуется удалить инвойс, если пользователь передумал.
Шаг 2: Проверка готовности (pre_checkout_query)
Когда пользователь вводит данные карты и нажимает финальную кнопку оплаты, Telegram отправляет вашему боту обновление типа pre_checkout_query. На этом этапе транзакция еще не проведена. Вы обязаны проверить:
- Действителен ли
payload(сходится ли HMAC-подпись). - Есть ли товар в наличии на складе.
- Не изменилась ли цена.
У вас есть ровно 10 секунд, чтобы вызвать метод answerPreCheckoutQuery. Если вы не ответите вовремя или ответите ok: false, Telegram отменит транзакцию и покажет пользователю ошибку.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Здесь только вызываем её — реализацию не копируем.
<?php
// Пример обработки входящего вебхука (см. главу "Webhooks")
$input = file_get_contents('php://input');
$update = json_decode($input, true);
if (isset($update['pre_checkout_query'])) {
$query = $update['pre_checkout_query'];
$queryId = $query['id'];
$payload = json_decode($query['invoice_payload'], true);
// 1. Проверяем подпись payload
$secretKey = getenv('PAYLOAD_SECRET');
$expectedHmac = hash_hmac('sha256', "{$payload['user_id']}:{$payload['item_id']}", $secretKey);
$isValid = hash_equals($expectedHmac, $payload['hmac'] ?? '');
// 2. Проверяем наличие товара в БД (условная функция)
$isAvailable = ($payload['item_id'] === 'premium_sub_30');
if ($isValid && $isAvailable) {
// Разрешаем платеж
telegramApi('answerPreCheckoutQuery', [
'pre_checkout_query_id' => $queryId,
'ok' => true
]);
} else {
// Отклоняем платеж с объяснением причины
telegramApi(&039;answerPreCheckoutQuery', [
'pre_checkout_query_id' => $queryId,
'ok' => false,
'error_message' => 'Извините, этот товар сейчас недоступен или сессия оплаты устарела.'
]);
}
exit;
}
Пример входящего обновления pre_checkout_query, которое придет на ваш вебхук:
{
"update_id": 88442211,
"pre_checkout_query": {
"id": "123456789012345",
"from": {
"id": 987654321,
"is_bot": false,
"first_name": "Alex",
"username": "alex_username"
},
"currency": "USD",
"total_amount": 499,
"invoice_payload": "{\"user_id\":987654321,\"item_id\":\"premium_sub_30\",\"hmac\":\"a1b2c3d4...\"}",
"order_info": {
"email": "alex@example.com"
}
}
}
Обратите внимание на поле invoice_payload. Оно приходит в виде строки, поэтому мы обязательно декодируем его через json_decode($query['invoice_payload'], true) перед валидацией подписи.
Шаг 3: Фиксация успешной оплаты (successful_payment)
Если шаг 2 прошел успешно, платежная система списывает деньги. Сразу после этого Telegram отправляет вашему боту стандартное сообщение (объект Message), содержащее поле successful_payment. Это финальная точка: деньги списаны, вы обязаны выдать товар пользователю.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Здесь только вызываем её — реализацию не копируем.
<?php
// Продолжение обработки вебхука (см. главу "Webhooks")
if (isset($update['message']['successful_payment'])) {
$payment = $update['message']['successful_payment'];
$chatId = $update['message']['chat']['id'];
$payload = json_decode($payment['invoice_payload'], true);
// Повторно проверяем HMAC для защиты от аномалий
$secretKey = getenv('PAYLOAD_SECRET');
$expectedHmac = hash_hmac('sha256', "{$payload['user_id']}:{$payload['item_id']}", $secretKey);
if (hash_equals($expectedHmac, $payload['hmac'] ?? '')) {
// Начисляем услугу в базе данных
// saveOrderToDb($payload['user_id'], $payload['item_id'], $payment['telegram_payment_charge_id']);
// Отправляем подтверждение пользователю
telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => "🎉 Спасибо за покупку! Подписка успешно активирована."
]);
} else {
// Критическая ошибка: подпись не совпала на финальном шаге
error_log("CRITICAL: Payment signature mismatch for user " . $payload['user_id']);
}
exit;
}
Пример входящего сообщения с успешной оплатой:
{
"update_id": 88442212,
"message": {
"message_id": 4505,
"from": {
"id": 987654321,
"is_bot": false,
"first_name": "Alex"
},
"chat": {
"id": 987654321,
"type": "private"
},
"date": 1700000015,
"successful_payment": {
"currency": "USD",
"total_amount": 499,
"invoice_payload": "{\"user_id\":987654321,\"item_id\":\"premium_sub_30\",\"hmac\":\"a1b2c3d4...\"}",
"telegram_payment_charge_id": "tg_charge_id_9988776655",
"provider_payment_charge_id": "ch_1234567890abcdef"
}
}
}
Обязательно сохраняйте в свою базу данных поля telegram_payment_charge_id (уникальный ID транзакции в Telegram) и provider_payment_charge_id (ID транзакции в самой платежной системе). Они понадобятся вам для бухгалтерии, разбора спорных ситуаций или оформления возвратов.
Как тестировать платежи без реальных денег
- Используйте только тестовые токены провайдеров (полученные в BotFather).
- При оплате тестовым инвойсом Telegram предложит ввести тестовую карту.
- Для Stripe используйте номер карты:
4242 4242 4242 4242, любой срок действия в будущем и любой CVC-код. - Для YooKassa используйте тестовую карту:
1111 1111 1111 1026или другие тестовые реквизиты из официальной документации ЮKassa. - В тестовом режиме весь цикл (включая
pre_checkout_queryиsuccessful_payment) проходит идентично реальному, но списания средств с настоящих карт не происходит.
Возвраты и фискализация
В рамках стандартного Bot API для фиатных валют нет метода для оформления возвратов. Если пользователю нужно вернуть деньги, вы должны сделать это вручную через личный кабинет вашего платежного провайдера (например, в панели Stripe или ЮKassa), используя сохраненный provider_payment_charge_id.
Что касается чеков (54-ФЗ в РФ и аналогичные законы в других странах): большинство современных провайдеров умеют формировать и отправлять чеки в налоговую автоматически. Для этого при отправке sendInvoice передавайте параметры need_email => true и send_email_to_provider => true, а в настройках личного кабинета платежного шлюза включите автоматическую фискализацию.
Дальше: Telegram Stars