Подключение оплаты Тинькофф к Telegram-боту на чистом php
Недавно добавил оплату в свой Телеграм‑бот. После некоторых изысканий выбор пал на Тинькофф (ныне Т‑банк). Сам бот работает на php без вспомогательных библиотек. Возможно, кому‑то пригодится мой опыт и код.
Схема следующая:
Пользователь в боте выбирает, на какую сумму хочет пополнить баланс.
Я формирую хитрый запрос к Тинькофф (код ниже).
Тинькофф отвечает ссылкой на оплату.
Я шлю пользователю эту ссылку.
Пользователь переходит по ссылке на домен Тинькофф, там выбирает удобный способ и оплачивает.
Тинькофф присылает мне не только письмо, но и POST‑запрос.
Я забираю из этого запроса сумму, статус и, что самое важное, id пользователя бота.
Пишу пользователю в Telegram благодарность.
Краткая схема оплаты из бота
О выборе способа оплаты
Есть два штатных способа оплаты в Telegram‑боте:
Stars — название намекает, что это ≠ деньги, поэтому пока не заставят не хочу.
Через платёжных провайдеров. Это когда прямо внутри Telegram всплывает окошко.
Я попробовал второй способ, начал настраивать ЮKassa. Схема выглядит рабочей, но есть некоторые нюансы:
Нужно вводить номер карты. Это не так прикольно, как в один клик перейти в приложение банка и там безопасно оплатить. Насколько я понял, именно в Tg так не сделать.
Долго с ними как‑то…
Перешёл к следующему варианту: эквайринг от Тинькофф. Я подумал, что как минимум на сайте смогу спокойно встроить платёжный модуль и там принимать оплату, а счёт у меня уже был. Воспользовался Конструктором сайтов тоже от Тинькофф, добавил там оплату, прошёл модерацию, протестировал. Опять нюансы:
Нужно из Telegram передать каким‑то образом id пользователя. Допустим, можно зашить в url.
Далее на форме оплаты этот id нужно подставить в форму. Допустим, можно написать свой код на JS (конструктор позволяет).
Далее вообще неожиданный сюрприз: не смотря на явную настройку «уведомлять об оплате по http», банк не хочет слать мне такое уведомление. Поддержка пояснила:
Если оплата идет с нашего сайта, то мы передаем всегда свой NotificationURL для нотификации в init и его изменить или удалить нельзя, так как мы без него не узнаем о том, что на сайте оплата произошла, и не сможем прокинуть заказ в заказы. Хорошего вечера!
Тут мои полномочия всё. Вручную обрабатывать немногочисленные платежи, конечно, можно, но я хочу сразу же уведомлять пользователя, что его деньги дошли.
К счастью, попался сотрудник поддержки, который понял мой конечный замысел. Он пояснил, что в моей схеме сайт — лишнее звено, что подключаться надо по API, дал ссылку на документацию. Что ж, сайт всё равно пригодился для размещения оферты.
Как формировать ссылку на оплату в Тинькофф
Про создание платёжного терминала для Интернет-эквайринга не буду писать: это делается через интерфейс, есть справка. Сосредоточусь на той части, которая в справке обозначена как «помощь программиста».
Для получения платёжной ссылки нам потребуется всего один запрос Init
, он описан тут.
Перед отправкой нужно элегантным образом зашифровать пароль в теле запроса и получить Token
.
Итак, по шагам:
Сформировать тело запроса — JSON-объект с обязательными полями:
TerminalKey
— берётся тут: Личный кабинет → Интернет-эквайринг → Магазины → [Магазин] → Терминалы → Рабочий терминал → Настроить, справа под словом «Терминал».Amount
— сумма в копейках (целое число).OrderId
— должно быть уникальным. Именно сюда я прячу id пользователя, чтобы потом получить его же в уведомлении о платеже, а через дефис добавляю уникальный номер заказа.Ещё
Description
наполовину обязательный. Я всегда добавляю, его видит пользователь.
Собрать массив передаваемых данных в виде пар ключ-значения. В массив нужно добавить только параметры корневого объекта. Вложенные объекты и массивы не участвуют в расчёте токена.
Добавить в массив
Password
— берётся в личном кабинете, там же где и Терминал.Отсортировать массив по алфавиту по ключу.
Конкатенировать только значения пар в одну строку (не добавляя разделители).
Применить к строке хэш-функцию SHA-256 (с поддержкой UTF-8).
Получившийся результат поместить в значение параметра
Token
в тело запроса (которое создали на 1 шаге).Удалить из тела запроса
Password
. Его передавать не надо.Отправить POST-запрос с JSON-телом.
Получить ответ и достать из него заветную ссылку.
Примерно так у меня это получилось на php:
$amount*100,
"Description" => 'Пополнение баланса бота "Мониторинг сайта"',
"OrderId" => "$chatId-n$orderNumber",
"TerminalKey" => TINKOFF_TERMINAL_KEY,
"Password" => TINKOFF_TERMINAL_PASSWORD
];
ksort($data);
// Получаем все значения из массива
$values = array_values($data);
// Конкатенируем все значения в одну строку
$concatenatedString = implode('', $values);
// Хэшируем
$hashedString = hash('sha256', $concatenatedString);
$data['Token'] = $hashedString;
unset($data['Password']);
$postDataJson = json_encode($data);
// Настройки cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, TINKOFF_INIT_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postDataJson);
// Добавляем заголовки для указания того, что тело запроса содержит JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($postDataJson)
]);
// Выполнение запроса и получение ответа
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($output === false || $httpCode !== 200) {
error_log('Не удалось выполнить запрос, HTTP код: ' . $httpCode);
return false;
}
$outputArray = json_decode($output, true); // true означает декодирование в массив
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('Ошибка при декодировании JSON: ' . json_last_error_msg());
return false;
}
if (isset($outputArray['Success']) && $outputArray['Success'] === true
&& isset($outputArray['PaymentURL'])) {
return $outputArray['PaymentURL'];
} else {
error_log("Ссылка не пришла");
return false;
}
}
Получившуюся ссылку отправляем пользователю, он может по ней перейти и оплатить.
Как узнать, что пришла оплата
В настройках терминала надо включить уведомления «По протоколу HTTP» и указать свой url. Либо можно передавать NotificationURL
в методе Init
, он будет иметь приоритет над настройкой терминала (не проверял).
Документация на формат уведомления здесь. Там же перечислены IP-адреса, с которых эти уведомления будут приходить, и алгоритм проверки Token
— аналогичен тому, что и при отправке Init
.
На это уведомление важно правильно ответить (200-й код, в теле «OK»), иначе эйквайринг будет с упрямством коллектора слать одинаковые уведомления снова и снова. Для подстраховки от задваивания поступлений в базе данных рекомендую разрешить только уникальные комбинации Status
+ PaymentId
. Либо по полю Token
.
Чтобы понять, какому пользователю нужно объявить благодарность за оплату, я достаю его id из поля OrderId
. Это значение я ранее составил из id пользователя и уникального номера заказа, теперь оно вернулось в уведомлении об оплате. Ещё есть вариант попросить поддержку включить передачу поля DATA
, в котором можно отправлять произвольные данные.
Лирическое отступление.
Лёгкость и скорость срабатывания процесса оплаты — от сканирования QR-кода до получения сообщения в Telegram — завораживает. Особенно когда часть кода из этого процесса написал ChatGPT сам.
Удивительно, что где-то ещё считают нормальным процессом оплаты выписывание и отправку по почте бумажных банковских чеков и их учёт в чековой книжке…
А что за бот вообще?
Бот для мониторинга доступности сайтов. Проверяет:
Код и скорость ответа.
Куда ведёт переадресация.
сайта.Срок регистрации домена.
Срок действия SSL-сертификата.
И если что-то из перечисленного на сайте меняется, то бот шлёт уведомление. И ещё он напоминает продлить домен/обновить сертификат за несколько дней до истечения срока.
Я сосредоточился только на этих функциях и постарался сделать их хорошо и понятно. Буду рад, если попробуете и расскажете, получилось ли. Мониторинг одного сайта там останется бесплатным, но буду не менее рад, если и оплату тоже попробуете;-)