Создание платёжного шлюза WooCommerce

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

Этот урок я писал на английском языке ещё супер-давно в Стокгольме, и вот наконец решил обновить и опубликовать его на русском.

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

А в конце урока вас ждёт небольшой бонус – выступление разработчика из нашей команды на конференции WordCamp Saint Petersburg 2019 с докладом про разработку платёжных шлюзов.

Ну погнали ⚡️

Шаг 1. Создание плагина

Платёжный шлюз 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
Наш плагин уже появится на странице Плагинов в админке и вы даже можете его сразу активировать.

Шаг 2. Платёжные шлюзы – это на самом деле PHP-классы. Структура класса.

Когда мы говорим о создании платёжного плагина для 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() {
 
		...
 
	 	}
 	}
}

Шаг 3. Настройки (опции) платёжного шлюза.

Вообще в этом шаге подробно рассмотрим сразу два метода – __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 > Настройки > Платежи.

настройки платёжного плагина WooCommerce

Шаг 4. Форма для ввода данных карт

Перед тем, как переходить к коду ниже, хотел бы обратить ваше внимание на несколько ключевых моментов:

  • Если вы создаёте платёжный плагин для платёжной системы / банка, у которых все действия происходят на их сайте (тот же PayPal), то просто можете пропустить этот шаг, удалив из своего класса методы payment_fields() и validate_fields().
  • Также в этом уроке я исхожу из того, что вы используете платёжную систему / банк, который получает данные введённых пользователем карт и превращает их в токен, который уже отправляется формой. Форма не должна отправлять данные карт в ваш PHP-файл, поэтому вы не имеете права добавлять атрибуты name для полей ввода карты!

Как это всё происходит по шагам:

  1. Пользователь вводит данные своей карты и нажимает кнопку «Подтвердить заказ».
  2. Мы предотвращаем отправку формы при помощи JavaScript-события checkout_place_order и вместо этого отправляем платёжную информацию непосредственно в банк.
  3. Если все данные верны, то банк возвращает нам токен, который мы и вставляем в форму в виде скрытого поля.
  4. Теперь при помощи JavaScript отправляем форму
  5. Используем внутри PHP токен для создания платежи при помощи API банка.

Вроде много шагов, но происходит это в течение пары секунд.

4.1 Дополнительный JavaScript

В третьем шаге мы уже законнектили метод 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().

4.2 Получаем токен через JavaScript

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

// функция, срабатывающая при успешном получении токена
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 );
 
});

4.3 Сама форма с полями ввода карты

При помощи метода payment_fields() вы можете спокойно сделать такую форму с вводом данных карты прямо на странице оформления заказа WooCommerce:

форма ввода данных карты платёжного шлюза 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>';
 
}

Шаг 5. Обработка платежей

5.1 Валидация полей

Хочу вам показать, как это работает на примере поля «Имя», хотя конечно обычные поля страницы оформления заказа валидируются по-другому.

public function validate_fields(){
 
	if( empty( $_POST[ 'billing_first_name' ]) ) {
		wc_add_notice(  'Имя обязательно для заполнения!', 'error' );
		return false;
	}
	return true;
 
}

5.2 Создание платежа при помощи API

О, а сейчас будет много текста 🙃

  • Как только вы получили объект заказа при помощи 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() вы можете сразу добавляет заметки к заказу, как для администраторов магазина (будут видны в админке), так и непосредственно для покупателя.
  • Так как в нашем уроке мы рассматриваем более интересный случай с обработкой платежей через API непосредственно на нашем интернет-магазине, то мы используем wp_remote_post() и подобные функции для подключения к API, однако, если у вас обработка платежей будет происходить на стороне банка, то это всё упрощает и вам достаточно лишь построить URL для редиректа при помощи add_query_arg() и редиректнуть при помощи wp_redirect().
  • $order->get_total() нам нужен для получения общей суммы заказа.
  • get_woocommerce_currency() – для получения валюты интернет-магазина.
  • Некоторые платёжные системы требуют передать ещё и список товаров, это легко делается при помощи $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;
	}
 
}

5.3 В случае, если нам нужно получить какой-то ответ от платёжной системы / банка и обработать его на сайте (типо PayPal IPN)

Тут мы сейчас вкратце поговорим о том ситуации, когда обработка платежа происходит на стороне платёжной системы (или банка). То есть в нашем платёжном шлюзе мы сформировали все параметры, осуществили редирект и всё. Больше доступа к тому, что происходит в процессе оплаты, у нас нет.

Именно для таких ситуаций и были придуманы уведомления о платежах. То есть когда платёж совершён, банка отправляет уведомление по определённому 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 в Санкт-Петербурге, выступление ведущего разработчика из нашей команды, Антона:

Миша

Впервые познакомился с WordPress в 2009 году. Организатор и спикер на конференциях WordCamp. Преподаватель в школе Нетология.

Пишите, если нужна помощь с сайтом или разработка с нуля.

Комментарии — 13

Чтобы оставить комментарий, пожалуйста, зарегистрируйтесь или войдите.

Миша Рудрастых и WordPress

Полезности из мира WordPress и жизни студии.

Мой телеграм-канал