Сейчас я покажу вам подробно и пошагово, как создать платёжный шлюз для WooCommerce.
Этот урок я писал на английском языке ещё супер-давно в Стокгольме, и вот наконец решил обновить и опубликовать его на русском.
Наша команда разрабатывает платёжные шлюзы WooCommerce для банков уже несколько лет, напишите нам, чтобы узнать подробности.
А в конце урока вас ждёт небольшой бонус – выступление разработчика из нашей команды на конференции WordCamp Saint Petersburg 2019 с докладом про разработку платёжных шлюзов.
Ну погнали ⚡️
Платёжный шлюз WooCommerce – это прежде всего плагин для WordPress.
Когда-то давно, когда я впервые услышал про создание плагина WordPress, я думал это что-то очень сложное, но по сути для плагина достаточно лишь создать один файл и добавить туда пару строчек кода. Но можете прочитать моё руководство по созданию плагинов.
Давайте в папке wp-content/plugins
создадим файл truemisha-gateway.php
и добавим в него код, который вы увидите ниже. Если у плагина будет больше чем один файл, то лучше ещё и поместить этот файл в одноимённую папку truemisha-gateway/truemisha-gateway.php
<?php
/*
* Plugin Name: Платёжный плагин для WooCommerce от Миши
* Plugin URI: https://misha.agency/woocommerce/sozdanie-platjozhnogo-shljuza.html
* Description: Принимайте оплату по картам у себя на сайте!
* Author: Миша Рудрастых
* Author URI: https://misha.agency
* Version: 1.0.1
*
Как только вы это сделаете, вы можете заглянуть в админке WordPress на страницу Плагины и лицезреть там ваш свеженький плагин!
Когда мы говорим о создании платёжного плагина для WooCommerce, мы говорим о расширении уже существующего класса WC_Payment_Gateway
.
Каждый метод класса описан максимально подробно ниже. Вообще вы можете начать с того, что скопируете весь код, который идёт после этого абзаца в свой файлик плагина, который мы с вами создали в первом шаге.
/* * Этот фильтр-хук позволяет зарегистрировать наш PHP-класс в качестве платёжного шлюза WooCommerce */ add_filter( 'woocommerce_payment_gateways', 'truemisha_register_gateway_class' ); function truemisha_register_gateway_class( $gateways ) { $gateways[] = 'WC_Truemisha_Gateway'; // название вашего класса, добавляем его в общий массив return $gateways; } /* * А дальше идёт сам класс, тоже обратите внимание, что он внутри хука plugins_loaded */ add_action( 'plugins_loaded', 'truemisha_gateway_class' ); function truemisha_gateway_class() { class WC_Truemisha_Gateway extends WC_Payment_Gateway { /** * Это конструктор класса, о нём мы с вами ещё поговорим в 3-м шаге урока */ public function __construct() { ... } /** * Опции платёжного шлюза, тоже подробнее в 3-м шаге */ public function init_form_fields(){ ... } /** * А этот метод пригодится, если захотите добавить форму ввода данных карты прямо на сайт * Подробнее об этом мы поговорим в 4-м шаге */ public function payment_fields() { ... } /* * Для подключения дополнительных CSS и JS, нужны также для формы ввода карт и создания токена для них */ public function payment_scripts() { ... } /* * Валидация полей, подробнее в шаге 5 */ public function validate_fields() { ... } /* * Тут мы будем обрабатывать платёж, подробнее в шаге 5 */ public function process_payment( $order_id ) { ... } /* * В том случае, если нам нужен хук, к которому будет обращаться банк для передачи ответа, типа PayPal IPN */ public function truemisha_webhook() { ... } } }
Вообще в этом шаге подробно рассмотрим сразу два метода – __construct()
и init_form_fields()
, хотя, как вы возможно поняли из заголовка, то основная цель этого шага – создать опции (страницу настроек) в админке WordPress для нашего платёжного плагина.
public function __construct() { $this->id = 'truemisha'; // ID платёжног шлюза $this->icon = ''; // URL иконки, которая будет отображаться на странице оформления заказа рядом с этим методом оплаты $this->has_fields = true; // если нужна собственная форма ввода полей карты $this->method_title = 'Платёжный шлюз от Миши'; $this->method_description = 'Описание платёжного шлюза от Миши'; // будет отображаться в админке // платёжные плагины могут поддерживать подписки, сохранённые карты, возвраты // но в пределах этого урока начнём с простых платежей, хотя в виде ниже будет чуть подробнее и о другом $this->supports = array( 'products' ); // тут хранятся все поля настроек $this->init_form_fields(); // инициализируем настройки $this->init_settings(); // название шлюза $this->title = $this->get_option( 'title' ); // описание $this->description = $this->get_option( 'description' ); // включен или выключен $this->enabled = $this->get_option( 'enabled' ); // работает в тестовом режиме (sandbox) или нет $this->testmode = 'yes' === $this->get_option( 'testmode' ); // и естественно отдельные ключи для тестового и рабочего режима шлюза $this->private_key = $this->testmode ? $this->get_option( 'test_private_key' ) : $this->get_option( 'private_key' ); $this->publishable_key = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' ); // Хук для сохранения всех настроек, как видите, можно еще создать собственный метод process_admin_options() и закастомить всё add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); // Если будет генерировать токен из данных карты, то по-любому нужно будет подключать какой-то JS add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) ); // ну и хук тоже можете тут зарегистрировать // add_action( 'woocommerce_api_{webhook name}', array( $this, 'truemisha_webhook' ) ); }
Дальше давайте настроим опции нашего шлюза, обратите внимание, что в зависимости от платёжной системы или банка эти опции могут полностью отличаться. Я решил использовать несколько наиболее очевидных и часто используемых опций.
public function init_form_fields(){ $this->form_fields = array( 'enabled' => array( 'title' => 'Включен/Выключен', 'label' => 'Включить Мишин платёжный плагин', 'type' => 'checkbox', 'description' => '', 'default' => 'no' ), 'title' => array( 'title' => 'Заголовок', 'type' => 'text', 'description' => 'Это то, что пользователь увидит как название метода оплаты на странице оформления заказа.', 'default' => 'Оплатить картой', 'desc_tip' => true, ), 'description' => array( 'title' => 'Описание', 'type' => 'textarea', 'description' => 'Описание этого метода оплаты, которое будет отображаться пользователю на странице оформления заказа.', 'default' => 'Оплатите при помощи карты легко и быстро.', ), 'testmode' => array( 'title' => 'Тестовый режим', 'label' => 'Включить тестовый режим', 'type' => 'checkbox', 'description' => 'Хотите сначала протестировать с тестовыми ключами API?', 'default' => 'yes', 'desc_tip' => true, ), 'test_publishable_key' => array( 'title' => 'Тестовый публичный ключ', 'type' => 'text' ), 'test_private_key' => array( 'title' => 'Тестовый приватный ключ', 'type' => 'password', ), 'publishable_key' => array( 'title' => 'Публичный ключ', 'type' => 'text' ), 'private_key' => array( 'title' => 'Приватный ключ', 'type' => 'password' ) ); }
Можете теперь осторожно вставлять эти методы в наш платёжный плагин, который мы создали ещё в первом шаге. Неиспользуемые пока что методы не забудьте закомментировать.
И если всё сделано правильно, то активируем плагин, переходим в WooCommerce > Настройки > Платежи.
Перед тем, как переходить к коду ниже, хотел бы обратить ваше внимание на несколько ключевых моментов:
payment_fields()
и validate_fields()
.Как это всё происходит по шагам:
checkout_place_order
и вместо этого отправляем платёжную информацию непосредственно в банк.Вроде много шагов, но происходит это в течение пары секунд.
В третьем шаге мы уже законнектили метод payment_scripts
к хуку WordPress wp_enqueue_scripts
.
public function payment_scripts() { // выходим, если находимся не на странице оформления заказа if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) { return; } // наш платёжный плагин отключен? ничего не делаем if ( 'no' === $this->enabled ) { return; } // также нет смысла подключать JS, если плагин не настроен, не указаны API ключи if ( empty( $this->private_key ) || empty( $this->publishable_key ) ) { return; } // проверяем также ssl, если плагин работает не в тестовом режиме if ( ! $this->testmode && ! is_ssl() ) { return; } // предположим, что это какой-то JS банка для обработки данных карт wp_enqueue_script( 'bank_js', 'https://урл-какого-то-банка/api/token.js' ); // а это наш произвольный JavaScript, дополняющий token.js wp_register_script( 'woocommerce_truemisha', plugins_url( 'truemisha.js', __FILE__ ), array( 'jquery', 'bank_js' ) ); // допустим, что нам в JavaScript коде понадобится публичный API ключ, передаём его вот так wp_localize_script( 'woocommerce_truemisha', 'truemisha_params', array( 'publishableKey' => $this->publishable_key ) ); wp_enqueue_script( 'woocommerce_truemisha' ); }
Используемые в этом коде функции WordPress и WooCommerce: is_cart(), is_checkout(), wp_enqueue_script(), wp_register_script(), wp_localize_script(), is_ssl().
Ещё раз хочу напомнить, что тут код может сильно зависеть от платёжной системы или банка, для которого вы пишете шлюз, т.е. ниже это лишь примерный код.
// функция, срабатывающая при успешном получении токена var successCallback = function(data) { var checkout_form = $( 'form.woocommerce-checkout' ); // добавляем токен в скрытое поле в форме <input tyoe="hidden" id="truemisha_token" ... // console.log(data) тоже помогает checkout_form.find( '#truemisha_token' ).val( data.token ); // снимаем функцию tokenRequest с события отправки формы checkout_form.off( 'checkout_place_order', tokenRequest ); // отправляем форму checkout_form.submit(); }; // функция, срабатывающая, если при получении токена что-то пошло не так var errorCallback = function( data ) { console.log( data ); }; var tokenRequest = function() { // тут будет какая-то функция вашей платёжный системы или банка, которая соберёт все данные формы и отправит их // возможно тут вам понадобится публичный API ключ, который у нас уже лежит тут truemisha_params.publishableKey // ну и срабатывает successCallback() при успехе, или errorCallback() если что-то пошло не так return false; }; jQuery(function($){ var checkout_form = $( 'form.woocommerce-checkout' ); checkout_form.on( 'checkout_place_order', tokenRequest ); });
При помощи метода payment_fields()
вы можете спокойно сделать такую форму с вводом данных карты прямо на странице оформления заказа WooCommerce:
А это код, который я использовал:
public function payment_fields() { // окей, давайте выведем описание, если оно заполнено if ( $this->description ) { // отдельные инструкции для тестового режима if ( $this->testmode ) { $this->description .= ' ТЕСТОВЫЙ РЕЖИМ АКТИВИРОВАН. В тестовом режиме вы можете использовать тестовые данные карт, указанные в <a href="#" target="_blank">документации</a>.'; $this->description = trim( $this->description ); } // описание закидываем в теги <p> echo wpautop( wp_kses_post( $this->description ) ); } // я использую функцию echo(), но по сути можете закрыть тег PHP и выводить прямо как HTML echo '<fieldset id="wc-' . $this->id . '-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">'; // чтобы разработчики плагинов могли сюда что-то добавить, но не обязательно do_action( 'woocommerce_credit_card_form_start', $this->id ); // I recommend to use inique IDs, because other gateways could already use #ccNo, #expdate, #cvc echo '<div class="form-row form-row-wide"><label>Номер карты <span class="required">*</span></label> <input id="truemisha_ccNo" type="text" autocomplete="off"> </div> <div class="form-row form-row-first"> <label>Срок действия <span class="required">*</span></label> <input id="truemisha_expdate" type="text" autocomplete="off" placeholder="MM / ГГ"> </div> <div class="form-row form-row-last"> <label>Код (CVC) <span class="required">*</span></label> <input id="truemisha_cvv" type="password" autocomplete="off" placeholder="CVC"> </div> <div class="clear"></div>'; // чтобы разработчики плагинов могли сюда что-то добавить, но не обязательно do_action( 'woocommerce_credit_card_form_end', $this->id ); echo '<div class="clear"></div></fieldset>'; }
Хочу вам показать, как это работает на примере поля «Имя», хотя конечно обычные поля страницы оформления заказа валидируются по-другому.
public function validate_fields(){ if( empty( $_POST[ 'billing_first_name' ]) ) { wc_add_notice( 'Имя обязательно для заполнения!', 'error' ); return false; } return true; }
О, а сейчас будет много текста 🙃
wc_get_order()
, вы можете использовать его методы для получения информации о заказе, например get_billing_first_name()
, get_billing_country()
и get_billing_address_1()
для получения платёжной информации покупателя, все остальные методы можно найти в объекте заказа WC_Order, он кстати лежит в includes/class-wc-order.php
в папке WooCommerce. Хотя в то же время многое нам доступно и из массива $_POST
.$order->add_order_note()
вы можете сразу добавляет заметки к заказу, как для администраторов магазина (будут видны в админке), так и непосредственно для покупателя.wp_remote_post()
и подобные функции для подключения к API, однако, если у вас обработка платежей будет происходить на стороне банка, то это всё упрощает и вам достаточно лишь построить URL для редиректа при помощи add_query_arg() и редиректнуть при помощи wp_redirect().$order->get_total()
нам нужен для получения общей суммы заказа.$order->get_items()
.public function process_payment( $order_id ) { // вот тут мы получаем объект заказа $order = wc_get_order( $order_id ); /* * Массив с параметрами для взаимодействия с API */ $args = array( ... ); /* * Взаимодействие с API можно построить при помощи функции wp_remote_post() */ $response = wp_remote_post( '{payment processor endpoint}', $args ); // если нет ошибки при подключении if( ! is_wp_error( $response ) ) { $body = json_decode( $response['body'], true ); // в зависимости от банка коды ответа могут быть разные конечно же if ( $body['response']['responseCode'] == 'APPROVED' ) { // получили платёж $order->payment_complete(); // может быть добавим немного заметок для пользователя? // измените второй параметр на false, чтобы заметка была для админа $order->add_order_note( 'Заказ оплачен, спасибочки!', true ); // очищаем корзину WC()->cart->empty_cart(); // редиректим на страницу спасибо return array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ) ); } else { wc_add_notice( 'Попробуйте ещё раз, оплата не прошла.', 'error' ); return; } } else { wc_add_notice( 'Ошибка с подключением к API.', 'error' ); return; } }
Тут мы сейчас вкратце поговорим о том ситуации, когда обработка платежа происходит на стороне платёжной системы (или банка). То есть в нашем платёжном шлюзе мы сформировали все параметры, осуществили редирект и всё. Больше доступа к тому, что происходит в процессе оплаты, у нас нет.
Именно для таких ситуаций и были придуманы уведомления о платежах. То есть когда платёж совершён, банка отправляет уведомление по определённому URL с кодом успеха или фейла.
В WooCommerce такие урлы, на которые приходят уведомления от банка называются «вебхуками», хотя зависит от перевода, можете называть их «перехватчиками», если вам так больше нравится :)
Предположим, что у нас есть вот такой урл-перехватчик http://сайт/wc-api/{название вебхука}/
, причём названием может быть практически что угодно, как например paypal-payment-complete
или просто слово truemisha
.
После чего в наш конструктор класса WC_Truemisha_Gateway
мы добавляем вот такой экшен-хук:
add_action( 'woocommerce_api_truemisha', array( $this, 'truemisha_webhook' ) ); // add_action( 'woocommerce_api_{название вебхука}', array( $this, 'метод обработчик' ) );
И метод, обрабатывающий вебхук будет выглядеть примерно так:
public function truemisha_webhook() { $order = wc_get_order( $_GET[ 'id' ] ); // типо ID заказа приходят нам из банка $order->payment_complete(); // платёж совершён $order->reduce_order_stock(); // уменьшаем количество товаров в запасах update_option( 'webhook_debug', $_GET ); // чисто чтобы посмотреть, что в $_GET }
Это всё, что я хотел вам рассказать :)
Ну и конечно, если вам нужна помощь с разработкой такого шлюза, пишите, будем рады вам помочь.
Как и обещал, вот видео с конференции WordCamp в Санкт-Петербурге, выступление ведущего разработчика из нашей команды, Антона:
Чтобы оставить комментарий, пожалуйста, зарегистрируйтесь или войдите.
Спасибо большое за статью, как раз хотел разобраться в этой теме - видел много проектов на Upwork там где нужно интегрировать какую-то платёжную систему в WooCommerce. В целом статьи обычно хватает, чтобы понять что к чему и листинги кода упрощают этот процесс, но если в процессе урока происходит создание какого более менее завершённого продукта, например заготовка для темы или плагина, то логично бы создать отдельный репозиторий на GitHub, чтобы проще было отследить ошибки или улучшить код со временем.
Пожалуйста, рад, что вам она помогла!
Да, наверное
Миша, спасибо за статью, очень познавательно! Касательно пункта 5.3 я могу посоветовать любознательным посмотреть как написан плагин платежного шлюза для АБ (красный банк). Как раз там описан подобный функционал. Я думаю, что в плане безопасности, ввод данных карты предпочтительнее на стороне банка, а не сайта (открыл Америку).
Спасибо за дополнение! ⚡️
Добрый. Подскажите после добавления своего способа оплаты как сделать чтобы при нажатий кнопки купить например, перекидывало на тот сайт который мне нужен и оттуда получить callback и по нему уже работать на сайте?
Добрый день!
А вот в шаге 5.2 об этом упомянуто.
Привет, использовал Ваш пример как шаблон. Но немогу понять, почему нет редиректа после оплаты. После того, как я симулирую работу банка и вызываю вэбхук - статус заказа меняется на "Оплачен/В обработке" но редиректа со страницы оплаты на страницу з благодарностью не происходит.
Это у меня в конструкторе плагина:
Редирект на страницу оплаты:
Вэбхук:
пункт 4.2 это отдельны файл js делаем?
Ага
Приветствую Вас, Миша!
Тема, конечно, интересна. Но у меня при ряде сложившихся обстоятельствах ( нет возможности интегрироваться со Stripe.com ) возник некий залихватский вопрос:
Возможно ли создать подобный платежный шлюз на WooCommerce, организовав все платежи (оплату производят только иностранцы, сайт на английском) только на валютную карту на карточный счёт владельца сайта при условии, если у владельца нет банковского счета юридического лица - ?
Есть ли какое-нибудь решение по этому поводу - ?
Заранее благодарю за ответ.
Здравствуйте, Всеволод,
Скажу вам даже больше – я задаюсь этим вопросом даже при наличии банковского счёта юридического лица.
Вроде как решения есть, но я пока их никак не изучал. Ещё есть любимая robokassa, они там недавно обновили страницу оплаты, что стало не стыдно даже иностранцам показать.
Взял уже готовый шлюз, и решил переписать и не понимаю почему оплата происходит через страницу "Order", где детали заказа, т.е. должно сразу вести на страницу оплаты а он ведет на страницу проверки заказа(
Думаю что проблема тут https://github.com/Mofsy/wc-webmoney/blob/master/includes/class-wc-webmoney-method.php#L121 но не заню что не так тут(
Здравствуйте! У меня почему-то не отображаются способы оплаты, не кастомные не с плагинами, что может быть? Только стандартные