Top.Mail.Ru

Woocommerce и Т-банк: баг с ордерами и оплатой

Настраивать в интернет-магазине на Woccommerce прием платежей – стандартная ситуация для любого вебмастера. Однако любая стандартная ситуация может выйти из-под контроля и принести очень много проблем и потрепать очень много нервов. Сегодня рассмотрим тандем Woocommerce и Т-банк (бывший Тинкофф) и баг с ордерами и обратным статусом от банка.

Woocommerce и Т-банк

Начало подключения онлайн-оплаты в интернет магазине везде одинаково:

  • подаете заявку в интересующем банке;
  • банк рассматривает эту возможность;
  • если все “ок”, банк выдает разрешение на подключение и реквизиты для терминала.

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

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

Woocommerce и Тинькофф: ситуация с ошибкой

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

Было решено переделать его полностью. Для этого:

  • была сделана копия сайта;
  • были удалены все кастомные решения, в том числе и шаблон;
  • потом установлен чистый шаблон и известные плагины, которые без проблем будут обновляться.

В общем, опять пошла лирика, но уже о переделке сайта :). Сейчас не об этом. Когда основные работы были проделаны, были подключены и протестированы платежи с копии сайта. Все отлично работало.

Потом подменили файлы рабочего сайта на файлы копии. Настроили нужные редиректы. Для оплаты подключили рабочий терминал банка и вот тут началось….

Ситуация

Суть была вот в чем. Не все платежи уходили с сайта. Что-то оплачивалось, что-то нет. Когда оплата не проходила выскакивала ошибка “Запрос не может быть обработан. Дублирующий order_id” В результате магазин не мог номально работать. Не менялись статусы заказов, а значит не уходили письма и не отправлялся товар.

Перепроверили все настройки внутри сайта и внутри личного кабинета банка – все отлично и правильно. День-два искали проблему – не могли найти. Обратились в техподдержку банка, так как проблема виднелась на их стороне. Техподдержка банка ответила, что у них все хорошо, что проблема на стороне сайта. В общем, как обычно – играли в теннис.

Смущало сообщение “Дублирующий order_id”. Техподдержка также подтвердила, что с сайта приходят дублирующие ордера, поэтому банк их обработать не может. WTF! Но у нас на сайте, Woocommerce установлен с нуля и никогда не был установлен, откуда “дублирующие ордера”?

В общем, немного поразмыслив, залез в Базу данных, чтобы глянуть, что там. А там увидел, что какое-то время на сайте была подключена оплата и был настроен плагин WP-Shop. За годы использования этого плагина там накопилось много ордеров.

Вот и получилось, что наш чистый Woocommerce отправлял в банк ордер на оплату, допустим 3555, но данный ордер уже отправлялся плагином wp-shop несколько лет назад! Для нас получался это новый ордер, а для банка дублирующий, а значит банк не мог его обработать! И банк обрабатывал только те номера ордеров, по которым оплата “тогда” не проходила.

Самое интересное в том, что когда работал над копией сайта, плагина wp-shop не было, поэтому его я сразу не увидел. Да и сама оплата какое-то время уже не работала на сайте.

Woocommerce и T-банк: решение ошибки с ордерами и оплатой

Woocommerce и Т-банк: баг с ордерами и оплатой

Продвинутый вебмастер скажет, так а в чем проблема заменить order_id и забыть про эту проблему? Я тоже так думал. Написал хук – не сработало. Попробовал через плагин – не сработало. Вернее все работало, но не так:

Если просто менять order_id хуком или плагином, то он меняется только у пользователя! То есть, при заказе пользователь видит “новый” order_id и в админке в Заказах, вы его тоже будете видеть. Но в банк отправляется “чистый” order_id и он же сохраняется в базе данных

В общем, подмена order_id не работает в этом случае.

Еще немного поразмыслив нашел 2 решения:

  1. Я подумал, что если в личном кабинете банка создать новый терминал для оплаты и потом подключить его на сайт, то все сработает. Задав этот вопрос в техподдержку, я не ошибся. Это действительно помогло бы. Но нужно было бы опять проходить тесты. Да и заказчика не радовало такое решение, так как в новом терминале не было бы истории платежей.
  2. Внести небольшие корректировки в плагин Т-банка.

Еще раз уточню. До этого момента мы много общались с техподдеркой. Историю с ордерами и двумя плагинами им обрисовал. О том, что невозможно подменить order_id ни хуком, ни плагином сказал. Получалось чтобы все-таки его подменить, оставалось подредактировать плагин Woocommerce и изменить у него логику формирования ордеров. Честно, вот в этот плагин точно лезть не хотелось, а в плагин Тинькофф, хотелось 🙂 потому что решение было на виду. Да и техподдержка банка была не против.

Редактируем плагин Т-банк

Решение было простое. Плагин цеплял с заказа order_id и прям перед его отправкой в банк, было решено ему добавить “соль”, а вернее метку времени.

Находите в файлах плагина файл wc-tbank.php и в нем вот эту функцию:

//Оригинальная функция        

function receipt_page($order_id)
        {
            $order = new WC_Order($order_id);
            $setting = array(
                "email_company" => $this->get_option('email_company'),
                "payment_method_ffd" => $this->get_option('payment_method_ffd'),
                "payment_object_ffd" => $this->get_option('payment_object_ffd'),
                "check_data_tax" => $this->get_option('check_data_tax'),
                "taxation" => $this->get_option('taxation'),
                "payment_form_language" => $this->get_option('payment_form_language'),
                "ffd" => $this->get_option('ffd')
            );

            $supportPaymentTBank = new SupportPaymentTBank($setting);
            $arrFields = SupportPaymentTBank::send_data($order, $order_id);

            if (!function_exists('get_plugins')) {
                // подключим файл с функцией get_plugins()
                require_once ABSPATH . 'wp-admin/includes/plugin.php';
            }

            // получим данные плагинов
            $all_plugins = get_plugins();

            foreach ($all_plugins as $key => $plugin) {
                preg_match_all('#woocommerce-subscriptions-[0-9.-]+\/woocommerce-subscriptions.php#uis', $key, $pluginSubscriptions);

                if (!empty($pluginSubscriptions[0])) {
                    // активирован ли плагин woocommerce-subscriptions
                    if (is_plugin_active($key)) {

                        if (wcs_order_contains_subscription($order)) {
                            $arrFields['Recurrent'] = "Y";
                            $arrFields['CustomerKey'] = (string)$order->get_user_id();
                        }
                    }
                }
            }

            $arrFields = SupportPaymentTBank::get_setting_language($arrFields);

            $TBank = new TBankMerchantAPI($this->get_option('merchant_id'), $this->get_option('secret_key'));
            $request = $TBank->buildQuery('Init', $arrFields);
            $request = json_decode($request);
            if (!$request->Success) {
                $log = ["OrderId" => $arrFields["OrderId"]];
                SupportPaymentTBank::logs($arrFields["OrderId"], $request);
            }

            if (!empty($this->payment_system_name)) {
                $arrFields['payment_system_name'] = $this->payment_system_name;
            }

            foreach ($arrFields as $strFieldName => $strFieldValue) {
                $args_array[] = '<input type="hidden" name="' . esc_attr($strFieldName) . '" value="' . esc_attr($strFieldValue) . '" />';
            }

            if (isset($request->PaymentURL)) {
                try {
                    wc_reduce_stock_levels($order_id);
                } catch (Exception $e) {

                }
                setcookie('tbankReturnUrl', $this->get_return_url($order), time() + 3600, "/");
                wp_redirect($request->PaymentURL);
            } else {
                echo '<p>' . LanguageTBank::get(LanguageTBank::REQUEST_TO_PAYMENT) . '</p>';
            }
        }

И вместо оригинальной функции пишем доработанную. Вот она:

//Доработанная функция
         function receipt_page($order_id)
{
    $order = new WC_Order($order_id);
    $setting = array(
        "email_company" => $this->get_option('email_company'),
        "payment_method_ffd" => $this->get_option('payment_method_ffd'),
        "payment_object_ffd" => $this->get_option('payment_object_ffd'),
        "check_data_tax" => $this->get_option('check_data_tax'),
        "taxation" => $this->get_option('taxation'),
        "payment_form_language" => $this->get_option('payment_form_language'),
        "ffd" => $this->get_option('ffd')
    );

    $supportPaymentTBank = new SupportPaymentTBank($setting);
    $arrFields = SupportPaymentTBank::send_data($order, $order_id);

    // Здесь мы модифицируем OrderId
    $arrFields['OrderId'] = $order_id . '-' . time();  // Добавляем метку времени, чтобы сделать OrderId уникальным

    if (!function_exists('get_plugins')) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }

    $all_plugins = get_plugins();

    foreach ($all_plugins as $key => $plugin) {
        preg_match_all('#woocommerce-subscriptions-[0-9.-]+\/woocommerce-subscriptions.php#uis', $key, $pluginSubscriptions);

        if (!empty($pluginSubscriptions[0])) {
            if (is_plugin_active($key)) {
                if (wcs_order_contains_subscription($order)) {
                    $arrFields['Recurrent'] = "Y";
                    $arrFields['CustomerKey'] = (string)$order->get_user_id();
                }
            }
        }
    }

    $arrFields = SupportPaymentTBank::get_setting_language($arrFields);

    $TBank = new TBankMerchantAPI($this->get_option('merchant_id'), $this->get_option('secret_key'));
    $request = $TBank->buildQuery('Init', $arrFields);
    $request = json_decode($request);

    if (!$request->Success) {
        SupportPaymentTBank::logs($arrFields["OrderId"], $request);
    }

    if (!empty($this->payment_system_name)) {
        $arrFields['payment_system_name'] = $this->payment_system_name;
    }

    foreach ($arrFields as $strFieldName => $strFieldValue) {
        $args_array[] = '<input type="hidden" name="' . esc_attr($strFieldName) . '" value="' . esc_attr($strFieldValue) . '" />';
    }

    if (isset($request->PaymentURL)) {
        try {
            wc_reduce_stock_levels($order_id);
        } catch (Exception $e) {
            // Логирование ошибки
        }
        setcookie('tbankReturnUrl', $this->get_return_url($order), time() + 3600, "/");
        wp_redirect($request->PaymentURL);
    } else {
        echo '<p>' . LanguageTBank::get(LanguageTBank::REQUEST_TO_PAYMENT) . '</p>';
    }
}

То есть, добавили к order_id метку времени и все заработало! Да, тут можно еще было поколдовать и сильнее усложнить “соль” order_id. Ведь потенциально, если 2 пользователя в ту одну и ту же секунду захотят совершить оплату, то будет ошибка. Вероятность такого мала, так как магазин специфический, поэтому метки по времени было достаточно. Но вы можете доработать этот нюанс.

Заключение

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

Поделитесь с друзьями