Содержание
Биллинг: Подключение платежной системы
В биллинге используется модульная система для интеграции с платежными системами. Для того чтобы подключить новую систему необходимо написать модуль для нее.
Выбор системного имени
Каждая платежная система в биллинге имеет системное имя, которое может состоять из одного латинского слова и цифр. Системное имя используется в именах файлов и классов.
В качестве примера, мы будем считать что наша новая платежная система называется «New Payment System», а ее системное имя – newpayment
. Здесь и далее newpament
нужно будет заменять на системное имя вашей платежной системы.
Файлы и папки
Модули платежных систем хранятся в папке system/controllers/billing/systems
. Внутри этой папки необходимо создать папку для новой платежной системы. Название папки совпадает с системным именем.
Например, system/controllers/billing/systems/newpayment
.
В папке системы нужно создать два PHP-файла:
options.form.php | Файл, содержащий описание формы настроек платежной системы. |
newpayment.php | Основной файл, внутри которого будет находиться класс модуля для данной платежной системы. Название файла совпадает с системным именем. |
Языковой файл
Платежная система может иметь языковой файл, в котором находятся необходимые тексты (например, названия опций).
Языковой файл системы должен иметь адрес: languages/язык/controllers/billing/systems/newpayment.php
.
Названия констант в языковом файле должны начинаться с префикса LANG_BILLING_SYSTEM_NEWPAYMENT_
.
Пример языкового файла:
<?php define('LANG_BILLING_SYSTEM_NEWPAYMENT_SHOP', 'Идентификатор магазина'); define('LANG_BILLING_SYSTEM_NEWPAYMENT_KEY', 'Ключ цифровой подписи');
Настройки платежной системы
В файле system/controllers/billing/systems/newpayment/options.form.php
нужно создать стандартную форму с полями для опций. Эти поля будут выводиться в форме настроек платежной системы в админке компонента.
Класс формы должен называться в формате form{системное-имя-с-большой-букввы}SystemOptions
.
Для нашего примера это formNewpaymentSystemOptions
Имена полей формы должны начинаться с префикса options:
.
Пример файла формы настроек:
<?php class formNewpaymentSystemOptions extends cmsForm{ public function init(){ return array( 'options' => array( 'type' => 'fieldset', 'childs' => array( new fieldString('options:shop_id', array( 'title' => LANG_BILLING_SYSTEM_NEWPAYMENT_SHOP, 'rules' => array( array('required') ) )), new fieldString('options:key', array( 'title' => LANG_BILLING_SYSTEM_NEWPAYMENT_KEY, 'is_password' => true, 'rules' => array( array('required') ) )), ) ) ); } }
Класс платежной системы
В файле system/controllers/billing/systems/newpayment/newpayment.php
должен быть определен класс платежной системы.
Класс должен называться в формате system{системное-имя-с-большой-букввы}
и наследоваться от класса billingPaymentSystem
.
Например:
class systemNewpayment extends billingPaymentSystem { }
Внутри класса могут быть определены несколько методов. Ниже подробно рассмотрим каждый из них.
Описание параметров методов
Многие методы используют одни и те же входящие параметры, поэтому опишем их сразу:
Параметр | Значение |
---|---|
$order | Массив с информацией о заказе на пополнение баланса. Содержит поля: id - номер заказа, summ - сумма заказа в реальной валюте, amount - сумма заказа во внутренней валюте, description - описание заказа |
$options | Массив с настройками платежной системы |
$rate | Курс валюты платежной системы, по отношению к реальной валюте сайта. С его помощью можно получить сумму, выставляемую к оплате в данной платежной системе, по формуле: $order['summ'] * $rate |
$request | Объект типа cmsRequest, содержащий информацию о текущем запросе (GET/POST). Получить переменную из запроса можно с помощью метода: $request->get('имя переменной') |
$model | Модель компонента биллинг. Используется для вызова методов, изменяющих статус заказа |
Получение списка полей для платежной формы
public function getPaymentFormFields($order, $options, $rate=1)
Обычно, проведение платежа в любой системе начинается с отправки платежной формы на специальный URL, предоставленный системой. Этот URL указывается в настройках системы в админке биллинга. Форма же генерируется динамически.
Когда пользователь пополняет баланс, он сначала выбирает количество валюты для покупки и нажимает «Продолжить». После этого он попадает на страницу «Проверьте правильность заказа». На самом деле эта страница нужна потому, что именно на ней выводится платежная форма. И нажатие кнопки «Перейти к оплате» отправляет платежную форму на нужный URL.
Метод getPaymentFormFields()
возвращает набор полей, которые будут добавлены в платежную форму. Этот набор уникален для каждой системы. Набор возвращается в виде массива, в котором ключи это названия полей, а элементы – значения.
Например, если наша платежная система требует наличия в форме полей shop_id
и summ
, то метод может выглядеть так:
public function getPaymentFormFields($order, $options, $rate=1){ // вычисляем сумму к оплате, по курсу $summ = number_format(round($order['summ'] * $rate, 2), 2, '.', ''); // получаем id магазина из настроек $shop_id = $options['shop_id']; return array( 'shop_id' => $shop_id, 'summ' => $summ ); }
Кроме того, в платежную форму можно добавить интерактивные поля, значения которых должен будет заполнить пользователь. В этом случае, в качестве значения поля в возвращаемом массиве используется экземпляр класса поля. Например:
return array( 'summ' => $summ, 'order_id' => $order['id'], 'comment' => $order['description'], 'phone' => new fieldString('phone', array( 'title' => 'Номер телефона для выставления счета', 'hint' => 'В формате <strong>+79221234567</strong>', )) );
Дополнительная обработка данных после платежной формы
public function preparePayment($request, $model, $options, $rate=1)
Некоторые платежные системы используют более сложные механизмы для запуска платежа, чем простая отправка формы. Поэтому предусмотрена возможность отдать модулю платежной системы полный контроль над отправкой запроса о платеже.
Схема реализуется в три этапа:
- В качестве платежного URL в настройках системы указывается:
billing/prepare/{системное-имя}
, напримерbilling/prepare/newpayment
; - Платежная форма по-прежнему формируется, но отправляется уже не в систему, а на внутренний URL, указанный выше;
- По данному URL срабатывает метод
preparePayment()
в нашем классе, и этот метод самостоятельно производит общение с реальной платежной системой (например, с помощью CURL) и редиректит куда требуется.
Такая схема используется в случаях, когда:
- Запрос о платеже нужно послать на несколько адресов одновременно (чего нельзя добиться с помощью простой формы);
- Для проведения платежа необходима дополнительная информация от пользователя (например, Qiwi требует ввода номера телефона. Поэтому в платежной форме этот номер запрашивается и затем отправляется на внутренний URL, где уже происходит обработка и отправка всех данных в Qiwi).
Примеры реализации описанного подхода вы можете посмотреть в коде модулей для систем Test и Qiwi из стандартной поставки биллинга.
Переопределение платежного URL
public function getPaymentURL($options)
Некоторые платежные системы используют динамический URL для отправки платежа. Тогда его не получится просто указать в настройках, т.к. при каждом платеже он будет разным.
Специально на этот случай в классе может быть описан метод getPaymentURL()
, который возвращает URL для отправки платежной формы. Если этого метода нет - используется URL из настроек платежной системы.
Пример метода из модуля для системы OnPay:
public function getPaymentURL($options){ return "https://secure.onpay.ru/pay/{$options['merchant_login']}"; }
Получение оповещения о платеже
public function processPayment($request, $model, $options, $rate=1)
Большинство платежных систем умеют сообщать об оплате заказа путем вызова определенного URL на сайте магазина. Чаще всего в настройках мерчантов он называется как Result URL или Status URL. Вызов этого URL происходит в фоновом режиме, незаметно для пользователя и без использования его браузера.
В биллинге данный URL уникален для каждой платежной системы. Он имеет вид: http://example.com/billing/process/newpayment, где example.com – адрес сайта, newpayment – системное имя платежной системы.
При вызове данного URL в классе модуля системы запускается метод processPayment()
.
Внутри метода обычно реализуется следующий алгоритм:
- Получаем данные, переданные платежной системой (номер заказа, сумма, статус оплаты и т.п.);
- По номеру заказа получаем информацию о заказе из базы;
- Сверяем правильность данных (что сумма заказа не изменена, цифровая подпись совпадает и т.п.);
- Если все верно – отмечаем заказ как оплаченный (на внутренний баланс пользователя при этом зачисляется купленная им внутренняя валюта);
- Возвращаем ответ в формате, требуемом платежной системой.
Данные, передаваемые на Result URL каждой платежной системой уникальны. Их описание нужно смотреть в документации по API платежной системы. Получение данных из запроса (GET/POST) происходит с помощью объекта $request
, например:
$order_id = $request->get('order_id');
Получение заказа по известному ID осуществляется с помощью модели:
$order = $model->getOperation( $order_id );
Смена статуса заказа на «оплаченный» происходит также с помощью модели:
$model->acceptPayment( $order_id );
Пример реализации метода для системы Robokassa:
public function processPayment($request, $model, $options, $rate=1){ // Получаем данные, переданные платежной системой // на наш Result URL $op_id = $request->get('InvId'); // - id заказа $out_summ = $request->get('OutSum'); // - сумма $out_signature = $request->get('SignatureValue'); // - подпись // Проверяем что получен id заказа if (!$op_id) { echo LANG_BILLING_ERR_ORDER_ID; return false; } // Получаем заказ из базы по id $operation = $model->getOperation($op_id); // Проверяем что такой заказ найден if (!$operation) { echo LANG_BILLING_ERR_ORDER_ID; return false; } // Проверяем что этот заказ еще не оплачен if ($operation['status'] != billing::STATUS_CREATED) { echo LANG_BILLING_ERR_ORDER_ID; return false; } // Вычисляем сумму к оплате, по курсу платежной // системы к реальной валюте нашего сайта $summ = round($operation['summ'] * $rate, 2); // Если требуемая сумма не совпала с суммой, // оплаченной пользователем, значит произошла подмена или ошибка if ($summ != $out_summ) { echo LANG_BILLING_ERR_SUMM; return false; } // Вычисляем контрольную подпись по алгоритму, // описанному в документации платежной системы Robokassa $sig = array($out_summ, $op_id, strrev($options['password1'])); $sig = mb_strtoupper(md5(implode(':', $sig))); // Проверяем что подписи совпадают if ($sig != $out_signature) { echo LANG_BILLING_ERR_SIG; return false; } // Принимаем платеж как успешно завершенный // Баланс пользователя при этом пополняется $model->acceptPayment($op_id); return true; }
Получение оповещения об успехе платежа
После успешного проведения платежа обычно платежные системы переадресуют пользователя на специальный URL магазина, по которому расположена страница с текстом типа «Оплата успешно принята». Обычно такой URL называется в мерчантах как Success URL.
В биллинге данный URL уникален для каждой платежной системы. Он имеет вид: http://example.com/billing/success/newpayment, где example.com – адрес сайта, newpayment – системное имя платежной системы.
Чаще всего, помимо простой переадресации, платежные системы также высылают на этот адрес ID оплаченного заказа.
В реальной жизни иногда возможны ситуации, когда переадресация на Success URL происходит раньше, чем вызов Result URL. То есть когда платежная система уже знает что платеж прошел и отправляет пользователя обратно на сайт, но при этом самому сайту сообщение о платеже еще не пришло.
Поэтому, для избежания неприятностей, на Success URL мы должны удостовериться что заказ оплачен и баланс пользователя пополнен. И если это не так, то заставить пользователя ждать пока сайт не получит уведомление о проведенном платеже.
Поскольку номер заказа приходит на Success URL вместе с пользователем, в модуле платежной системы должен быть определен метод:
public function getSuccessOrderId($request)
Этот метод должен получать из объекта $request
значение ID заказа, передаваемое платежной системой на Success URL, и возвращать его. Используя полученный ID биллинг найдет заказ и проверит его статус. Если заказ окажется по-прежнему не оплаченным, биллинг будет проверять его статус каждые несколько секунд, до тех пор пока уведомление об оплате не поступит. Пользователь при этом будет видеть просьбу подождать.
Пример метода:
public function getSuccessOrderId($request){ return $request->get('InvId'); }
Получение оповещения о неудаче платежа
После неудачи в проведении платежа обычно платежные системы переадресуют пользователя на специальный URL магазина, по которому расположена страница с текстом типа «Ошибка оплаты». Обычно такой URL называется в мерчантах как Fail URL.
В биллинге данный URL един для всех платежных систем. Он имеет вид: http://example.com/billing/fail, где example.com – адрес сайта.
Никаких дополнительных действий в модулей платежной системы для обработки неудачных платежей не требуется.
Примеры подключений
С примерами реализаций модулей для несколько платежных систем вы можете ознакомиться в папке system/controllers/billing/systems
из комплекта поставки биллинга.