version opencart 3 (#1)

This commit is contained in:
malkoas 2018-10-02 18:56:58 +03:00 committed by GitHub
parent 7d6d3c454e
commit 29b1c81f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1160 additions and 2 deletions

BIN
RBKmoney.ocmod.zip Normal file

Binary file not shown.

164
README.md
View File

@ -1,2 +1,162 @@
# rbkmoney-cms-opencart_3
Opencart: payment module, provides payments through RBKmoney payment system
# rbkmoney-cms-opencart
Пожалуйста, обязательно делайте бекапы!
Модуль разрабатывался и тестировался на Opencart 3
#### Требования
- PHP 5.4 (минимум)
- OpenSSL - 1.0.2k-fips (минимум)
- Curl
#### Доступные ставки НДС для корзины
- ничего не указано - без НДС
- 0 - 0% НДС
- 10 - 10% НДС
- 18 - 18% НДС
### Установка и настройка модуля
#### Установка без архива
Для установки модуля скопируйте содержимое каталога `upload`:
```
<OpenCart>/admin/
<OpenCart>/catalog/
```
#### Установка с архивом
Заархивируйте папку `upload` в `zip` архив и переименуйте его в `rbkmoney-payment.ocmod.zip`
После чего необходимо зайти в `Extension Installer`
![Extension Installer](images/extension_Installer.png)
нажать `Upload` и выбрать архив для установки: `rbkmoney-payment.ocmod.zip`
![Upload](images/upload.png)
#### Настройка модуля
В панели администратора установите и настройте его:
```
Extensions > Payments > RBKmoney нажать [Install]
```
![Payments](images/payments.png)
![Module](images/module.png)
#### Для начала приема платежей на Вашем сайте осталось совсем немного
```
Extensions > Payments > RBKmoney нажать [Edit] и заполнить необходимые настройки
```
![Settings](images/settings.png)
Настройте плагин в соответствии с данными из [личного кабинета RBKmoney](https://dashboard.rbk.money).
`Shop ID` - идентификатор магазина из RBKmoney. Скопируйте его в Личном кабинете RBKmoney в разделе Детали магазина, поле Идентификатор;
`Private key` - ключ для доступа к API. Скопируйте его в Личном кабинете RBKmoney в разделе API Ключ
`Callback public key` - ключ для обработки уведомлений о смене статуса
- Заходим в личный кабинет RBKmoney: Создать Webhook;
- Вставляем в поле URL вида `http://your-site/index.php?route=payment/rbkmoney_payment/callback`, скопированного из `Notification URL`
- Выбираем Типы событий `InvoicePaid` и `Invoice Canсelled`;
- после создания Webhook-а копируем Публичный ключ после нажатия Показать детали;
- скопированный ключ вставляем в поле `Callback public key` на странице настроек модуля;
- Сохраните изменения и проведите тестовый платеж
В настройках модуля можно включить или отключить логирование `Advanced settings / Enable logs`
![Advanced settings](images/advanced_settings.png)
C ними можно ознакомиться `Tools / Error logs`
![Tools](images/tools.png)
Выглядит это так:
![Error logs](images/error_logs.png)
### Нашли ошибку или у вас есть предложение по улучшению модуля?
Пишите нам support@rbkmoney.com При обращении необходимо:
- Указать наименование CMS и компонента магазина, а также их версии
- Указать версию платежного модуля
- Описать проблему или предложение
- Приложить снимок экрана (для большей информативности)
#### Возможные проблемы и их решения
1. Ошибка: FTP должен быть включен в настройках
Два варианта решения:
- Если при установке модуля из админки отображается эта ошибка - вам нужно установить бесплатную FTP QuickFix модификацию localcopy.ocmod.xml. Она установиться без проблем через тот же установщик дополнений, только после установки не забудьте обновить модификации и затем можете приступать к установке любых модулей на Opencart 2.
- Также решить эту ошибку можно по другому: прописать доступы к FTP в админке Система > Настройки > Магазин > вкладка FTP.
2. Ошибка: Доступ запрещен!
Если вы видите сообщение "Доступ запрещен! У Вас нет прав для доступа к этой странице. Если она Вам нужна, обратитесь к администратору." - нужно дать права администраторам на управление модулем или страницей.
Решение: в админке Opencart 2 переходим в Система > Пользователи > Группы пользователей > Администраторы и здесь нажимаем "Выделить все" ниже обоих блоков, затем Сохранить.
3. Ошибка: Недопустимый тип файла!
Если модуль - это один XML файл, то его расширение должно быть .ocmod.xml
Если модуль - это ocmod.zip архив, то его не нужно распаковывать, а устанавливать как есть. В таком архиве обязательно должна быть папка upload (может быть пустой), а также могут быть файлы модификаций: install.xml, install.php, install.sql. Никаких других файлов в корне архива быть не должно.
Читайте подробнее как [устанавливать модули в Opencart 2](https://opencart2x.ru/blog/install-module)
4. Ошибка: Каталог, содержащий файлы для загрузки не может быть найден!
Эта ошибка означает, что в загружаемом архиве отсутсвует папка upload. Даже если у модуля нет файлов, кроме модификаций - эта папка должна присутствовать в архиве модуля .ocmod.zip, тогда она должна оставаться пустой.
5. Ошибка: Модификатор использует тот же ID код который вы пытаетесь загрузить!
Эта ошибка означает, что вы пытаетесь установить модификатор, который уже установлен или, возможно, у какого-то вашего модуля такой же ID.
Для решения этой ошибки вам нужно перед установкой удалить старую версию модификации в разделе Модули > Модификации.
Если такого модуля у вас нет, но совпадает ID, тогда нужно поменять значение параметра `<code>` в устанавливаемом модификаторе XML, сделать этот параметр уникальным дописав несколько символов.
6. Ошибка: `Warning: DOMDocument::loadXML(): CData section not finished`
Эта ошибка означает, что вы пытаетесь установить слишком объемный xml-модификатор.
Количество символов в `ocmod.xml` файле не должно превышать 65535.
Для решения ошибки нужно разбить xml-файл модификации на несколько частей, главное - не забыть задавать каждой уникальное значение в `<code>`, можно добавлять к текущему значению цифры 1,2,3... как идентификаторы части.
Еще одним способом решения есть изменения типа в поля, где храняться модификации, в таблице `'oc_modification'` базы данных. Нужно выполнить следующий SQL-запрос:
```
ALTER TABLE oc_modification CHANGE xml xml MEDIUMTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
images/error_logs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/module.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
images/payments.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

BIN
images/tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,189 @@
<?php
class ControllerExtensionPaymentRbkmoney extends Controller {
private $error = array();
public function index() {
$this->load->language('extension/payment/rbkmoney');
$this->document->setTitle($this->language->get('heading_title'));
$data['text_enabled'] = $this->language->get('text_enabled');
$data['text_disabled'] = $this->language->get('text_disabled');
$this->load->model('setting/setting');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
$this->model_setting_setting->editSetting('payment_rbkmoney', $this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true));
}
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->error['shop_id'])) {
$data['error_shop_id'] = $this->error['shop_id'];
} else {
$data['error_shop_id'] = '';
}
if (isset($this->error['merchant_private_key'])) {
$data['error_merchant_private_key'] = $this->error['merchant_private_key'];
} else {
$data['error_merchant_private_key'] = '';
}
if (isset($this->error['rbkmoney_public_key'])) {
$data['error_rbkmoney_public_key'] = $this->error['rbkmoney_public_key'];
} else {
$data['error_rbkmoney_public_key'] = '';
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_extension'),
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/payment/rbkmoney', 'user_token=' . $this->session->data['user_token'], true)
);
$data['action'] = $this->url->link('extension/payment/rbkmoney', 'user_token=' . $this->session->data['user_token'], true);
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true);
if (isset($this->request->post['payment_rbkmoney_shop_id'])) {
$data['payment_rbkmoney_shop_id'] = $this->request->post['payment_rbkmoney_shop_id'];
} else {
$data['payment_rbkmoney_shop_id'] = $this->config->get('payment_rbkmoney_shop_id');
}
if (isset($this->request->post['payment_rbkmoney_merchant_private_key'])) {
$data['payment_rbkmoney_merchant_private_key'] = $this->request->post['payment_rbkmoney_merchant_private_key'];
} else {
$data['payment_rbkmoney_merchant_private_key'] = $this->config->get('payment_rbkmoney_merchant_private_key');
}
if (isset($this->request->post['payment_rbkmoney_rbkmoney_public_key'])) {
$data['payment_rbkmoney_rbkmoney_public_key'] = $this->request->post['payment_rbkmoney_rbkmoney_public_key'];
} else {
$data['payment_rbkmoney_rbkmoney_public_key'] = $this->config->get('payment_rbkmoney_rbkmoney_public_key');
}
if (isset($this->request->post['payment_rbkmoney_order_status_id'])) {
$data['payment_rbkmoney_order_status_id'] = $this->request->post['payment_rbkmoney_order_status_id'];
} elseif ($this->config->has('payment_rbkmoney_order_status_id')) {
$data['payment_rbkmoney_order_status_id'] = $this->config->get('payment_rbkmoney_order_status_id');
} else {
$data['payment_rbkmoney_order_status_id'] = '5';
}
if (isset($this->request->post['payment_rbkmoney_order_status_progress_id'])) {
$data['payment_rbkmoney_order_status_progress_id'] = $this->request->post['payment_rbkmoney_order_status_progress_id'];
} elseif ($this->config->has('payment_rbkmoney_order_status_progress_id')) {
$data['payment_rbkmoney_order_status_progress_id'] = $this->config->get('payment_rbkmoney_order_status_progress_id');
} else {
$data['payment_rbkmoney_order_status_progress_id'] = '2';
}
if (isset($this->request->post['payment_rbkmoney_order_status_cancelled_id'])) {
$data['payment_rbkmoney_order_status_cancelled_id'] = $this->request->post['payment_rbkmoney_order_status_cancelled_id'];
} elseif ($this->config->has('payment_rbkmoney_order_status_cancelled_id')) {
$data['payment_rbkmoney_order_status_cancelled_id'] = $this->config->get('payment_rbkmoney_order_status_cancelled_id');
} else {
$data['payment_rbkmoney_order_status_cancelled_id'] = '7';
}
$this->load->model('localisation/order_status');
$data['order_statuses'] = $this->model_localisation_order_status->getOrderStatuses();
if (isset($this->request->post['payment_rbkmoney_form_company_name'])) {
$data['payment_rbkmoney_form_company_name'] = $this->request->post['payment_rbkmoney_form_company_name'];
} else {
$data['payment_rbkmoney_form_company_name'] = $this->config->get('payment_rbkmoney_form_company_name');
}
if (isset($this->request->post['payment_rbkmoney_form_button_label'])) {
$data['payment_rbkmoney_form_button_label'] = $this->request->post['payment_rbkmoney_form_button_label'];
} else {
$data['payment_rbkmoney_form_button_label'] = $this->config->get('payment_rbkmoney_form_button_label');
}
if (isset($this->request->post['payment_rbkmoney_form_description'])) {
$data['payment_rbkmoney_form_description'] = $this->request->post['payment_rbkmoney_form_description'];
} else {
$data['payment_rbkmoney_form_description'] = $this->config->get('payment_rbkmoney_form_description');
}
if (isset($this->request->post['payment_rbkmoney_geo_zone_id'])) {
$data['payment_rbkmoney_geo_zone_id'] = $this->request->post['payment_rbkmoney_geo_zone_id'];
} else {
$data['payment_rbkmoney_geo_zone_id'] = $this->config->get('payment_rbkmoney_geo_zone_id');
}
$this->load->model('localisation/geo_zone');
$data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones();
if (isset($this->request->post['payment_rbkmoney_status'])) {
$data['payment_rbkmoney_status'] = $this->request->post['payment_rbkmoney_status'];
} else {
$data['payment_rbkmoney_status'] = $this->config->get('payment_rbkmoney_status');
}
if (isset($this->request->post['payment_rbkmoney_sort_order'])) {
$data['payment_rbkmoney_sort_order'] = $this->request->post['payment_rbkmoney_sort_order'];
} else {
$data['payment_rbkmoney_sort_order'] = $this->config->get('payment_rbkmoney_sort_order');
}
$data['notify_url'] = HTTPS_CATALOG . 'index.php?route=extension/payment/rbkmoney/callback';
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('extension/payment/rbkmoney', $data));
}
private function validate() {
if (!$this->user->hasPermission('modify', 'extension/payment/rbkmoney')) {
$this->error['warning'] = $this->language->get('error_permission');
}
if (!$this->request->post['payment_rbkmoney_shop_id']) {
$this->error['shop_id'] = $this->language->get('error_shop_id');
}
if (!$this->request->post['payment_rbkmoney_merchant_private_key']) {
$this->error['merchant_private_key'] = $this->language->get('error_merchant_private_key');
}
if (!$this->request->post['payment_rbkmoney_rbkmoney_public_key']) {
$this->error['rbkmoney_public_key'] = $this->language->get('error_rbkmoney_public_key');
}
return !$this->error;
}
}

View File

@ -0,0 +1,37 @@
<?php
// Heading
$_['heading_title'] = 'RBKmoney';
// Text
$_['text_extension'] = 'Extensions';
$_['text_success'] = 'Success: You have modified RBKmoney account details!';
$_['text_edit'] = 'Edit RBKmoney';
$_['text_rbkmoney'] = '<a target="_BLANK" href="https://welcome.rbk.money/"><img src="view/image/payment/rbkmoney.png" alt="RBKmoney Website" title="RBKmoney Website" style="border: 1px solid #EEEEEE;" /></a>';
$_['text_live'] = 'Live';
$_['text_sandbox'] = 'Sandbox';
// Entry
$_['entry_shop_id'] = 'Shop ID';
$_['entry_merchant_private_key'] = 'Api Private Key';
$_['entry_rbkmoney_public_key'] = 'Callback Public Key';
$_['entry_form_company_name'] = 'Company name in payment form:';
$_['entry_form_button_label'] = 'Button label in payment form:';
$_['entry_form_description'] = 'Description in payment form:';
$_['entry_order_status'] = 'Order status if payment success:';
$_['entry_order_status_progress'] = 'Order Status after invoicing:';
$_['entry_order_status_cancelled'] = 'Order Status if payment failed or cancelled:';
$_['entry_geo_zone'] = 'Geo Zone';
$_['entry_status'] = 'Status';
$_['entry_sort_order'] = 'Sort Order';
$_['entry_notify_url'] = 'Webhooks URL';
// Help
$_['help_form_company_name'] = 'Your company name for payment form';
$_['help_form_button_label'] = 'Your button label for payment form';
$_['help_form_description'] = 'Your description for payment form';
$_['help_rbkmoney_setup'] = '<a target="_blank" href="https://github.com/rbkmoney/rbkmoney-cms-opencart">Click here</a> to learn how to set up RBKmoney';
// Error
$_['error_permission'] = 'Warning: You do not have permission to modify payment rbkmoney!';
$_['error_shop_id'] = 'Shop ID required!';
$_['error_merchant_private_key'] = 'Merchant Api Key required!';
$_['error_rbkmoney_public_key'] = 'Public Key required!';

View File

@ -0,0 +1,45 @@
<?php
// Heading
$_['heading_title'] = 'RBKmoney';
// Text
$_['text_extension'] = 'Расширения';
$_['text_all_zones'] = 'Все географические зоны';
$_['text_enabled'] = 'Включен';
$_['text_disabled'] = 'Выключен';
$_['text_home'] ='Home';
$_['text_success'] = 'Настройки модуля RBKmoney успешно обновлены!';
$_['text_edit'] = 'Редактирование RBKmoney';
$_['text_rbkmoney'] = '<a target="_BLANK" href="https://welcome.rbk.money/"><img src="view/image/payment/rbkmoney.png" alt="RBKmoney Website" title="RBKmoney Website" style="border: 1px solid #EEEEEE;" /></a>';
$_['text_live'] = 'Live';
$_['text_sandbox'] = 'Sandbox';
// Entry
$_['entry_shop_id'] = 'Идентификатор магазина';
$_['entry_merchant_private_key'] = 'Приватный ключ';
$_['entry_rbkmoney_public_key'] = 'Публичный ключ';
$_['entry_form_company_name'] = 'Название компании в платежной форме:';
$_['entry_form_button_label'] = 'Надпись на кнопке в платежной форме:';
$_['entry_form_description'] = 'Дополнительное описание в платежной форме:';
$_['entry_order_status'] = 'Статус заказа после оплаты:';
$_['entry_order_status_progress'] = 'Статус заказа после выставления счета:';
$_['entry_order_status_cancelled'] = 'Статус заказа после неуспешной оплаты:';
$_['entry_geo_zone'] = 'Географическая зона';
$_['entry_status'] = 'Статус:';
$_['entry_sort_order'] = 'Порядок сортировки';
$_['entry_notify_url'] = 'URL для оповещения о платеже:';
// Help
$_['help_form_company_name'] = 'Название компании отображаемой в платежной форме';
$_['help_form_button_label'] = 'Надпись на кнопке в платежной форме';
$_['help_form_description'] = 'Дополнительное описание в платежной форме:';
$_['help_rbkmoney_setup'] = '<a target="_blank" href="https://github.com/rbkmoney/rbkmoney-cms-opencart">Click here</a> to learn how to set up RBKmoney';
// Error
$_['error_permission'] = 'Warning: You do not have permission to modify payment rbkmoney!';
$_['error_shop_id'] = 'Shop ID обязателен';
$_['error_merchant_private_key'] = 'Api Key обязателен';
$_['error_rbkmoney_public_key'] = 'Public Key обязателен';

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,164 @@
{{ header }}{{ column_left }}
<div id="content">
<div class="page-header">
<div class="container-fluid">
<div class="pull-right">
<button type="submit" form="form-payment" data-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fa fa-save"></i></button>
<a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i class="fa fa-reply"></i></a></div>
<h1>{{ heading_title }}</h1>
<ul class="breadcrumb">
{% for breadcrumb in breadcrumbs %}
<li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
{% endfor %}
</ul>
</div>
</div>
<div class="container-fluid">
{% if error_warning %}
<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }}
<button type="button" class="close" data-dismiss="alert">&times;</button>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3>
</div>
<div class="panel-body">
<form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-payment" class="form-horizontal">
<div class="tab-content">
<div class="form-group required">
<label class="col-sm-2 control-label" for="entry-shop-id">{{ entry_shop_id }}</label>
<div class="col-sm-10">
<input type="text" name="payment_rbkmoney_shop_id" value="{{ payment_rbkmoney_shop_id }}" placeholder="{{ entry_shop_id }}" id="entry-shop-id" class="form-control"/>
{% if error_shop_id %}
<div class="text-danger">{{ error_shop_id }}</div>
{% endif %}
</div>
</div>
<div class="form-group required">
<label class="col-sm-2 control-label" for="entry-merchant-private-key">{{ entry_merchant_private_key }}</label>
<div class="col-sm-10">
<textarea name="payment_rbkmoney_merchant_private_key" rows="10" placeholder="{{ entry_merchant_private_key }}" id="entry-merchant-private-key" class="form-control">{{ payment_rbkmoney_merchant_private_key }}</textarea>
{% if error_merchant_private_key %}
<div class="text-danger">{{ error_merchant_private_key }}</div>
{% endif %}
</div>
</div>
<div class="form-group required">
<label class="col-sm-2 control-label" for="entry-rbkmoney-public-key">{{ entry_rbkmoney_public_key }}</label>
<div class="col-sm-10">
<textarea name="payment_rbkmoney_rbkmoney_public_key" rows="5" placeholder="{{ entry_rbkmoney_public_key }}" id="entry-rbkmoney-public-key" class="form-control">{{ payment_rbkmoney_rbkmoney_public_key }}</textarea>
{% if error_rbkmoney_public_key %}
<div class="text-danger">{{ error_rbkmoney_public_key }}</div>
{% endif %}
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-notify-url">{{ entry_notify_url }}</label>
<div class="col-sm-10">
<input type="text" readonly value="{{ notify_url }}" id="input-notify-url" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-status">{{ entry_status }}</label>
<div class="col-sm-10">
<select name="payment_rbkmoney_status" id="input-status" class="form-control">
{% if payment_rbkmoney_status %}
<option value="1" selected="selected">{{ text_enabled }}</option>
<option value="0">{{ text_disabled }}</option>
{% else %}
<option value="1">{{ text_enabled }}</option>
<option value="0" selected="selected">{{ text_disabled }}</option>
{% endif %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-order-status">{{ entry_order_status }}</label>
<div class="col-sm-10">
<select name="payment_rbkmoney_order_status_id" id="input-order-status" class="form-control">
{% for order_status in order_statuses %}
{% if order_status.order_status_id == payment_rbkmoney_order_status_id %}
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
{% else %}
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-order-status-progress">{{ entry_order_status_progress }}</label>
<div class="col-sm-10">
<select name="payment_rbkmoney_order_status_progress_id" id="input-order-status-progress" class="form-control">
{% for order_status in order_statuses %}
{% if order_status.order_status_id == payment_rbkmoney_order_status_progress_id %}
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
{% else %}
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-order-status-cancelled">{{ entry_order_status_cancelled }}</label>
<div class="col-sm-10">
<select name="payment_rbkmoney_order_status_cancelled_id" id="input-order-status-cancelled" class="form-control">
{% for order_status in order_statuses %}
{% if order_status.order_status_id == payment_rbkmoney_order_status_cancelled_id %}
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
{% else %}
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-form-company-name"><span data-toggle="tooltip" title="{{ help_form_company_name }}">{{ entry_form_company_name }}</span></label>
<div class="col-sm-10">
<input type="text" name="payment_rbkmoney_form_company_name" value="{{ payment_rbkmoney_form_company_name }}" placeholder="{{ entry_form_company_name }}" id="input-form-company-name" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-form-button-label"><span data-toggle="tooltip" title="{{ help_form_button_label }}">{{ entry_form_button_label }}</span></label>
<div class="col-sm-10">
<input type="text" name="payment_rbkmoney_form_button_label" value="{{ payment_rbkmoney_form_button_label }}" placeholder="{{ entry_form_button_label }}" id="input-form-button-label" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-form-description"><span data-toggle="tooltip" title="{{ help_form_description }}">{{ entry_form_description }}</span></label>
<div class="col-sm-10">
<input type="text" name="payment_rbkmoney_form_description" value="{{ payment_rbkmoney_form_description }}" placeholder="{{ entry_form_description }}" id="input-form-description" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-geo-zone">{{ entry_geo_zone }}</label>
<div class="col-sm-10">
<select name="payment_rbkmoney_geo_zone_id" id="input-geo-zone" class="form-control">
<option value="0">{{ text_all_zones }}</option>
{% for geo_zone in geo_zones %}
{% if geo_zone.geo_zone_id == payment_rbkmoney_geo_zone_id %}
<option value="{{ geo_zone.geo_zone_id }}" selected="selected">{{ geo_zone.name }}</option>
{% else %}
<option value="{{ geo_zone.geo_zone_id }}">{{ geo_zone.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="input-sort-order">{{ entry_sort_order }}</label>
<div class="col-sm-10">
<input type="text" name="payment_rbkmoney_sort_order" value="{{ payment_rbkmoney_sort_order }}" placeholder="{{ entry_sort_order }}" id="input-sort-order" class="form-control"/>
</div>
</div>
</div>
</form>
<div class="alert alert-info">{{ help_rbkmoney_setup }}</div>
</div>
</div>
</div>
</div>
{{ footer }}

View File

@ -0,0 +1,203 @@
<?php
class ControllerExtensionPaymentRBKmoney extends Controller
{
const HEADER_OK = "HTTP/1.0 200 OK";
const HEADER_BAD_REQUEST = "HTTP/1.0 400 Bad Request";
/**
* Constants for Callback
*/
const ORDER_ID = 'order_id';
const EVENT_TYPE = 'eventType';
const EVENT_TYPE_INVOICE_PAID = 'InvoicePaid';
const EVENT_TYPE_INVOICE_CANCELLED = 'InvoiceCancelled';
const INVOICE = 'invoice';
const INVOICE_ID = 'id';
const INVOICE_SHOP_ID = 'shopID';
const INVOICE_METADATA = 'metadata';
const INVOICE_STATUS = 'status';
const INVOICE_AMOUNT = 'amount';
const CALLBACK_STATUS_UNPAID = 'unpaid';
const CALLBACK_STATUS_CANCELLED = 'cancelled';
const CALLBACK_STATUS_PAID = 'paid';
const CALLBACK_STATUS_REFUNDED = 'refunded';
const CALLBACK_STATUS_FULFILLED = 'fulfilled';
const SIGNATURE = 'HTTP_CONTENT_SIGNATURE';
const SIGNATURE_ALG = 'alg';
const SIGNATURE_DIGEST = 'digest';
const SIGNATURE_PATTERN = "|alg=(\S+);\sdigest=(.*)|i";
const OPENSSL_VERIFY_SIGNATURE_IS_CORRECT = 1;
const CHECKOUT_URL = 'https://checkout.rbk.money/checkout.js';
public function index()
{
$data['button_confirm'] = $this->language->get('button_confirm');
$data['button_back'] = $this->language->get('button_back');
$this->load->language('extension/payment/rbkmoney');
$this->load->model('checkout/order');
$this->load->model('extension/payment/rbkmoney');
$data['payment_form_url'] = static::CHECKOUT_URL;
$data['payment_form_success_url'] = $this->url->link('checkout/success');
$data['form_css_button'] = strip_tags($this->config->get('payment_rbkmoney_form_css_button'));
$data['shop_id'] = $this->config->get('payment_rbkmoney_shop_id');
$data['form_company_name'] = $this->config->get('payment_rbkmoney_form_company_name');
$data['form_button_label'] = $this->config->get('payment_rbkmoney_form_button_label');
$data['form_description'] = $this->config->get('payment_rbkmoney_form_description');
$data['private_key'] = $this->config->get('payment_rbkmoney_merchant_private_key');
$order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']);
$data['order_id'] = $this->session->data['order_id'];
$response = array();
$data['invoice_id'] = "";
$data['invoice_access_token'] = "";
try {
$responseCreateInvoice = $this->model_extension_payment_rbkmoney->createInvoice($order_info);
$response = json_decode($responseCreateInvoice['body'], true);
$this->model_checkout_order->addOrderHistory(
$this->session->data['order_id'],
$this->config->get('payment_rbkmoney_order_status_progress_id')
);
} catch (Exception $ex) {
$logs = array();
$logs['error']['message'] = $ex->getMessage();
$data['errormsg'] = $ex->getMessage();
$this->model_extension_payment_rbkmoney->logger('exception', $logs);
}
if (isset($response["invoice"])) {
$data['invoice_id'] = $response["invoice"]["id"];
$data['invoice_access_token'] = $response["invoiceAccessToken"]["payload"];
}
if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/extension/payment/rbkmoney')) {
return $this->load->view($this->config->get('config_template') . '/template/extension/payment/rbkmoney', $data);
} else {
return $this->load->view('extension/payment/rbkmoney', $data);
}
}
/**
* http{s}://{your-site}/index.php?route=payment/rbkmoney/callback
*/
public function callback()
{
$content = file_get_contents('php://input');
$logs = array(
'request' => array(
'method' => 'POST',
'data' => $content,
),
);
$method = 'notification';
$this->load->model('extension/payment/rbkmoney');
$this->model_extension_payment_rbkmoney->logger($method, $logs);
if (empty($_SERVER[static::SIGNATURE])) {
$logs['error']['message'] = 'Webhook notification signature missing';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
$logs['signature'] = $_SERVER[static::SIGNATURE];
$params_signature = $this->model_extension_payment_rbkmoney->getParametersContentSignature($_SERVER[static::SIGNATURE]);
if (empty($params_signature[static::SIGNATURE_ALG])) {
$logs['error']['message'] = 'Missing required parameter ' . static::SIGNATURE_ALG;
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
if (empty($params_signature[static::SIGNATURE_DIGEST])) {
$logs['error']['message'] = 'Missing required parameter ' . static::SIGNATURE_DIGEST;
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
$signature = $this->model_extension_payment_rbkmoney->urlSafeB64decode($params_signature[static::SIGNATURE_DIGEST]);
$public_key = trim($this->config->get('payment_rbkmoney_rbkmoney_public_key'));
if (!$this->model_extension_payment_rbkmoney->verificationSignature($content, $signature, $public_key)) {
$logs['error']['message'] = 'Webhook notification signature mismatch';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
$required_fields = [static::INVOICE, static::EVENT_TYPE];
$data = json_decode($content, TRUE);
foreach ($required_fields as $field) {
if (empty($data[$field])) {
$logs['error']['message'] = 'One or more required fields are missing';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
}
$current_shop_id = $this->config->get('payment_rbkmoney_shop_id');
if ($data[static::INVOICE][static::INVOICE_SHOP_ID] != $current_shop_id) {
$logs['error']['message'] = static::INVOICE_SHOP_ID . ' is missing';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
if (empty($data[static::INVOICE][static::INVOICE_METADATA][static::ORDER_ID])) {
$logs['error']['message'] = static::ORDER_ID . ' is missing';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
$order_id = $data[static::INVOICE][static::INVOICE_METADATA][static::ORDER_ID];
$this->load->model('checkout/order');
if (!$order_info = $this->model_checkout_order->getOrder($order_id)) {
$logs['error']['message'] = 'Order ' . $order_id . ' is missing';
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
if (!empty($order_info['total'])) {
$order_amount = (int)$this->model_extension_payment_rbkmoney->prepareAmount($order_info['total']);
$invoice_amount = (int)$data[static::INVOICE][static::INVOICE_AMOUNT];
if ($order_amount != $invoice_amount) {
$logs['order_info'] = $order_info;
$logs['error']['message'] = 'Received amount ' . $order_amount . ' vs Order amount mismatch ' . $data[static::INVOICE][static::INVOICE_AMOUNT];
return $this->outputWithLogger($method, $logs, $logs['error']['message']);
}
}
$allowedEventTypes = array(static::EVENT_TYPE_INVOICE_PAID, static::EVENT_TYPE_INVOICE_CANCELLED);
if (in_array($data[static::EVENT_TYPE], $allowedEventTypes)) {
$invoiceStatus = $data[static::INVOICE][static::INVOICE_STATUS];
if (($invoiceStatus == static::CALLBACK_STATUS_PAID) && ($order_info['order_status_id'] != $this->config->get('payment_rbkmoney_order_status_id'))) {
$this->model_checkout_order->addOrderHistory($order_info['order_id'], $this->config->get('payment_rbkmoney_order_status_id'), 'RBKmoney', TRUE);
} elseif (($invoiceStatus == static::CALLBACK_STATUS_CANCELLED) && ($order_info['order_status_id'] != $this->config->get('payment_rbkmoney_order_status_cancelled_id'))) {
$this->model_checkout_order->addOrderHistory($order_info['order_id'], $this->config->get('payment_rbkmoney_order_status_cancelled_id'), 'RBKmoney', TRUE);
}
return $this->outputWithLogger($method, $logs, 'OK', self::HEADER_OK);
}
return $this->outputWithLogger($method, $logs, 'FINISH', self::HEADER_OK);
}
private function outputWithLogger($method, &$logs, $message, $header = self::HEADER_BAD_REQUEST)
{
$response = array('message' => $message);
$this->load->model('extension/payment/rbkmoney');
$this->model_extension_payment_rbkmoney->logger($method, $logs);
$this->response->addHeader($header);
$this->response->setOutput(json_encode($response));
}
}

View File

@ -0,0 +1,5 @@
<?php
// Text
$_['text_title'] = 'RBKmoney';
?>

View File

@ -0,0 +1,5 @@
<?php
// Text
$_['text_title'] = 'RBKmoney';
?>

View File

@ -0,0 +1,324 @@
<?php
class ModelExtensionPaymentRBKmoney extends Model
{
/**
* Create invoice settings
*/
const CREATE_INVOICE_TEMPLATE_DUE_DATE = 'Y-m-d\TH:i:s\Z';
const CREATE_INVOICE_DUE_DATE = '+1 days';
const SIGNATURE = 'HTTP_CONTENT_SIGNATURE';
const SIGNATURE_ALG = 'alg';
const SIGNATURE_DIGEST = 'digest';
const SIGNATURE_PATTERN = "|alg=(\S+);\sdigest=(.*)|i";
private $api_url = 'https://api.rbk.money/v2/';
public function getMethod($address, $total)
{
$this->load->language('extension/payment/rbkmoney');
if ($this->config->get('payment_rbkmoney_status')) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . (int)$this->config->get('payment_rbkmoney_geo_zone_id') . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");
if (!$this->config->get('payment_rbkmoney_geo_zone_id')) {
$status = TRUE;
} elseif ($query->num_rows) {
$status = TRUE;
} else {
$status = FALSE;
}
} else {
$status = FALSE;
}
$method_data = array();
if ($status) {
$method_data = array(
'code' => 'rbkmoney',
'title' => $this->language->get('text_title'),
'terms' => '',
'sort_order' => $this->config->get('payment_rbkmoney_sort_order')
);
}
return $method_data;
}
private function getHeaders()
{
$headers = array();
$headers[] = 'X-Request-ID: ' . uniqid();
$headers[] = 'Authorization: Bearer ' . $this->config->get('payment_rbkmoney_merchant_private_key');
$headers[] = 'Content-type: application/json; charset=utf-8';
$headers[] = 'Accept: application/json';
return $headers;
}
public function createInvoice(array $order_info)
{
$data = [
'shopID' => $this->config->get('payment_rbkmoney_shop_id'),
'amount' => $this->prepareAmount($order_info['total']),
'metadata' => $this->prepareMetadata($order_info['order_id']),
'dueDate' => $this->prepareDueDate(),
'currency' => $order_info['currency_code'],
'product' => $order_info['order_id'],
'cart' => $this->prepareCart(),
'description' => $this->getProductDescription(),
];
$url = $this->prepareApiUrl('processing/invoices');
$headers = $this->getHeaders();
return $this->send($url, 'POST', $headers, json_encode($data, true), 'init_invoice');
}
private function prepareCart()
{
$lines = array();
foreach ($this->cart->getProducts() as $product) {
$item = array();
$item['product'] = $product['name'];
$item['quantity'] = (int)$product['quantity'];
$tax = $this->tax->calculate($product['price'], $product['tax_class_id'], $this->config->get('config_tax'));
$price = round($tax, 2, PHP_ROUND_HALF_UP);
$item['price'] = $this->prepareAmount($price);
$tax_rates = $this->tax->getRates($product['price'], $product['tax_class_id']);
if (!empty($tax_rates)) {
foreach ($tax_rates as $rate) {
$rate = $this->getRate($rate['rate']);
if ($rate != null) {
$taxMode = [
'type' => 'InvoiceLineTaxVAT',
'rate' => $rate,
];
$item['taxMode'] = $taxMode;
}
}
}
$lines[] = $item;
}
$shippingMethod = "";
if (isset($this->session->data['shipping_method'])) {
$shippingMethod = $this->session->data['shipping_method'];
}
if (!empty($shippingMethod)) {
if (isset($shippingMethod['cost']) && $shippingMethod['cost'] > 0) {
$item = array();
$item['product'] = $shippingMethod['title'];
$item['quantity'] = 1;
$tax = $this->tax->calculate($shippingMethod['cost'], $shippingMethod['tax_class_id'], $this->config->get('config_tax'));
$price = round($tax, 2, PHP_ROUND_HALF_UP);
$item['price'] = $this->prepareAmount($price);
// Shipping always 18%
$taxMode = [
'type' => 'InvoiceLineTaxVAT',
'rate' => "18%",
];
$item['taxMode'] = $taxMode;
$lines[] = $item;
}
}
return $lines;
}
private function getRate($rate)
{
switch ($rate) {
case '0':
case '0.0000':
return '0%';
break;
case '10':
case '10.0000':
return '10%';
break;
case '18':
case '18.0000':
return '18%';
break;
case '10/100':
return '10/110';
break;
case '18/118':
return '18/118';
break;
default:
return null;
break;
}
}
private function getProductDescription()
{
$products = '';
$i = 0;
foreach ($this->cart->getProducts() as $product) {
if ($i == 0) {
$products .= $product['quantity'] . ' x ' . $product['name'];
} else {
$products .= ', ' . $product['quantity'] . ' x ' . $product['name'];
}
$i++;
}
if (mb_strlen($products, 'UTF-8') > 255) {
$products = mb_substr($products, 0, 252, 'UTF-8') . '...';
}
return $products;
}
private function send($url, $method = "POST", $headers = array(), $data = '', $type = '')
{
$logs = array(
'request' => array(
'url' => $url,
'method' => $method,
'headers' => $headers,
'data' => $data,
),
);
$this->logger($type . ': request', $logs);
if (empty($url)) {
throw new Exception('Required url parameter not passed');
}
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$body = curl_exec($curl);
$info = curl_getinfo($curl);
$curl_errno = curl_errno($curl);
$response['http_code'] = $info['http_code'];
$response['body'] = $body;
$response['error'] = $curl_errno;
$logs['response'] = $response;
$this->logger($type . ': response', $logs);
curl_close($curl);
return $response;
}
/**
* Prepare due date
*
* @return string
*/
private function prepareDueDate()
{
date_default_timezone_set('UTC');
return date(static::CREATE_INVOICE_TEMPLATE_DUE_DATE, strtotime(static::CREATE_INVOICE_DUE_DATE));
}
/**
* Prepare metadata
*
* @param $order_id
* @return array
*/
private function prepareMetadata($order_id)
{
return [
'cms' => 'opencart',
'cms_version' => VERSION,
'module' => 'rbkmoney',
'order_id' => $order_id,
];
}
/**
* Prepare amount (e.g. 124.24 -> 12424)
*
* @param $amount int
* @return int
*/
public function prepareAmount($amount)
{
return number_format($amount, 2, '.', '') * 100;
}
private function prepareApiUrl($path = '', $query_params = array())
{
$url = rtrim($this->api_url, '/') . '/' . $path;
if (!empty($query_params)) {
$url .= '?' . http_build_query($query_params);
}
return $url;
}
public function urlSafeB64decode($string)
{
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
public function urlSafeB64encode($string)
{
$data = base64_encode($string);
return str_replace(array('+', '/'), array('-', '_'), $data);
}
public function getParametersContentSignature($content_signature)
{
preg_match_all(static::SIGNATURE_PATTERN, $content_signature, $matches, PREG_PATTERN_ORDER);
$params = array();
$params[static::SIGNATURE_ALG] = !empty($matches[1][0]) ? $matches[1][0] : '';
$params[static::SIGNATURE_DIGEST] = !empty($matches[2][0]) ? $matches[2][0] : '';
return $params;
}
public function verificationSignature($data, $signature, $public_key)
{
if (empty($data) || empty($signature) || empty($public_key)) {
return FALSE;
}
$public_key_id = openssl_get_publickey($public_key);
if (empty($public_key_id)) {
return FALSE;
}
$verify = openssl_verify($data, $signature, $public_key_id, OPENSSL_ALGO_SHA256);
return ($verify == 1);
}
public function logger($method, $message)
{
// if ($this->config->get('rbkmoney_logs')) {
$this->log->write('rbkmoney ' . $method . '. ' . print_r($message, true));
// }
}
}

View File

@ -0,0 +1,26 @@
{% if (errormsg is not empty) %}
{{ errormsg }}
{% else %}
{% if (form_css_button is not empty) %}
<style>
{{ form_css_button }}
</style>
{% endif %}
<form action="{{ payment_form_success_url }}" method="POST">
<script src="{{ payment_form_url }}" class="rbkmoney-checkout"
data-invoice-id="{{ invoice_id }}"
data-invoice-access-token="{{ invoice_access_token }}"
{% if (form_company_name is not empty) %}
data-name="{{ form_company_name }}"
{% endif %}
{% if (form_button_label is not empty) %}
data-label="{{ form_button_label }}"
{% endif %}
{% if (form_description is not empty) %}
data-description="{{ form_description }}"
{% endif %}
>
</script>
</form>
{% endif %}