mirror of
https://github.com/valitydev/rbkmoney-cms-magento.git
synced 2024-11-06 02:15:24 +00:00
update module (#2)
This commit is contained in:
parent
6ad22314bc
commit
e5b9d9ad97
66
README.md
66
README.md
@ -1,15 +1,69 @@
|
|||||||
# rbkmoney-cms-magento
|
# rbkmoney-cms-magento
|
||||||
|
|
||||||
### Инструкция по установке
|
Пожалуйста, обязательно делайте бекапы!
|
||||||
|
|
||||||
|
Платежный плагин RBKmoney для **Magento**
|
||||||
|
|
||||||
|
Модуль разрабатывался и тестировался на **Magento 1.9.1.0**
|
||||||
|
|
||||||
|
|
||||||
|
#### Требования
|
||||||
|
|
||||||
|
- PHP 5.4 (минимум)
|
||||||
|
- OpenSSL - 1.0.2k-fips (минимум)
|
||||||
|
- Curl
|
||||||
|
|
||||||
|
|
||||||
|
### Установка и настройка модуля
|
||||||
|
|
||||||
- Скопировать содержимое `app` в соответствующую папку `app/`
|
- Скопировать содержимое `app` в соответствующую папку `app/`
|
||||||
|
|
||||||
- Установленный модуль заработает сразу после его настройки в админке:
|
- Установленный модуль заработает сразу после его настройки в админке:
|
||||||
```
|
```
|
||||||
"Система - Конфигурация - Продажи - Методы оплаты"
|
"Система - Конфигурация" (System - Configuration)"
|
||||||
(System - Configuration - Sales - Payment Methods)
|
|
||||||
```
|
```
|
||||||
- Параметры для заполнения можно получить на сайте платёжной системы [RBKmoney](https://dashboard.rbk.money)
|
![Plugin system](images/plugin_system.png)
|
||||||
- В качестве URL для уведомления о смене статуса инвойса в личном кабинете указать:
|
|
||||||
```
|
```
|
||||||
http{s}://{your-site}/rbkmoney/payment/notification
|
"Продажи - Методы оплаты" (Sales - Payment Methods)
|
||||||
```
|
```
|
||||||
|
![Plugin sales](images/plugin_sales.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Для начала приема платежей на Вашем сайте осталось совсем немного
|
||||||
|
|
||||||
|
|
||||||
|
![Plugin settings](images/plugin_settings.png)
|
||||||
|
|
||||||
|
Настройте плагин в соответствии с данными из [личного кабинета RBKmoney](https://dashboard.rbk.money).
|
||||||
|
|
||||||
|
`Shop ID` - идентификатор магазина из RBKmoney. Скопируйте его в Личном кабинете RBKmoney в разделе Детали магазина, поле Идентификатор;
|
||||||
|
|
||||||
|
`Private key` - ключ для доступа к API. Скопируйте его в Личном кабинете RBKmoney в разделе API Ключ
|
||||||
|
|
||||||
|
`Callback public key` - ключ для обработки уведомлений о смене статуса
|
||||||
|
|
||||||
|
- Заходим в личный кабинет RBKmoney: Создать Webhook;
|
||||||
|
- Вставляем в поле URL вида `http{s}://{your-site}/rbkmoney/payment/notification`, скопированного из `Notification URL`
|
||||||
|
- Выбираем Типы событий `InvoicePaid` и `Invoice Canсelled`;
|
||||||
|
- после создания Webhook-а копируем Публичный ключ после нажатия Показать детали;
|
||||||
|
- скопированный ключ вставляем в поле `Публичный ключ` на странице настроек модуля;
|
||||||
|
|
||||||
|
|
||||||
|
- Сохраните изменения и проведите тестовый платеж
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
В настройках модуля можно включить или отключить логирование `Debug on/off`
|
||||||
|
С результатом логирования можно ознакомиться по пути `/var/log/rbkmoney.log` в корне сайта
|
||||||
|
|
||||||
|
|
||||||
|
### Нашли ошибку или у вас есть предложение по улучшению модуля?
|
||||||
|
|
||||||
|
Пишите нам `support@rbkmoney.com` При обращении необходимо:
|
||||||
|
|
||||||
|
- Указать наименование CMS и компонента магазина, а также их версии
|
||||||
|
- Указать версию платежного модуля (доступна на странице Управление пакетами)
|
||||||
|
- Описать проблему или предложение
|
||||||
|
- Приложить снимок экрана (для большей информативности)
|
||||||
|
@ -9,7 +9,7 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
* Payment form
|
* Payment form
|
||||||
*/
|
*/
|
||||||
const PAYMENT_FORM_URL = 'https://checkout.rbk.money/checkout.js';
|
const PAYMENT_FORM_URL = 'https://checkout.rbk.money/checkout.js';
|
||||||
const API_URL = 'https://api.rbk.money/v1/';
|
const API_URL = 'https://api.rbk.money/v2/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create invoice settings
|
* Create invoice settings
|
||||||
@ -29,7 +29,6 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
*/
|
*/
|
||||||
const SETTINGS_SHOP_ID = 'shop_id';
|
const SETTINGS_SHOP_ID = 'shop_id';
|
||||||
|
|
||||||
const SETTINGS_PAYMENT_FORM_LOGO = 'payment_form_logo';
|
|
||||||
const SETTINGS_PAYMENT_FORM_COMPANY_NAME = 'payment_form_company_name';
|
const SETTINGS_PAYMENT_FORM_COMPANY_NAME = 'payment_form_company_name';
|
||||||
const SETTINGS_PAYMENT_FORM_BUTTON_LABEL = 'payment_form_button_label';
|
const SETTINGS_PAYMENT_FORM_BUTTON_LABEL = 'payment_form_button_label';
|
||||||
const SETTINGS_PAYMENT_FORM_DESCRIPTION = 'payment_form_description';
|
const SETTINGS_PAYMENT_FORM_DESCRIPTION = 'payment_form_description';
|
||||||
@ -43,7 +42,7 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
|
|
||||||
public function getShopId()
|
public function getShopId()
|
||||||
{
|
{
|
||||||
return (int)Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_SHOP_ID);
|
return trim(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_SHOP_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPrivateKey()
|
public function getPrivateKey()
|
||||||
@ -56,11 +55,6 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
return trim(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_CALLBACK_PUBLIC_KEY));
|
return trim(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_CALLBACK_PUBLIC_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPaymentFormLogo()
|
|
||||||
{
|
|
||||||
return trim(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_PAYMENT_FORM_LOGO));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPaymentFormCompanyName()
|
public function getPaymentFormCompanyName()
|
||||||
{
|
{
|
||||||
return trim(strip_tags(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_PAYMENT_FORM_COMPANY_NAME)));
|
return trim(strip_tags(Mage::getStoreConfig(static::COMMON_PATH . static::SETTINGS_PAYMENT_FORM_COMPANY_NAME)));
|
||||||
@ -96,29 +90,6 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
return Mage::getUrl('checkout/onepage/error', array('_secure' => false));
|
return Mage::getUrl('checkout/onepage/error', array('_secure' => false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create invoice access token
|
|
||||||
*
|
|
||||||
* @param $invoice_id
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function createInvoiceAccessToken($invoice_id)
|
|
||||||
{
|
|
||||||
$url = $this->_prepareApiUrl('processing/invoices/' . $invoice_id . '/access_tokens');
|
|
||||||
$response = $this->_send($url, $this->_getHeaders(), '', 'access_tokens');
|
|
||||||
|
|
||||||
if ($response['http_code'] != static::HTTP_CODE_CREATED) {
|
|
||||||
throw new Exception('An error occurred while creating Invoice Access Token');
|
|
||||||
}
|
|
||||||
|
|
||||||
$response_decode = json_decode($response['body'], true);
|
|
||||||
$access_token = !empty($response_decode['payload']) ? $response_decode['payload'] : '';
|
|
||||||
|
|
||||||
return $access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create invoice
|
* Create invoice
|
||||||
@ -141,7 +112,7 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
];
|
];
|
||||||
|
|
||||||
$url = $this->_prepareApiUrl('processing/invoices');
|
$url = $this->_prepareApiUrl('processing/invoices');
|
||||||
$response = $this->_send($url, $this->_getHeaders(), json_encode($data), 'init_invoice');
|
$response = $this->_send($url, $this->_getHeaders(), json_encode($data));
|
||||||
|
|
||||||
if ($response['http_code'] != static::HTTP_CODE_CREATED) {
|
if ($response['http_code'] != static::HTTP_CODE_CREATED) {
|
||||||
$message = 'An error occurred while creating invoice';
|
$message = 'An error occurred while creating invoice';
|
||||||
@ -149,9 +120,7 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
$response_decode = json_decode($response['body'], true);
|
$response_decode = json_decode($response['body'], true);
|
||||||
$invoice_id = !empty($response_decode['id']) ? $response_decode['id'] : '';
|
return $response_decode;
|
||||||
|
|
||||||
return $invoice_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -278,7 +247,8 @@ class RBKmoney_Payform_Helper_Data extends Mage_Core_Helper_Data
|
|||||||
|
|
||||||
public function log($message, $logs = array(), $level = Zend_Log::INFO, $fileName = "rbkmoney.log")
|
public function log($message, $logs = array(), $level = Zend_Log::INFO, $fileName = "rbkmoney.log")
|
||||||
{
|
{
|
||||||
if (!empty($this->getDebug())) {
|
$debug = $this->getDebug();
|
||||||
|
if (!empty($debug)) {
|
||||||
Mage::log($message . ' ' . print_r($logs, true), $level, $fileName);
|
Mage::log($message . ' ' . print_r($logs, true), $level, $fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by IntelliJ IDEA.
|
|
||||||
* User: avcherkasov
|
|
||||||
* Date: 20/04/2017
|
|
||||||
* Time: 13:10
|
|
||||||
*/
|
|
||||||
class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Action
|
class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Action
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -13,9 +8,7 @@ class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Acti
|
|||||||
* Constants for Callback
|
* Constants for Callback
|
||||||
*/
|
*/
|
||||||
const SIGNATURE = 'HTTP_CONTENT_SIGNATURE';
|
const SIGNATURE = 'HTTP_CONTENT_SIGNATURE';
|
||||||
const SIGNATURE_ALG = 'alg';
|
const SIGNATURE_PATTERN = "/alg=(\S+);\sdigest=/";
|
||||||
const SIGNATURE_DIGEST = 'digest';
|
|
||||||
const SIGNATURE_PATTERN = "|alg=(\S+);\sdigest=(.*)|i";
|
|
||||||
|
|
||||||
const EVENT_TYPE = 'eventType';
|
const EVENT_TYPE = 'eventType';
|
||||||
|
|
||||||
@ -65,19 +58,16 @@ class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Acti
|
|||||||
}
|
}
|
||||||
$logs['signature'] = $_SERVER[static::SIGNATURE];
|
$logs['signature'] = $_SERVER[static::SIGNATURE];
|
||||||
|
|
||||||
$params_signature = $this->getParametersContentSignature($_SERVER[static::SIGNATURE]);
|
$signature = $this->getSignatureFromHeader($_SERVER[static::SIGNATURE]);
|
||||||
if (empty($params_signature[static::SIGNATURE_ALG])) {
|
$logs['signature_from_header'] = $signature;
|
||||||
$message = 'Missing required parameter ' . static::SIGNATURE_ALG;
|
if (empty($signature)) {
|
||||||
|
$message = 'Signature is missing';
|
||||||
static::outputWithExit($message, $logs);
|
static::outputWithExit($message, $logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($params_signature[static::SIGNATURE_DIGEST])) {
|
$signatureDecode = $this->urlSafeB64decode($signature);
|
||||||
$message = 'Missing required parameter ' . static::SIGNATURE_DIGEST;
|
$publicKey = $payform->getCallbackPublicKey();
|
||||||
static::outputWithExit($message, $logs);
|
if (!$this->verificationSignature($content, $signatureDecode, $publicKey)) {
|
||||||
}
|
|
||||||
|
|
||||||
$signature = $this->urlSafeB64decode($params_signature[static::SIGNATURE_DIGEST]);
|
|
||||||
if (!$this->verificationSignature($content, $signature, $payform->getCallbackPublicKey())) {
|
|
||||||
$message = 'Webhook notification signature mismatch';
|
$message = 'Webhook notification signature mismatch';
|
||||||
static::outputWithExit($message, $logs);
|
static::outputWithExit($message, $logs);
|
||||||
}
|
}
|
||||||
@ -93,7 +83,7 @@ class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Acti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$current_shop_id = (int)$payform->getShopId();
|
$current_shop_id = $payform->getShopId();
|
||||||
if ($data[static::INVOICE][static::INVOICE_SHOP_ID] != $current_shop_id) {
|
if ($data[static::INVOICE][static::INVOICE_SHOP_ID] != $current_shop_id) {
|
||||||
$message = static::INVOICE_SHOP_ID . ' is missing';
|
$message = static::INVOICE_SHOP_ID . ' is missing';
|
||||||
static::outputWithExit($message, $logs);
|
static::outputWithExit($message, $logs);
|
||||||
@ -145,21 +135,12 @@ class RBKmoney_Payform_PaymentController extends Mage_Core_Controller_Front_Acti
|
|||||||
|
|
||||||
private function urlSafeB64decode($string)
|
private function urlSafeB64decode($string)
|
||||||
{
|
{
|
||||||
$data = str_replace(array('-', '_'), array('+', '/'), $string);
|
return base64_decode(strtr($string, '-_,', '+/='));
|
||||||
$mod4 = strlen($data) % 4;
|
|
||||||
if ($mod4) {
|
|
||||||
$data .= substr('====', $mod4);
|
|
||||||
}
|
|
||||||
return base64_decode($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getParametersContentSignature($content_signature)
|
private function getSignatureFromHeader($content_signature)
|
||||||
{
|
{
|
||||||
preg_match_all(static::SIGNATURE_PATTERN, $content_signature, $matches, PREG_PATTERN_ORDER);
|
return preg_replace(static::SIGNATURE_PATTERN, '', $content_signature);
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,8 +43,7 @@
|
|||||||
<title>RBKmoney</title>
|
<title>RBKmoney</title>
|
||||||
<allowspecific>0</allowspecific>
|
<allowspecific>0</allowspecific>
|
||||||
<payment_action>sale</payment_action>
|
<payment_action>sale</payment_action>
|
||||||
<shop_id>1</shop_id>
|
<shop_id>TEST</shop_id>
|
||||||
<payment_form_logo></payment_form_logo>
|
|
||||||
<payment_form_company_name></payment_form_company_name>
|
<payment_form_company_name></payment_form_company_name>
|
||||||
<payment_form_button_label></payment_form_button_label>
|
<payment_form_button_label></payment_form_button_label>
|
||||||
<payment_form_description></payment_form_description>
|
<payment_form_description></payment_form_description>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<frontend_class>complex</frontend_class>
|
<frontend_class>complex</frontend_class>
|
||||||
<frontend_model>payform/adminhtml_system_config_fieldset_group</frontend_model>
|
<frontend_model>payform/adminhtml_system_config_fieldset_group</frontend_model>
|
||||||
<comment>Process payments using your own internet merchant account.</comment>
|
<comment>Process payments using your own internet merchant account.</comment>
|
||||||
<help_url>https://rbkmoney.github.io/docs/</help_url>
|
<help_url>https://developer.rbk.money/docs/</help_url>
|
||||||
<fields>
|
<fields>
|
||||||
<title translate="label">
|
<title translate="label">
|
||||||
<label>Title</label>
|
<label>Title</label>
|
||||||
@ -92,14 +92,6 @@
|
|||||||
<show_in_store>0</show_in_store>
|
<show_in_store>0</show_in_store>
|
||||||
<sort_order>8</sort_order>
|
<sort_order>8</sort_order>
|
||||||
</payment_form_css_button>
|
</payment_form_css_button>
|
||||||
<payment_form_logo translate="label">
|
|
||||||
<label>Logo in payment form:</label>
|
|
||||||
<frontend_type>text</frontend_type>
|
|
||||||
<show_in_default>1</show_in_default>
|
|
||||||
<show_in_website>1</show_in_website>
|
|
||||||
<show_in_store>0</show_in_store>
|
|
||||||
<sort_order>9</sort_order>
|
|
||||||
</payment_form_logo>
|
|
||||||
<payment_form_company_name translate="label">
|
<payment_form_company_name translate="label">
|
||||||
<label>Company name in payment form:</label>
|
<label>Company name in payment form:</label>
|
||||||
<frontend_type>text</frontend_type>
|
<frontend_type>text</frontend_type>
|
||||||
|
@ -7,23 +7,33 @@ $order->loadByIncrementId($orderId);
|
|||||||
/*** @var RBKmoney_Payform_Helper_Data $data */
|
/*** @var RBKmoney_Payform_Helper_Data $data */
|
||||||
$data = Mage::helper("payform");
|
$data = Mage::helper("payform");
|
||||||
|
|
||||||
$dataLogo = !empty($data->getPaymentFormLogo()) ? 'data-logo="' . $data->getPaymentFormLogo() . '"' : '';
|
$paymentFormCompanyName = $data->getPaymentFormCompanyName();
|
||||||
$companyName = !empty($data->getPaymentFormCompanyName()) ? 'data-name="' . $data->getPaymentFormCompanyName() . '"' : '';
|
$companyName = !empty($paymentFormCompanyName) ? 'data-name="' . $paymentFormCompanyName . '"' : '';
|
||||||
$buttonLabel = !empty($data->getPaymentFormButtonLabel()) ? 'data-label="' . $data->getPaymentFormButtonLabel() . '"' : '';
|
|
||||||
$description = !empty($data->getPaymentFormDescription()) ? 'data-description="' . $data->getPaymentFormDescription() . '"' : '';
|
|
||||||
|
|
||||||
$style = !empty($data->getPaymentFormCssButton()) ? '<style>' . $data->getPaymentFormCssButton() . '</style>' : '';
|
$paymentFormButtonLabel = $data->getPaymentFormButtonLabel();
|
||||||
|
$buttonLabel = !empty($paymentFormButtonLabel) ? 'data-label="' . $paymentFormButtonLabel . '"' : '';
|
||||||
|
|
||||||
|
$paymentFormDescription = $data->getPaymentFormDescription();
|
||||||
|
$description = !empty($paymentFormDescription) ? 'data-description="' . $paymentFormDescription . '"' : '';
|
||||||
|
|
||||||
|
$paymentFormCssButton = $data->getPaymentFormCssButton();
|
||||||
|
$style = !empty($paymentFormCssButton) ? '<style>' . $paymentFormCssButton . '</style>' : '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(empty($_SESSION['order']) || $_SESSION['order']['id'] != $orderId) {
|
if(empty($_SESSION['order']) || $_SESSION['order']['id'] != $orderId) {
|
||||||
$invoice_id = $data->createInvoice($order);
|
$response = $data->createInvoice($order);
|
||||||
|
|
||||||
|
$invoice_id = $response["invoice"]["id"];
|
||||||
|
$invoice_access_token = $response["invoiceAccessToken"]["payload"];
|
||||||
|
|
||||||
$_SESSION['order']['id'] = $orderId;
|
$_SESSION['order']['id'] = $orderId;
|
||||||
$_SESSION['order']['invoice_id'] = $invoice_id;
|
$_SESSION['order']['invoice_id'] = $invoice_id;
|
||||||
|
$_SESSION['order']['invoiceAccessToken'] = $invoice_access_token;
|
||||||
} else {
|
} else {
|
||||||
$invoice_id = $_SESSION['order']['invoice_id'];
|
$invoice_id = $_SESSION['order']['invoice_id'];
|
||||||
|
$invoice_access_token = $_SESSION['order']['invoiceAccessToken'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoice_access_token = $data->createInvoiceAccessToken($invoice_id);
|
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
die($ex->getMessage());
|
die($ex->getMessage());
|
||||||
}
|
}
|
||||||
@ -36,7 +46,6 @@ try {
|
|||||||
<script src="<?php echo $data::PAYMENT_FORM_URL; ?>" class="rbkmoney-checkout"
|
<script src="<?php echo $data::PAYMENT_FORM_URL; ?>" class="rbkmoney-checkout"
|
||||||
data-invoice-id="<?php echo $invoice_id; ?>"
|
data-invoice-id="<?php echo $invoice_id; ?>"
|
||||||
data-invoice-access-token="<?php echo $invoice_access_token; ?>"
|
data-invoice-access-token="<?php echo $invoice_access_token; ?>"
|
||||||
<?php echo $dataLogo; ?>
|
|
||||||
<?php echo $companyName; ?>
|
<?php echo $companyName; ?>
|
||||||
<?php echo $buttonLabel; ?>
|
<?php echo $buttonLabel; ?>
|
||||||
<?php echo $description; ?>
|
<?php echo $description; ?>
|
||||||
|
BIN
images/plugin_sales.png
Normal file
BIN
images/plugin_sales.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
images/plugin_settings.png
Normal file
BIN
images/plugin_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 346 KiB |
BIN
images/plugin_system.png
Normal file
BIN
images/plugin_system.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
Loading…
Reference in New Issue
Block a user