Недавно добавил оплату в свой Телеграм‑бот. После некоторых изысканий выбор пал на Тинькофф (ныне Т‑банк). Сам бот работает на 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:
<?php function tinkoff_getLink($amount, $chatId, $orderNumber) { $link = false; // Amount нужно передать в копейках, целое число // OrderId должен быть уникальным $data = [ "Amount" => $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 сам.
Удивительно, что где-то ещё считают нормальным процессом оплаты выписывание и отправку по почте бумажных банковских чеков и их учёт в чековой книжке…
А что за бот вообще?
Бот для мониторинга доступности сайтов. Проверяет:
-
Код и скорость ответа.
-
Куда ведёт переадресация.
-
<title>
сайта. -
Срок регистрации домена.
-
Срок действия SSL-сертификата.
И если что-то из перечисленного на сайте меняется, то бот шлёт уведомление. И ещё он напоминает продлить домен/обновить сертификат за несколько дней до истечения срока.
Я сосредоточился только на этих функциях и постарался сделать их хорошо и понятно. Буду рад, если попробуете и расскажете, получилось ли. Мониторинг одного сайта там останется бесплатным, но буду не менее рад, если и оплату тоже попробуете 😉
ссылка на оригинал статьи https://habr.com/ru/articles/845128/
Добавить комментарий