Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров9.7K

Недавно добавил оплату в свой Телеграм‑бот. После некоторых изысканий выбор пал на Тинькофф (ныне Т‑банк). Сам бот работает на 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-сертификата.

И если что-то из перечисленного на сайте меняется, то бот шлёт уведомление. И ещё он напоминает продлить домен/обновить сертификат за несколько дней до истечения срока.

Я сосредоточился только на этих функциях и постарался сделать их хорошо и понятно. Буду рад, если попробуете и расскажете, получилось ли. Мониторинг одного сайта там останется бесплатным, но буду не менее рад, если и оплату тоже попробуете ;-)

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 16: ↑14 и ↓2+21
Комментарии7

Публикации

Истории

Работа

PHP программист
78 вакансий

Ближайшие события

19 марта – 28 апреля
Экспедиция «Рэйдикс»
Нижний НовгородЕкатеринбургНовосибирскВладивостокИжевскКазаньТюменьУфаИркутскЧелябинскСамараХабаровскКрасноярскОмск
24 апреля
VK Go Meetup 2025
Санкт-ПетербургОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
14 мая
LinkMeetup
Москва
5 июня
Конференция TechRec AI&HR 2025
МоскваОнлайн
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область