Подключение оплаты Тинькофф к Telegram-боту на чистом php

от автора

Недавно добавил оплату в свой Телеграм‑бот. После некоторых изысканий выбор пал на Тинькофф (ныне Т‑банк). Сам бот работает на php без вспомогательных библиотек. Возможно, кому‑то пригодится мой опыт и код.

Схема следующая:

  1. Пользователь в боте выбирает, на какую сумму хочет пополнить баланс.

  2. Я формирую хитрый запрос к Тинькофф (код ниже).

  3. Тинькофф отвечает ссылкой на оплату.

  4. Я шлю пользователю эту ссылку.

  5. Пользователь переходит по ссылке на домен Тинькофф, там выбирает удобный способ и оплачивает.

  6. Тинькофф присылает мне не только письмо, но и POST‑запрос.

  7. Я забираю из этого запроса сумму, статус и, что самое важное, id пользователя бота.

  8. Пишу пользователю в Telegram благодарность.

Краткая схема оплаты из бота

Краткая схема оплаты из бота

О выборе способа оплаты

Есть два штатных способа оплаты в Telegram‑боте:

  1. Stars — название намекает, что это ≠ деньги, поэтому пока не заставят не хочу.

  2. Через платёжных провайдеров. Это когда прямо внутри Telegram всплывает окошко.

Я попробовал второй способ, начал настраивать ЮKassa. Схема выглядит рабочей, но есть некоторые нюансы:

  1. Нужно вводить номер карты. Это не так прикольно, как в один клик перейти в приложение банка и там безопасно оплатить. Насколько я понял, именно в Tg так не сделать.

  2. Долго с ними как‑то…

Перешёл к следующему варианту: эквайринг от Тинькофф. Я подумал, что как минимум на сайте смогу спокойно встроить платёжный модуль и там принимать оплату, а счёт у меня уже был. Воспользовался Конструктором сайтов тоже от Тинькофф, добавил там оплату, прошёл модерацию, протестировал. Опять нюансы:

  1. Нужно из Telegram передать каким‑то образом id пользователя. Допустим, можно зашить в url.

  2. Далее на форме оплаты этот id нужно подставить в форму. Допустим, можно написать свой код на JS (конструктор позволяет).

  3. Далее вообще неожиданный сюрприз: не смотря на явную настройку «уведомлять об оплате по http», банк не хочет слать мне такое уведомление. Поддержка пояснила:

    Если оплата идет с нашего сайта, то мы передаем всегда свой NotificationURL для нотификации в init и его изменить или удалить нельзя, так как мы без него не узнаем о том, что на сайте оплата произошла, и не сможем прокинуть заказ в заказы. Хорошего вечера!

    Тут мои полномочия всё. Вручную обрабатывать немногочисленные платежи, конечно, можно, но я хочу сразу же уведомлять пользователя, что его деньги дошли.

К счастью, попался сотрудник поддержки, который понял мой конечный замысел. Он пояснил, что в моей схеме сайт — лишнее звено, что подключаться надо по API, дал ссылку на документацию. Что ж, сайт всё равно пригодился для размещения оферты.

Как формировать ссылку на оплату в Тинькофф

Про создание платёжного терминала для Интернет-эквайринга не буду писать: это делается через интерфейс, есть справка. Сосредоточусь на той части, которая в справке обозначена как «помощь программиста».

Для получения платёжной ссылки нам потребуется всего один запрос Init, он описан тут.

Перед отправкой нужно элегантным образом зашифровать пароль в теле запроса и получить Token.

Итак, по шагам:

  1. Сформировать тело запроса — JSON-объект с обязательными полями:

    1. TerminalKey — берётся тут: Личный кабинет → Интернет-эквайринг → Магазины → [Магазин] → Терминалы → Рабочий терминал → Настроить, справа под словом «Терминал».

    2. Amount — сумма в копейках (целое число).

    3. OrderId — должно быть уникальным. Именно сюда я прячу id пользователя, чтобы потом получить его же в уведомлении о платеже, а через дефис добавляю уникальный номер заказа.

    4. Ещё Description наполовину обязательный. Я всегда добавляю, его видит пользователь.

  2. Собрать массив передаваемых данных в виде пар ключ-значения. В массив нужно добавить только параметры корневого объекта. Вложенные объекты и массивы не участвуют в расчёте токена. 

  3. Добавить в массив Password — берётся в личном кабинете, там же где и Терминал.

  4. Отсортировать массив по алфавиту по ключу.

  5. Конкатенировать только значения пар в одну строку (не добавляя разделители).

  6. Применить к строке хэш-функцию SHA-256 (с поддержкой UTF-8).

  7. Получившийся результат поместить в значение параметра Token в тело запроса (которое создали на 1 шаге).

  8. Удалить из тела запроса Password . Его передавать не надо.

  9. Отправить POST-запрос с JSON-телом.

  10. Получить ответ и достать из него заветную ссылку.

Примерно так у меня это получилось на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *