Содержание
Биллинг: Подключение платежной системы
В биллинге используется модульная система для интеграции с платежными системами. Для того чтобы подключить новую систему необходимо написать модуль для нее.
Выбор системного имени
Каждая платежная система в биллинге имеет системное имя, которое может состоять из одного латинского слова и цифр. Системное имя используется в именах файлов и классов.
В качестве примера, мы будем считать что наша новая платежная система называется «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 [ 'options' => [ 'type' => 'fieldset', 'childs' => [ new fieldString('options:shop_id', [ 'title' => LANG_BILLING_SYSTEM_NEWPAYMENT_SHOP, 'rules' => [ ['required'] ] ]), new fieldString('options:key', [ 'title' => LANG_BILLING_SYSTEM_NEWPAYMENT_KEY, 'is_password' => true, 'rules' => [ ['required'] ] ]) ] ] ]; } }
Класс платежной системы
В файле system/controllers/billing/systems/newpayment/newpayment.php должен быть определен класс платежной системы.
Класс должен называться в формате system{системное-имя-с-большой-букввы} и наследоваться от класса billingPaymentSystem.
Например:
class systemNewpayment extends billingPaymentSystem { }
Внутри класса могут быть определены несколько методов. Ниже подробно рассмотрим каждый из них.
Описание параметров методов
Многие методы используют одни и те же входящие параметры, поэтому опишем их сразу:
| Параметр | Значение |
|---|---|
| $order | Массив с информацией о заказе на пополнение баланса. Содержит поля: id - номер заказа, summ - сумма заказа в реальной валюте, amount - сумма заказа во внутренней валюте, description - описание заказа |
| $request | Объект типа cmsRequest, содержащий информацию о текущем запросе (GET/POST). Получить переменную из запроса можно с помощью метода: $request->get('имя переменной') |
| $model | Модель компонента биллинг. Используется для вызова методов, изменяющих статус заказа |
Во всех методах через свойство $this→options доступны опции платёжной системы.
Сумма с учётом курса платёжной системы
public function getPaymentOrderSumm($summ)
Этот метод возвращает сумму к оплате, учитывая курс платёжной системы, указанный в опциях.
Получение списка полей для платежной формы
public function getPaymentFormFields($order)
Обычно, проведение платежа в любой системе начинается с отправки платежной формы на специальный URL, предоставленный системой. Этот URL указывается в настройках системы в админке биллинга. Форма же генерируется динамически.
Когда пользователь пополняет баланс, он сначала выбирает количество валюты для покупки и нажимает «Продолжить». После этого он попадает на страницу «Проверьте правильность заказа». На самом деле эта страница нужна потому, что именно на ней выводится платежная форма. И нажатие кнопки «Перейти к оплате» отправляет платежную форму на нужный URL.
Метод getPaymentFormFields() возвращает набор полей, которые будут добавлены в платежную форму. Этот набор уникален для каждой системы. Набор возвращается в виде массива, в котором ключи это названия полей, а элементы – значения.
Например, если наша платежная система требует наличия в форме полей shop_id и summ, то метод может выглядеть так:
public function getPaymentFormFields($order) { return [ // id магазина из настроек 'shop_id' => $this->options['shop_id'], // вычисляем сумму к оплате, по курсу 'summ' => $this->getPaymentOrderSumm($order['summ']) ]; }
Кроме того, в платежную форму можно добавить интерактивные поля, значения которых должен будет заполнить пользователь. В этом случае, в качестве значения поля в возвращаемом массиве используется экземпляр класса поля. Например:
return [ 'summ' => $summ, 'order_id' => $order['id'], 'comment' => $order['description'], 'phone' => new fieldString('phone', [ 'title' => 'Номер телефона для выставления счета', 'hint' => 'В формате <b>+79221234567</b>' ]) ];
Дополнительная обработка данных после платежной формы
public function preparePayment(cmsRequest $request, modelBilling $model)
Некоторые платежные системы используют более сложные механизмы для запуска платежа, чем простая отправка формы. Поэтому предусмотрена возможность отдать модулю платежной системы полный контроль над отправкой запроса о платеже.
Схема реализуется в три этапа:
- В качестве платежного URL в настройках системы указывается:
billing/prepare/{системное-имя}, напримерbilling/prepare/newpayment; - Платежная форма по-прежнему формируется, но отправляется уже не в систему, а на внутренний URL, указанный выше;
- По данному URL срабатывает метод
preparePayment()в нашем классе, и этот метод самостоятельно производит общение с реальной платежной системой (например, с помощью CURL) и редиректит куда требуется.
Такая схема используется в случаях, когда:
- Запрос о платеже нужно послать на несколько адресов одновременно (чего нельзя добиться с помощью простой формы);
- Для проведения платежа необходима дополнительная информация от пользователя (например, Qiwi требует ввода номера телефона. Поэтому в платежной форме этот номер запрашивается и затем отправляется на внутренний URL, где уже происходит обработка и отправка всех данных в Qiwi).
Примеры реализации описанного подхода вы можете посмотреть в коде модулей для систем Test и Yookassa из стандартной поставки Биллинга.
Если нужен редирект, то метод должен вернуть строку с URL для редиректа. НЕ выполняйте редирект в теле метода.
Если в методе в вашей логике обработки возникла ошибка, то в этом методе необходимо вернуть её конструкцией:
return $this->error(LANG_BILLING_ERR_ORDER_ID);
Где LANG_BILLING_ERR_ORDER_ID это языковая константа, вместо её вы можете использовать любой произвольный текст.
Переопределение платежного URL
public function getPaymentURL()
Некоторые платежные системы используют динамический URL для отправки платежа. Тогда его не получится просто указать в настройках, т.к. при каждом платеже он будет разным.
Специально на этот случай в классе может быть описан метод getPaymentURL(), который возвращает URL для отправки платежной формы. Если этого метода нет - используется URL из настроек платежной системы.
Пример метода из модуля для системы OnPay:
public function getPaymentURL(){ return "https://secure.onpay.ru/pay/{$this->options['merchant_login']}"; }
Обратите внимание! В админке в URL можно указывать значения опций в фигурных скобках, например, учитывая пример выше, URL в настройках может быть указан как:
https://secure.onpay.ru/pay/{merchant_login}
И для таких простых случаев не требуется переназначать этот метод. {merchant_login} превратится в значение $this→options['merchant_login'].
Получение оповещения о платеже
public function processPayment(cmsRequest $request, modelBilling $model)
Большинство платежных систем умеют сообщать об оплате заказа путем вызова определенного 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 );
Смена статуса заказа на «оплаченный» происходит также с помощью модели:
if (!$model->acceptPayment($operation['id'])) { return $this->log(LANG_BILLING_ERR_TRANS); } return true;
Пример реализации метода для системы Robokassa:
public function processPayment(cmsRequest $request, modelBilling $model) { // Получаем данные, переданные платежной системой // на наш Result URL $op_id = $request->get('InvId', 0); // id заказа $out_summ = $request->get('OutSum', ''); // сумма $out_signature = $request->get('SignatureValue', ''); // подпись // Проверяем что получен id заказа if (!$op_id) { return $this->log(LANG_BILLING_ERR_ORDER_ID); } // Получаем заказ из базы по id $operation = $model->getOperation($op_id); // Проверяем что такой заказ найден if (!$operation) { return $this->log(LANG_BILLING_ERR_ORDER_ID); } // Проверяем что этот заказ еще не оплачен if ($operation['status'] != modelBilling::STATUS_CREATED) { return $this->log(LANG_BILLING_ERR_STATUS); } // Вычисляем сумму к оплате, по курсу платежной // системы к реальной валюте нашего сайта $summ = $this->getPaymentOrderSumm($operation['summ']); // Если требуемая сумма не совпала с суммой, // оплаченной пользователем, значит произошла подмена или ошибка if ($summ != $out_summ) { return $this->log(LANG_BILLING_ERR_SUMM . 'OutSum: '.$out_summ); } // Вычисляем контрольную подпись по алгоритму, // описанному в документации платежной системы Robokassa $password2 = !empty($this->options['test_mode']) ? $this->options['password2_test'] : $this->options['password2']; $sig = [$out_summ, $op_id, $password2]; $sig = strtoupper(hash($this->hash_type, implode(':', $sig))); // Проверяем что подписи совпадают if ($sig !== $out_signature) { return $this->log(LANG_BILLING_ERR_SIG); } // Принимаем платеж как успешно завершенный // Баланс пользователя при этом пополняется if (!$model->acceptPayment($operation['id'])) { return $this->log(LANG_BILLING_ERR_TRANS); } return 'OK' . $op_id; }
Метод processPayment может вернуть:
- Строку. Это строка будет выведена в теле HTTP ответа;
- Массив. В формате
['headers' ⇒ [], 'body' ⇒ 'Text'], где ключ headers - это массив HTTP заголовков, которые надо послать в ответе, ключ body - тело HTTP ответа. - false. В этом случае HTTP ответ будет с кодом 500 и телом ответа «An error occurred in payment confirmation processing».
Получение оповещения об успехе платежа
После успешного проведения платежа обычно платежные системы переадресуют пользователя на специальный 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(cmsRequest $request)
Этот метод должен получать из объекта $request значение ID заказа, передаваемое платежной системой на Success URL, и возвращать его. Используя полученный ID биллинг найдет заказ и проверит его статус. Если заказ окажется по-прежнему не оплаченным, биллинг будет проверять его статус каждые несколько секунд, до тех пор пока уведомление об оплате не поступит. Пользователь при этом будет видеть просьбу подождать.
Пример метода:
public function getSuccessOrderId(cmsRequest $request) { return $request->get('InvId'); }
Получение оповещения о неудаче платежа
После неудачи в проведении платежа обычно платежные системы переадресуют пользователя на специальный URL магазина, по которому расположена страница с текстом типа «Ошибка оплаты». Обычно такой URL называется в мерчантах как Fail URL.
В биллинге данный URL един для всех платежных систем. Он имеет вид: http://example.com/billing/fail, где example.com – адрес сайта.
Никаких дополнительных действий в модулей платежной системы для обработки неудачных платежей не требуется.
Вспомогательные методы
public function log($text)
Часто требуется как для отладки платёжной системы, так и для разбора возможных сбоев, вести логи. Для этого существует метод $this→log('Error Text').
Метод записывает в лог платёжной системы указанную ошибку, добавляя запись в конец файла вместе с текущей датой и временем. В переменную $text можно передавать строку или массив. В случае если передан массив, он будет преобразован в JSON.
Логи записываются в директории /cache/billing/имя_платёжной_системы_pay_api.log
Краткий лог из последних 100 записей можно посмотреть в админке в списке платёжных систем.
public function error(string $text)
Метод записывает ошибку в память и возвращает NULL.
Примеры подключений
С примерами реализаций модулей для несколько платежных систем вы можете ознакомиться в папке system/controllers/billing/systems из комплекта поставки биллинга.
