Создание Чат-Бот-Магазин в Телеграм с нуля. Часть четвертая

Продолжение статей : (Часть 1Часть 2, Часть 3)

После оформления заказа добавим возможность оплатить заказ через Яндекс.Деньги на пользовательский кошелек. Для этого заменим кнопку, которая была в предыдущих статьях на экране просмотра заказа. Установим кнопку с ссылкой на страницу Яндекс.Денег с предзаполненой формой оплаты. Форма заполняется переданными в ссылке данными, саму ссылку генерирует метод getUrl

<?php
    /** Формируем ссылку для оплаты
     * @param $sum
     * @param $user_id
     * @param $order_id
     * @return string
     */
    private function getUrl($sum, $user_id, $order_id)
    {
        return "https://money.yandex.ru/quickpay/confirm.xml?receiver=" . $this->receiver
            . "&quickpay-form=shop&targets=" . urlencode($this->nameShop)
            . "&paymentType=AC&sum=" . $sum
            . "&label=" . $user_id . ":" . $order_id . ":" . md5(rand(0, 1000))
            . "&comment=" . urlencode("Оплата заказа #" . $order_id)
            . "&successURL=" . $this->urlBot;
    }

 

Кнопку заменим в методе drawOrder

<?php
    /** Рисуем заказ
     * @param $user_id
     * @param $begin
     * @return array
     */
    private function drawOrder($user_id, $begin)
    {

        // ...... 


            if(!$orderRaw['status']) {
                // готовим кнопку для перехода в Яндекс.Деньги
                $url = $this->getUrl($total, $user_id, $orderRaw['id']);
                $buttons[][] = $this->buildInlineKeyBoardButton('Оплатить через Яндекс.Деньги', '', $url);
            } else {
                // если заказ оплачен то уведомляем
                $text .= "\n<b>Заказ оплачен</b>\n";
            }


       // .......

    }

 

Для полноты формы создадим дополнительные свойства в начале нашего обработчика

<?php
    // Яндекс.Кошелек для приема оплаты
    private $receiver = "ВАШ_ЯНДЕКС_КОШЕЛЕК";
    // адрес на который переадресует пользователя в случае успешного платежа
    private $urlBot = "t.me/forarticlebot";
    // название магазина
    private $nameShop = "Телеграм Чат-Бот-Магазин";

 


HTTP уведомления

Теперь нам необходимо в настройках Яндекс.Денег настроить прием уведомлений на специальный адрес обработчика, который будет уведомлять пользователя и администратора о совершенном платеже, и в случае успешного выполнения задуманного сценария - менять статус заказа. 

Для этого на странице https://money.yandex.ru/myservices/online.xml необходимо указать путь до файла ya_success.php - перед сохранением изменений убедитесь, что чекбокс установлен у "Отправлять уведомления". Вы можете дополнительно сделать проверку уведомлений использовав секретное слово.

Файл - ya_success.php

<?php
// отсекаем лишние запросы
if ($_SERVER["REQUEST_METHOD"] != "POST") {
    echo "Hello world!";
    exit();
}

// запускаем
new Yandex();

/**
 * Class Yandex
 */
class Yandex
{
    private $data;

    // первичные данные
    private $token = "ВАШ_ТОКЕН";
    private $admin = 0;

    // для соединения с БД
    private $host = 'localhost';
    private $db = '';
    private $user = '';
    private $pass = '';
    private $charset = 'utf8mb4';

    /**
     * @var PDO
     */
    private $pdo;

    public function __construct()
    {
        $this->data = $_POST;
        $this->setPdo();
        $this->getPay();
    }

    /** Получаем Label
     * @return mixed
     */
    private function getLabel()
    {
        return $this->data['label'];
    }

    /** Получаем id пользователя
     * @return mixed
     */
    private function getIdUser()
    {
        return explode(":", $this->getLabel())[0];
    }

    /** Получаем id заказа
     * @return mixed
     */
    private function getIdOrder()
    {
        return explode(":", $this->getLabel())[1];
    }

    /** Получаем сумму
     * @return mixed
     */
    private function getFullSum()
    {
        return $this->data['withdraw_amount'];
    }

    /**
     *  Принимаем оплату
     */
    private function getPay()
    {
        // делаем запрос в бд по пришедшим данным
        $order = $this->pdo->prepare("SELECT * FROM bot_shop_order WHERE user_id = :user_id AND id = :order_id AND status = 0");
        $order->execute(['user_id' => $this->getIdUser(), 'order_id' => $this->getIdOrder()]);
        // если запись в бд найдена
        if ($order->rowCount() > 0) {
            // запрос на обновление статуса заказа
            $update = $this->pdo->prepare("UPDATE bot_shop_order SET status = 1 WHERE id = :id");
            // если обновили то уведомляем
            if ($update->execute(['id' => $this->getIdOrder()])) {
                // шлем пользователю уведомление о заказ оплачен
                $this->sendMsg([
                    'chat_id' => $this->getIdUser(), 
                    'text' => "Заказ #" . 
                        $this->getIdOrder() . " успешно оплачен. В ближайшее время с вами свяжется менеджер."]);
                // шлем алмину уведомление о заказ оплачен
                $this->sendMsg([
                    'chat_id' => $this->getIdUser(), 
                    'text' => "Заказ #" . 
                        $this->getIdOrder() . " успешно оплачен. Сумма " . 
                        $this->getFullSum() . "."]);
            } else {
                // шлем пользователю уведомление о том что пришли деньги за заказ, но есть проблемы
                $this->sendMsg([
                    'chat_id' => $this->getIdUser(), 
                    'text' => 'Пришли деньги ' . 
                        $this->getFullSum() . ' РУБ, за заказ ' . 
                        $this->getIdOrder() . ' но не удалось обновить статус заказа. С вами свяжется менеджер.']);
                // шлем админу уведомление о том что пришли деньги за заказ, но есть проблемы
                $this->sendMsg([
                    'chat_id' => $this->admin, 
                    'text' => 'Пришли деньги ' . 
                        $this->getFullSum() . ' РУБ, за заказ ' . 
                        $this->getIdOrder() . ' от пользователя ' . 
                        $this->getIdUser() . ' но не удалось обновить статус заказа.']);
            }
        } else {
            // шлем админу уведомление о том что пришли какие-то деньги
            $this->sendMsg([
                    'chat_id' => $this->admin, 
                    'text' => 'Пришли деньги ' . 
                        $this->getFullSum() . ' руб, но назначение не понятно ']);
        }
    }

    /** Отправляем сообщение
     * @param $fields
     * @return mixed
     */
    public function sendMsg($fields)
    {
        $ch = curl_init('https://api.telegram.org/bot' . $this->token . '/sendMessage');
        curl_setopt_array($ch, array(
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => http_build_query($fields),
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_TIMEOUT => 10
        ));
        $r = json_decode(curl_exec($ch), true);
        curl_close($ch);
        return $r;
    }

    /**
     *  Соединение с базой
     */
    private function setPdo()
    {
        $dsn = "mysql:host=$this->host;dbname=$this->db;charset=$this->charset";
        $opt = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
        ];
        $this->pdo = new PDO($dsn, $this->user, $this->pass, $opt);
    }
}

 


Исходные файлы бота

Это была последняя часть по строительству Чат-Бот-Магазина в Телеграм. Стиль программирования со временем конечно у меня меняется (прошло, наверное, больше полгода с начала первой статьи) и уже если честно, то прикручивать оплату в этот проект бота было уже не привычно, даже я скажу: "Как вообще я мог так пис`ать?", но чтобы не ломать логику настроенную в самом начале - пришлось подстраиваться. 

По итогу "скелет" бот-магазина готов. По вашим потребностям вы можете прикручивать любой функционал, менять и использовать этого бота без каких-либо ограничений. Огромная просьба не использовать бот в целях, которые нарушают законы. 


Комментарии

Авторизуйтесь через Telegram, чтобы оставить комментарий.
Откройте бот @SiteAuthBot, нажмите кнопку Старт/Start. Следуйте инструкциям бота.


  • Спасибо! Отличная статья!
    Юрий(НСП) 10.04.2019 в 20:07
    • Спасибо.
      iMakeBots 10.04.2019 в 20:08
  • Отлично! это лучшее что есть в сети, видно что человек любит своё дело)) Это шедевр! 10 из 10!
    Willgellm Altarius 11.04.2019 в 13:38
    • Спасибо. Дальше еще интереснее будет ))
      iMakeBots 11.04.2019 в 13:39
  • Когда часть 5?)))
    insta 4at 16.05.2019 в 02:17
    • Всего по плану было 4 части
      iMakeBots 16.05.2019 в 13:54
  • Спасибо за статью! Не подскажете, как можно изменить инлайн в обычную клавиатуру?
    Khurshid Akhmedjanov 17.05.2019 в 08:02
    • Ничего сложно в выводе клавиатуры вместо кнопок нет, просто вам этого будет не достаточно, ведь кнопка клавиатуры отправляет объект message, а инлайн кнопка объект callback_query - и необходимо сделать метод, который будет ловить команду и обрабатывать по вашему сценарию.

      Все кнопки отправляются в АПИ одинаково в параметре reply_markup - только со своими параметрами:

      Массив данных (<br />
          'Тип клавиатуры' =&gt; <br />
              Массив строк кнопок (<br />
                  Массив кнопок строки (<br />
                      Массив параметров кнопки (<br />
                  <br />
                      )<br />
                  )<br />
               ),<br />
          // если предусмотрено <br />
          'Дополнительный параметр',<br />
      )
      iMakeBots 17.05.2019 в 10:32
  • Не подскажите как добавить способ оплаты "Наличными при получении"? чтоб было 2 способа оплаты, Яндекс. Деньги и наличные
    A M 21.06.2019 в 23:43
    • Просто вывести кнопку "Наличными при получении" просто. Но нужен еще обработчик этой кнопки.

      private function drawOrder($user_id, $begin)<br />
          {<br />
              // ......<br />
              $buttons[] = [<br />
                   $this-&gt;buildInlineKeyBoardButton('Оплатить через Яндекс.Деньги', '', $url),<br />
                   $this-&gt;buildInlineKeyBoardButton('Наличными при получении', 'getNal_' . $orderRaw['id'])<br />
              ];<br />
          }<br />
      <br />
      /**<br />
      * Обработчик кнопки &quot;За наличные&quot;<br />
      */<br />
      public function getNal($data) <br />
          {<br />
              // ......<br />
              // Получаем id заказа через данные от кнопки<br />
              $param = explode(&quot;_&quot;, $data['data']);<br />
              $order_id = $param[1];<br />
              // Далее логика - например записать в бд <br />
              // что этот заказ будет оплачен наличными, <br />
              // выслать инструкцию ...<br />
          }<br />
      
      iMakeBots 22.06.2019 в 09:08
      • Благодарю, а не подскажите как реализовать следующую вещь. Допустим у клиента несколько адресов доставки или несколько получателей заказа, допустим 3 (корзина одна, а получателй несколько), как реализовать клонирование полей ввода для клиента? Чтоб нажимая кнопку "Добавить нового получателя" дублировались Телефон1/Адрес1/Имя1, затем нажимая "Добавить нового получателя" процесс повторялся до того момента пока пользователь не внесет всех получателей?

        Я так понимаю это тут надо делать?
          <br />
        private function savePhoneUser($text, $data)<br />
            {<br />
                $user_id = $this-&gt;getChatId($data);<br />
                // проверяем телефон<br />
                if (preg_match(&quot;/^ [0-9]{9,14}$/i&quot;, $text)) {<br />
                    if ($this-&gt;setActionUser(&quot;step_2_adress&quot;, $user_id)) {<br />
                        if ($this-&gt;setParamUser('phone', $text, $user_id)) {<br />
                            $text_ = &quot;&lt;b&gt;Оформление заказа&lt;/b&gt;nn&quot;;<br />
                            // сумма заказа<br />
                            $text_ .= &quot;Сумма заказа: &quot; . $this-&gt;totalSumOrder($user_id) . &quot; рублей&quot;;<br />
                            // телефон<br />
                            $text_ .= &quot;nТелефон: &quot; . $text;<br />
                            $text_ .= &quot;nnУкажите ваш адрес для доставки:&quot;;<br />
        

        И еще один вопрос (если не сложно) как сделать информирование пользователя о том, что его заказ принят и/или исполнен? К примеру чтоб админ нажимал на кнопку "заказ принят", "заказ выполнен" или любую другую, и пользователь получал в свой телеграм или на почту уведомление о том, что его заказ "такой-то" принят или выполнен?
        A M 22.06.2019 в 22:51
        • Вопрос 1:
          Если вы запланируете в сценарии оформления заказа возможность выбора адреса доставки из множества, то в отсутствии хотя бы одного заполненного профиля места доставки по умолчанию просто запрос данных, если уже есть минимум один то возможность выбрать из имеющихся + возможность заполнить новый.
          Да, это можно сделать на этом участке

          private function savePhoneUser($text, $data)
          Вопрос 2:
          Для таких уведомлений надо реализовать администрирование заказов, предусмотреть смену статусов и при смене статуса в зависимости от сценария производить уведомления или какие-либо другие действия.
          iMakeBots 23.06.2019 в 12:59
      • Да и не могли бы вы вот этот момент расписать если не сложно

        " // Далее логика - например записать в бд
        // что этот заказ будет оплачен наличными,
        // выслать инструкцию ..."
        Я как бы чайник, и в этом не особо шарю, да и другим людям это тоже пригодиться. Спасибо
        A M 22.06.2019 в 23:23
        • Расписать это можно по разному, у каждого своя модель взаимодействия )

          Этот участок кода включается когда пользователь нажал на кнопку для оплаты наличными, по моей логике должно происходить следующее:
          1. Установить флаг в заказе о том что оплата будет за наличные
          2. Вывести на экран инструкцию как это будет происходить: " ... с вами свяжется менеджер для подтверждения заказа, после курьер вам доставит товар и вы произведете оплату на месте вручения товара ..."
          3. К инструкции я еще бы добавил кнопку на переход в личный кабинет для просмотра заказа в нем
          iMakeBots 23.06.2019 в 12:44
          • Спасибо, за ответы, но я повторяю - я не программист понимаете? Мне бы ваши ответы выше в виде кода с пояснением что, куда подставить и не только мне эти ответы были бы полезны, не думаю что у вас аудитория данного блога состоит из профи, я думаю что все же основная масса сюда зашедших это "чайники" как я к примеру, было бы здорово если бы вы максимально в сторону чайников ориентировались, потому что чайники не знают что, куда и как писать, они просто хотят готового решения.

            К примеру вот тут:
            private function savePhoneUser($text, $data)
            Я у вас решения спрашивал, я не знаю как это реализовать самостоятельно.

            И касательно вот этого:
            <br />
            Этот участок кода включается когда пользователь нажал на кнопку для оплаты наличными, <br />
            по моей логике должно происходить следующее:<br />
            1. Установить флаг в заказе о том что оплата будет за наличные<br />
            2. Вывести на экран инструкцию как это будет происходить: <br />
            &quot; ... с вами свяжется менеджер для подтверждения заказа, после курьер вам доставит товар <br />
            и вы произведете оплату на месте вручения товара ...&quot;<br />
            3. К инструкции я еще бы добавил кнопку на переход в личный кабинет для просмотра заказа в нем<br />
            

            Я как бы тоже могу диссертацию написать, но хотелось бы решения потому что я лично не программист повторяю и я вообще не понимаю как вот это сделать
            "1. Установить флаг в заказе о том что оплата будет за наличные
            2. Вывести на экран инструкцию как это будет происходить: " ... с вами свяжется менеджер для подтверждения заказа, после курьер вам доставит товар и вы произведете оплату на месте вручения товара ..."
            3. К инструкции я еще бы добавил кнопку на переход в личный кабинет для просмотра заказа в нем".

            Представьте что вы сдали телефон в ремонт или пришли подстригаться, а вас на половину подстригли и сказали, ну домой потом придешь сам себя подравняешь, лишнее уберешь и я бы еще вот это вот добавил, ну ладно мне пора, пока...
            З.Ы: добавьте на сайт какой нибудь способ сделать пожертвования в пользу вас/вашего проекта.
            A M 23.06.2019 в 16:52
            • Прекрасно понимал, когда писал ответы, что вы хотите рабочий код вместо текста. В комментариях все что вы хотите не запилить - все запутаются куда и чего копировать\вставлять, это отдельная статья.

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

              Сам для коммерческих проектов, так как описываю в статьях, я не пишу, потому что обслуживать такую архитектуру проекта очень сложно, а вот понять новичку легче когда весь код в одном файле.

              Устанавливать донат пока в планах нет.
              iMakeBots 23.06.2019 в 17:44
      • Доброго времени суток, я сделал как вы написали, но ничего не происходит, см. скриншот, просто появляется таймер после клика на кнопку и все, код обработчика тот же что и у вас. в чем может быть проблема? обработчик в любое место вставляется? я его вставил после
        private function getType($data)<br />
            {<br />
                if (isset($data['callback_query'])) {<br />
                    return &quot;callback_query&quot;;<br />
                } elseif (isset($data['message']['text'])) {<br />
                    return &quot;message&quot;;<br />
                } elseif (isset($data['message']['photo'])) {<br />
                    return &quot;photo&quot;;<br />
                } else {<br />
                    return false;<br />
                }<br />
            } 

        http://skrinshoter.ru/s/260619/MYTzC8AP?a 
        Код кнопок оплаты
        <br />
                    if(!$orderRaw['status']) {<br />
                        // готовим кнопку для перехода в Яндекс.Деньги<br />
                        $url = $this-&gt;getUrl($total, $user_id, $orderRaw['id']);<br />
                        $buttons[] = [ $this-&gt;buildInlineKeyBoardButton('Оплатить через Яндекс.Деньги', '', $url),<br />
        				$this-&gt;buildInlineKeyBoardButton(<br />
        				    'При отправлении Наличными', <br />
        				    'getNal_' . $orderRaw['id']),<br />
        				$this-&gt;buildInlineKeyBoardButton(<br />
        				    'При отправлении Сканирование QR-Кода Яндекс.Деньги', <br />
        				    'getQrYandex_' . $orderRaw['id']),<br />
        				];<br />
                    } else {<br />
                        // если заказ оплачен то уведомляем<br />
                        $text .= &quot;n&lt;b&gt;Заказ оплачен&lt;/b&gt;n&quot;;<br />
                    }<br />
        

        У вас статья есть "Клавиатура как способ взаимодействия с ботом", не подскажете как вывести кнопки под сообщением по аналогии из той статьи после того как пользователь /start отправит?
        $justKeyboard = $this-&gt;getKeyBoard([[[&quot;text&quot; =&gt; &quot;Голосовать&quot;], [&quot;text&quot; =&gt; &quot;Помощь&quot;]]]);
        A M 26.06.2019 в 14:03
  • Приветствую , статьи все крутые.
    Хотел спросить можно ли вместо этих кнопок , переделать все в клавиатуру? Я просто прям новичок в этом ) благодарю заранее за ответ)
    Ruslan Sharipov 08.05.2020 в 08:58
    • Переделать конечно можно, но будет не удобно (мое мнение)
      iMakeBots 11.05.2020 в 17:58
  • Рабочий скелет то!? Есть кто делал чат бота магазину по нему!?
    W O N D E R F U L 11.05.2020 в 06:23
    • Это бот больше для примера
      iMakeBots 11.05.2020 в 17:57
  • Здравствуйте! Пытаюсь запустить бота локально, указал в настройках свой id в телеграм, так же токен бота и данные для соединения с бд.
    Скачал и установил программу Windscribe для VPN соединения с локальным сайтом, но бот отказывается работать! А при запуске в консоли OpenServer файла index.php выдает ошибку:
    Warning: array_key_exists() expects parameter 2 to be array, null given in OSPaneldomainstelegrambot.locindex.php on line 71
    и
    Warning: array_key_exists() expects parameter 2 to be array, null given in OSPaneldomainstelegrambot.locindex.php on line 149
    Что я делаю не так? Может надо через setWebHook?
    Rus 27.05.2020 в 13:04
    • Этот бот работает через WebHook, то есть на него нужно настроить вебхук. Если вы не можете настроить webhook на локальный сервер, то можете воспользоваться NodeJS - у меня есть статья как это можно настроить.

      Ошибка array_key_exists() - отрабатывает нормально - так как скрипт ожидает данных от Телеграм и преобразовывает их в массив - далее передает в метод и там идет проверка array_key_exists() - а в вашем случае массив не передается - он и ругается.
      iMakeBots 27.05.2020 в 15:10
      • Я попробовал через ссылку в браузере активировать WebHook, но бот так и не ожил, ссылка имеет вид:
        https://api.tlgr.org/bot<TOKEN>/setWebHook?url=https://<my-site>/telegrambot/index.php
        Так же должно работать по идее? В боте пишу команду /start и /admin - ноль реакций...
        Rus 27.05.2020 в 23:02