3
0

Compare commits

..

No commits in common. "release" and "payanyway.ocmod" have entirely different histories.

11 changed files with 115 additions and 340 deletions

View File

@ -5,7 +5,7 @@
![PHP](https://img.shields.io/badge/PHP-8.0%2B-purple) ![PHP](https://img.shields.io/badge/PHP-8.0%2B-purple)
![License](https://img.shields.io/badge/License-MIT-green) ![License](https://img.shields.io/badge/License-MIT-green)
> **📦 Версия модуля: 1.0.2** > **📦 Версия модуля: 1.0.0**
> ** Совместимость версий OpenCart:** > ** Совместимость версий OpenCart:**
> 4.0.0.0, 4.0.1.0, 4.0.1.1, 4.0.2.0, 4.0.2.1, 4.0.2.2, 4.0.2.3, 4.1.0.0, 4.1.0.1, 4.1.0.2, 4.1.0.3 > 4.0.0.0, 4.0.1.0, 4.0.1.1, 4.0.2.0, 4.0.2.1, 4.0.2.2, 4.0.2.3, 4.1.0.0, 4.1.0.1, 4.1.0.2, 4.1.0.3
@ -77,7 +77,7 @@
| **Тестовый режим** | `Нет` | | **Тестовый режим** | `Нет` |
| **Check URL** | `https://your_site_name/index.php?route=extension/payanyway/payment/callback` | | **Check URL** | `https://your_site_name/index.php?route=extension/payanyway/payment/callback` |
| **Pay URL** | `https://your_site_name/index.php?route=extension/payanyway/payment/callback` | | **Pay URL** | `https://your_site_name/index.php?route=extension/payanyway/payment/callback` |
| **HTTP method** | `GET` | | **HTTP method** | `GET` / `POST` |
| **Можно переопределять настройки в URL** | `Да` | | **Можно переопределять настройки в URL** | `Да` |
| **Подпись формы оплаты обязательна** | `Да` | | **Подпись формы оплаты обязательна** | `Да` |
| **Код проверки целостности данных** | аш_код` (произвольный набор символов) | | **Код проверки целостности данных** | аш_код` (произвольный набор символов) |
@ -88,14 +88,6 @@
> ⚠️ **Важно!** Для кириллического домена PayURL и CheckURL должны быть указаны в кодировке [Punycode](https://2ip.ru/punycode/). > ⚠️ **Важно!** Для кириллического домена PayURL и CheckURL должны быть указаны в кодировке [Punycode](https://2ip.ru/punycode/).
> ⚠️ **Важно!** В OpenCart 4 при возврате с сайта платежной системы (эквайринга) авторизация пользователя чаще всего слетает из-за конфликта cookie-атрибута SameSite.
Для решения проблемы выполните следующие действия:
> 1. Изменение настроек сессии в панели управления OpenCart по умолчанию использует для кук параметр `Lax` или `Strict`,
что вызывает разрыв сессии при возврате с внешнего сервера Настройки `SameSite`.
Перейдите в административную панель сайта. Откройте `Система (System)` > `Настройки (Settings)` и отредактируйте ваш магазин.
> 2. Перейдите на вкладку `Сервер (Server)`. Найдите настройку `Сессионный файл Cookie (Session Samesite Cookie)`.
Измените значение с `Lax/Strict` на `None (Нет)` (это разрешит отправку куки при переходе с другого сайта).
--- ---
## 📚 Полезные ресурсы OpenCart ## 📚 Полезные ресурсы OpenCart

View File

@ -5,13 +5,15 @@ declare(strict_types=1);
namespace Opencart\Admin\Controller\Extension\PayAnyWay\Payment; namespace Opencart\Admin\Controller\Extension\PayAnyWay\Payment;
/** /**
* Class PayAnyWay
*
* @package Opencart\Admin\Controller\Extension\PayAnyWay\Payment * @package Opencart\Admin\Controller\Extension\PayAnyWay\Payment
*/ */
class PayAnyWay extends \Opencart\System\Engine\Controller class PayAnyWay extends \Opencart\System\Engine\Controller
{ {
public const CMS_NAME = 'OpenCart'; public const CMS_NAME = 'OpenCart';
public const MODULE_NAME = 'payanyway'; public const MODULE_NAME = 'payanyway';
public const MODULE_VERSION = '1.0.2'; public const MODULE_VERSION = '1.0.0';
public const MODULE_DEFAULT_SORT_ORDER = 0; public const MODULE_DEFAULT_SORT_ORDER = 0;
private const SUPPORTED_CURRENCIES = ['RUB']; private const SUPPORTED_CURRENCIES = ['RUB'];
@ -318,10 +320,9 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$cmsName = $this->detectCmsByPatterns($textFooter); $cmsName = $this->detectCmsByPatterns($textFooter);
$cmsVersion = defined('VERSION') ? constant('VERSION') : '.unknown'; $cmsVersion = defined('VERSION') ? constant('VERSION') : '.unknown';
return sprintf('%s v%s|PHP %s|%s v%s', return sprintf('%s v%s | %s v%s',
$cmsName, $cmsName,
$cmsVersion, $cmsVersion,
PHP_VERSION,
self::MODULE_NAME, self::MODULE_NAME,
self::MODULE_VERSION self::MODULE_VERSION
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -28,7 +28,7 @@
{{ text_edit }} {{ text_edit }}
</div> </div>
<div class="card-header text-center"> <div class="card-header text-center">
<a href="https://www.payanyway.ru/" target="_blank"><img src="../extension/payanyway/admin/view/image/payment/payanyway.png" alt="PayAnyWay" title="PayAnyWay" width="336" height="50" /></a> <a href="https://www.payanyway.ru/" target="_blank"><img src="../extension/payanyway/admin/view/image/payment/payanyway.png" alt="PayAnyWay" title="PayAnyWay" width="250" height="29" /></a>
</div> </div>
<div class="card-body"> <div class="card-body">
<form id="form-payment" action="{{ save }}" method="post" data-oc-toggle="ajax"> <form id="form-payment" action="{{ save }}" method="post" data-oc-toggle="ajax">

View File

@ -13,18 +13,11 @@ class Callback extends \Opencart\System\Engine\Controller
{ {
use \Opencart\Catalog\Library\Extension\PayAnyWay\Traits\PayAnyWay; use \Opencart\Catalog\Library\Extension\PayAnyWay\Traits\PayAnyWay;
private const FAIL_RESPONSE = 'FAIL'; private const INVENTORY_ITEM_NAME_MAX_LENGTH = 128;
private const XML_CONTENT_TYPE = 'application/xml'; private const INVENTORY_ITEM_DEFAULT_VAT_TAG = '1105';
private const JSON_CONTENT_TYPE = 'application/json'; private const INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD = 'full_payment';
private const TEXT_CONTENT_TYPE = 'text/plain; charset=UTF-8'; private const INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT = 'commodity';
private const INVENTORY_ITEM_DEFAULT_MEASURE = 'unit';
public const DESCRIPTION_MAX_LENGTH = 500;
private const ITEM_NAME_MAX_LENGTH = 128;
private const ITEM_DEFAULT_VAT_TAG = '1105';
private const DEFAULT_PAYMENT_METHOD = 'full_payment';
private const DEFAULT_PAYMENT_OBJECT = 'commodity';
private const SHIPPING_PAYMENT_OBJECT = 'service';
private const DEFAULT_MEASURE = 'unit';
private const CANCEL_ORDER_STATUS_IDS = [7, 9, 16]; private const CANCEL_ORDER_STATUS_IDS = [7, 9, 16];
@ -49,7 +42,7 @@ class Callback extends \Opencart\System\Engine\Controller
parent::__construct($registry); parent::__construct($registry);
if (!$this->validateOrder()) { if (!$this->validateOrder()) {
$this->sendResponse(self::FAIL_RESPONSE); $this->sendResponse('FAIL');
} }
$this->initializeModule(); $this->initializeModule();
@ -59,8 +52,6 @@ class Callback extends \Opencart\System\Engine\Controller
/** @throws \Exception */ /** @throws \Exception */
public function index(): void public function index(): void
{ {
$this->validateCallback();
($this->callbackData['MNT_COMMAND'] === 'CHECK') ($this->callbackData['MNT_COMMAND'] === 'CHECK')
? $this->handleCheckCallback() ? $this->handleCheckCallback()
: $this->handlePayCallback(); : $this->handlePayCallback();
@ -83,69 +74,62 @@ class Callback extends \Opencart\System\Engine\Controller
]; ];
} }
private function validateCallback(): void private function sendResponse(string $response, int $statusCode = 200): void
{
if (empty($this->mntId) || empty($this->integrityCode)) {
$this->sendResponse(self::FAIL_RESPONSE);
}
if ($this->mntId !== (int)$this->callbackData['MNT_ID']) {
$this->sendResponse(self::FAIL_RESPONSE);
}
}
/**
* @param array|string $response
* @param int $statusCode
* @return void
*/
private function sendResponse(array|string $response, int $statusCode = 200): void
{ {
http_response_code($statusCode); http_response_code($statusCode);
if (is_string($response)) {
header('Content-Type: ' . self::TEXT_CONTENT_TYPE);
echo $response;
} else {
header('Content-Type: ' . $response['contentType']);
echo $response['data'];
}
exit(); $isXml = str_starts_with($response, '<?xml') || str_contains($response, '<MNT_RESPONSE');
header('Content-Type: ' . ($isXml ? 'application/xml' : 'text/plain; charset=UTF-8'));
echo $response;
exit;
} }
/** @throws \Exception */ /** @throws \Exception */
private function handleCheckCallback(): void private function handleCheckCallback(): void
{ {
if (!$this->checkSignature($this->callbackData)) { if (!$this->checkSignature($this->callbackData)) {
$this->sendResponse(self::FAIL_RESPONSE); $this->sendResponse('FAIL');
} }
[$resultCode, $description] = $this->determineCheckResult(); $this->load->model('account/order');
$responseData = $this->buildBaseResponseData($resultCode, $description); $products = $this->model_account_order->getProducts($this->orderId);
$deliveryPrice = $this->getDeliveryPrice();
$products = $this->getAccountOrderProducts(); $xmlData = [
$deliveryInfo = $this->getDeliveryInfo(); 'MNT_ID' => $this->mntId,
'MNT_TRANSACTION_ID' => $this->callbackData['MNT_TRANSACTION_ID'],
'MNT_AMOUNT' => $this->formatPrice((float)($this->orderInfo['total'] ?? 0)),
'MNT_CURRENCY_CODE' => $this->callbackData['MNT_CURRENCY_CODE'],
'inventory' => $this->getInventoryJson($products) ?: null,
'client' => $this->callbackData['MNT_SUBSCRIBER_ID'],
'sno' => null,
'delivery' => (null !== $deliveryPrice) ? $this->formatPrice($deliveryPrice) : null,
];
$responseData['client'] = $this->getClientInfo(); [$xmlData['MNT_RESULT_CODE'], $xmlData['MNT_DESCRIPTION']] = $this->determineCheckResult();
$responseData['items'] = $this->getCheckItems($products, $deliveryInfo);
$this->sendResponse($this->buildJsonResponse($responseData)); $xmlData['MNT_SIGNATURE'] = md5(
$xmlData['MNT_RESULT_CODE'] .
$xmlData['MNT_ID'] .
$xmlData['MNT_TRANSACTION_ID'] .
$this->integrityCode,
);
$this->sendResponse($this->buildXMLResponse($xmlData));
} }
/** /** @throws \Exception */
* @return array<int|string>
* @throws \Exception
*/
private function determineCheckResult(): array private function determineCheckResult(): array
{ {
$orderStatusId = isset($this->orderInfo['order_status_id']) ? (int)$this->orderInfo['order_status_id'] : null; $orderStatusId = isset($this->orderInfo['order_status_id']) ? (int)$this->orderInfo['order_status_id'] : null;
if (null === $orderStatusId) { if (null === $orderStatusId) {
return [500, 'Order status not set']; return [500, "Order status not set"];
} }
$orderStatus = $this->getOrderStatusById($orderStatusId); $orderStatus = $this->getOrderStatusById($orderStatusId);
if (empty($this->callbackData['MNT_AMOUNT'])) { if (empty($this->callbackData['MNT_AMOUNT'])) {
return [100, "Order status is {$orderStatus}"]; return [100, "Order status is '{$orderStatus}'"];
} }
if ($this->successOrderStatusId === $orderStatusId) { if ($this->successOrderStatusId === $orderStatusId) {
@ -153,7 +137,7 @@ class Callback extends \Opencart\System\Engine\Controller
} }
if (in_array($orderStatusId, self::CANCEL_ORDER_STATUS_IDS, true)) { if (in_array($orderStatusId, self::CANCEL_ORDER_STATUS_IDS, true)) {
return [500, "Order status is {$orderStatus}"]; return [500, "Order status is '{$orderStatus}'"];
} }
return [402, 'Order created, but not paid']; return [402, 'Order created, but not paid'];
@ -166,62 +150,43 @@ class Callback extends \Opencart\System\Engine\Controller
'MNT_ID' => $this->mntId, 'MNT_ID' => $this->mntId,
'MNT_TRANSACTION_ID' => $this->callbackData['MNT_TRANSACTION_ID'], 'MNT_TRANSACTION_ID' => $this->callbackData['MNT_TRANSACTION_ID'],
'MNT_OPERATION_ID' => $this->callbackData['MNT_OPERATION_ID'], 'MNT_OPERATION_ID' => $this->callbackData['MNT_OPERATION_ID'],
'MNT_AMOUNT' => $this->formatPrice((float)($this->orderInfo['total'] ?? 0)), 'MNT_AMOUNT' => $this->formatPrice((float)$this->orderInfo['total']),
'MNT_CURRENCY_CODE' => $this->orderInfo['currency_code'] ?? 'RUB', 'MNT_CURRENCY_CODE' => $this->orderInfo['currency_code'],
'MNT_SUBSCRIBER_ID' => $this->callbackData['MNT_SUBSCRIBER_ID'], 'MNT_SUBSCRIBER_ID' => $this->callbackData['MNT_SUBSCRIBER_ID'],
'MNT_TEST_MODE' => $this->callbackData['MNT_TEST_MODE'], 'MNT_TEST_MODE' => $this->callbackData['MNT_TEST_MODE'],
]; ];
if (!$this->checkSignature($shopData)) { if (!$this->checkSignature($shopData)) {
$this->sendResponse(self::FAIL_RESPONSE); $this->sendResponse('FAIL');
} }
$isPayedOrder = $this->isOrderPaid(); $isPayedOrder = $this->isOrderPaid();
$resultCode = 200;
$this->load->model('account/order');
$products = $this->model_account_order->getProducts($this->orderId);
$deliveryPrice = $this->getDeliveryPrice();
$xmlData = [
'MNT_ID' => $this->mntId,
'MNT_TRANSACTION_ID' => $this->callbackData['MNT_TRANSACTION_ID'],
'MNT_RESULT_CODE' => $resultCode,
'MNT_SIGNATURE' => md5($resultCode . $this->mntId . $this->callbackData['MNT_TRANSACTION_ID'] . $this->integrityCode),
'MNT_AMOUNT' => $this->callbackData['MNT_AMOUNT'],
'MNT_CURRENCY_CODE' => $this->callbackData['MNT_CURRENCY_CODE'],
'MNT_DESCRIPTION' => $isPayedOrder ? 'Order already paid' : 'Order success paid',
'inventory' => $this->getInventoryJson($products),
'client' => $this->callbackData['MNT_SUBSCRIBER_ID'],
'sno' => null,
'delivery' => $deliveryPrice !== null ? $this->formatPrice($deliveryPrice) : null,
];
if (!$isPayedOrder) { if (!$isPayedOrder) {
$this->load->language('extension/payanyway/payment/payanyway'); $this->load->language('extension/payanyway/payment/payanyway');
$this->addOrderHistory($this->orderId, $this->successOrderStatusId, $this->language->get('text_payment_completed'),); $this->addOrderHistory($this->orderId, $this->successOrderStatusId, $this->language->get('text_payment_completed'),);
} }
$resultCode = 200; $this->sendResponse($this->buildXMLResponse($xmlData));
$description = $isPayedOrder ? 'Order already paid' : 'Order success paid';
$responseData = $this->buildBaseResponseData($resultCode, $description);
$products = $this->getAccountOrderProducts();
$deliveryInfo = $this->getDeliveryInfo();
$payInventoryJson = $this->getPayInventoryJson($products, $deliveryInfo);
$responseData['inventory'] = $payInventoryJson ?: null;
$clientInfo = $this->getClientInfo();
$responseData['client'] = $clientInfo
? json_encode([$clientInfo], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
: null;
$this->sendResponse($this->buildXmlResponse($responseData));
}
/**
* @param int $resultCode
* @param string $description
* @return array<string, mixed>
* @throws \Exception
*/
private function buildBaseResponseData(int $resultCode, string $description): array
{
$orderTotal = (float)($this->orderInfo['total'] ?? 0);
$data = [
'MNT_ID' => $this->mntId,
'MNT_TRANSACTION_ID' => $this->callbackData['MNT_TRANSACTION_ID'],
'MNT_RESULT_CODE' => $resultCode,
'MNT_DESCRIPTION' => $this->validateString($description, self::DESCRIPTION_MAX_LENGTH),
'MNT_AMOUNT' => $this->formatPrice($orderTotal),
'MNT_CURRENCY_CODE' => $this->callbackData['MNT_CURRENCY_CODE'],
];
$signatureSource = $resultCode . $this->mntId . $this->callbackData['MNT_TRANSACTION_ID'] . $this->integrityCode;
$data['MNT_SIGNATURE'] = md5($signatureSource);
return $data;
} }
private function isOrderPaid(): bool private function isOrderPaid(): bool
@ -253,97 +218,35 @@ class Callback extends \Opencart\System\Engine\Controller
return hash_equals(md5($signatureString), $this->callbackData['MNT_SIGNATURE']); return hash_equals(md5($signatureString), $this->callbackData['MNT_SIGNATURE']);
} }
private function getDeliveryInfo(): ?array private function getDeliveryPrice(): ?float
{ {
$this->load->model('account/order'); $this->load->model('account/order');
foreach ($this->model_account_order->getTotals($this->orderId) as $orderTotal) { foreach ($this->model_account_order->getTotals($this->orderId) as $orderTotal) {
if (($orderTotal['code'] ?? '') === 'shipping') { if (($orderTotal['code'] ?? '') === 'shipping') {
return $orderTotal; $deliveryPrice = (float)($orderTotal['value'] ?? 0);
return $deliveryPrice !== 0.0 ? $deliveryPrice : null;
} }
} }
return null; return null;
} }
private function getClientInfo(): array /**
* @param array<string, mixed> $products
* @return false|string
*/
private function getInventoryJson(array $products): false|string
{ {
$clientInfo = [];
$name = $this->getClientName();
if (null !== $name) {
$clientInfo['name'] = $name;
}
$email = $this->getClientEmail();
if (null !== $email) {
$clientInfo['email'] = $email;
}
$phoneNumber = $this->getClientPhone();
if (null !== $phoneNumber) {
$clientInfo['phone'] = $phoneNumber;
}
return $clientInfo;
}
private function getCheckItems(array $products, ?array $deliveryInfo): array
{
$checkItems = array();
foreach ($products as $product) {
$checkItems[] = array(
'name' => $this->validateString($product['name'] ?? '', self::ITEM_NAME_MAX_LENGTH),
'price' => $this->formatPrice((float)($product['price'] ?? 0)),
'quantity' => $this->formatQuantity((float)($product['quantity'] ?? 0)),
'vat' => $this->getVat($this->vatRate),
'measure' => self::DEFAULT_MEASURE,
'paymentMethod' => self::DEFAULT_PAYMENT_METHOD,
'paymentObject' => self::DEFAULT_PAYMENT_OBJECT,
);
}
if (null === $deliveryInfo) {
return $checkItems;
}
$checkItems[] = array(
'name' => $this->validateString($deliveryInfo['title'] ?? '', self::ITEM_NAME_MAX_LENGTH),
'price' => $this->formatPrice((float)($deliveryInfo['value'] ?? 0)),
'quantity' => 1,
'vat' => $this->getVat($this->vatRate),
'measure' => self::DEFAULT_MEASURE,
'paymentMethod' => self::DEFAULT_PAYMENT_METHOD,
'paymentObject' => self::SHIPPING_PAYMENT_OBJECT,
);
return $checkItems;
}
private function getPayInventoryJson(array $products, ?array $deliveryInfo): false|string
{
if (empty($products) && empty($deliveryInfo)) {
return false;
}
$inventory = []; $inventory = [];
foreach ($products as $product) { foreach ($products as $product) {
$inventory[] = [ $inventory[] = [
'name' => $this->validateString($product['name'] ?? '', self::ITEM_NAME_MAX_LENGTH), 'name' => $this->validateString($product['name'] ?? '', self::INVENTORY_ITEM_NAME_MAX_LENGTH),
'price' => $this->formatPrice((float)($product['price'] ?? 0)), 'price' => $this->formatPrice((float)($product['price'] ?? 0)),
'quantity' => $this->formatQuantity((float)($product['quantity'] ?? 0)), 'quantity' => $this->formatQuantity((string)($product['quantity'] ?? '0')),
'vatTag' => $this->vatRate, 'vatTag' => $this->vatRate,
'pm' => self::DEFAULT_PAYMENT_METHOD, 'pm' => self::INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD,
'po' => self::DEFAULT_PAYMENT_OBJECT, 'po' => self::INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT,
'measure' => self::DEFAULT_MEASURE, 'measure' => self::INVENTORY_ITEM_DEFAULT_MEASURE,
];
}
if (null !== $deliveryInfo) {
$inventory[] = [
'name' => $this->validateString($deliveryInfo['title'] ?? '', self::ITEM_NAME_MAX_LENGTH),
'price' => $this->formatPrice((float)($deliveryInfo['value'] ?? 0)),
'quantity' => '1',
'vatTag' => $this->vatRate,
'pm' => self::DEFAULT_PAYMENT_METHOD,
'po' => self::SHIPPING_PAYMENT_OBJECT,
'measure' => self::DEFAULT_MEASURE,
]; ];
} }
@ -373,45 +276,22 @@ class Callback extends \Opencart\System\Engine\Controller
return $orderStatus; return $orderStatus;
} }
private function formatQuantity(float $quantity): string private function formatQuantity(string $quantity): string
{ {
$quantityString = number_format($quantity, 3, '.', ''); $quantity = str_replace(',', '.', $quantity);
return rtrim(rtrim($quantityString, '0'), '.');
}
/** if (!str_contains($quantity, '.') && ctype_digit($quantity)) {
* @param array<non-empty-string, mixed> $data return (string)(int)$quantity;
* @return array }
*/
public function buildJsonResponse(array $data): array
{
$jsonData = [
'id' => $data['MNT_ID'],
'transactionId' => $data['MNT_TRANSACTION_ID'],
'amount' => $data['MNT_AMOUNT'],
'signature' => $data['MNT_SIGNATURE'],
'resultCode' => $data['MNT_RESULT_CODE'],
'description' => $data['MNT_DESCRIPTION'],
'externalId' => $data['MNT_TRANSACTION_ID'],
'receipt' => [
'client' => $data['client'],
'items' => $data['items'],
],
];
$json = json_encode($jsonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); return number_format((float)$quantity, 3, '.', '');
return [
'data' => $json ?: '',
'contentType' => self::JSON_CONTENT_TYPE,
];
} }
/** /**
* @param array<string, mixed> $data * @param array<string, mixed> $data
* @throws \Exception * @throws \Exception
*/ */
private function buildXmlResponse(array $data): array private function buildXMLResponse(array $data): false|string
{ {
$dom = new \DOMDocument('1.0', 'UTF-8'); $dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = false; $dom->formatOutput = false;
@ -454,40 +334,7 @@ class Callback extends \Opencart\System\Engine\Controller
} }
} }
$xml = $dom->saveXML(); return $dom->saveXML();
return array(
'data' => (false !== $xml) ? $xml : self::FAIL_RESPONSE,
'contentType' => (false !== $xml) ? self::XML_CONTENT_TYPE : self::TEXT_CONTENT_TYPE,
);
}
public function getVat($vatRate): string
{
return match ($vatRate) {
'1104' => 'vat0',
'1108' => 'vat5',
'1109' => 'vat7',
'1103' => 'vat10',
'7000' => 'vat20',
'1113' => 'vat22',
'1110' => 'vat105',
'1111' => 'vat107',
'1107' => 'vat110',
'7001' => 'vat120',
'1114' => 'vat122',
default => 'none'
};
}
/**
* @return array
* @throws \Exception
*/
public function getAccountOrderProducts(): array
{
$this->load->model('account/order');
return $this->model_account_order->getProducts($this->orderId);
} }
/** @param array<string, mixed> $params */ /** @param array<string, mixed> $params */

View File

@ -4,38 +4,8 @@ declare(strict_types=1);
namespace Opencart\Catalog\Controller\Extension\PayAnyWay\Payment; namespace Opencart\Catalog\Controller\Extension\PayAnyWay\Payment;
use Opencart\Catalog\Model\Account\Customer as ModelAccountCustomer;
use Opencart\Catalog\Model\Account\Order as ModelAccountOrder;
use Opencart\Catalog\Model\Checkout\Order as ModelCheckoutOrder;
use Opencart\System\Engine\Config;
use Opencart\System\Engine\Loader;
use Opencart\System\Library\Cart\Currency;
use Opencart\System\Library\Cart\Customer;
use Opencart\System\Library\Document;
use Opencart\System\Library\Language;
use Opencart\System\Library\Request;
use Opencart\System\Library\Response;
use Opencart\System\Library\Session;
use Opencart\System\Library\Url;
require_once DIR_EXTENSION . '/payanyway/catalog/library/payanyway/traits/payanyway.php'; require_once DIR_EXTENSION . '/payanyway/catalog/library/payanyway/traits/payanyway.php';
/**
* @property Config $config
* @property Loader $load
* @property Request $request
* @property Response $response
* @property Document $document
* @property Session $session
* @property Language $language
* @property Url $url
* @property Currency $currency
* @property ModelCheckoutOrder $model_checkout_order
* @property ModelAccountOrder $model_account_order
* @property ModelAccountCustomer $model_account_customer
* @property Customer $customer
*/
class PayAnyWay extends \Opencart\System\Engine\Controller class PayAnyWay extends \Opencart\System\Engine\Controller
{ {
use \Opencart\Catalog\Library\Extension\PayAnyWay\Traits\PayAnyWay; use \Opencart\Catalog\Library\Extension\PayAnyWay\Traits\PayAnyWay;
@ -43,8 +13,7 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
private const PAYMENT_SERVER_PROD = 'prod'; private const PAYMENT_SERVER_PROD = 'prod';
private const PAYMENT_URL_PROD = 'https://www.payanyway.ru/assistant.htm'; private const PAYMENT_URL_PROD = 'https://www.payanyway.ru/assistant.htm';
private const PAYMENT_URL_DEMO = 'https://demo.moneta.ru/assistant.htm'; private const PAYMENT_URL_DEMO = 'https://demo.moneta.ru/assistant.htm';
private const DESCRIPTION_MAX_LENGTH = 500;
public const DESCRIPTION_MAX_LENGTH = 500;
private int $mntId; private int $mntId;
private string $integrityCode; private string $integrityCode;
@ -102,16 +71,14 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->integrityCode, $this->integrityCode,
); );
$data['MNT_SIGNATURE'] = $signature; $data['MNT_SIGNATURE'] = $signature;
$customerToken = $this->session->data['customer_token']; $data['MNT_SUCCESS_URL'] = $this->url->link('extension/payanyway/payment/payanyway.success', 'language=' . $this->config->get('config_language'), true,);
$data['MNT_SUCCESS_URL'] = $this->url->link('extension/payanyway/payment/payanyway.success', 'customer_token=' . $customerToken, true); $data['MNT_FAIL_URL'] = $this->url->link('extension/payanyway/payment/payanyway.fail', 'language=' . $this->config->get('config_language'), true,);
$data['MNT_FAIL_URL'] = $this->url->link('extension/payanyway/payment/payanyway.fail', 'customer_token=' . $customerToken, true); $data['MNT_RETURN_URL'] = $this->url->link('checkout/checkout', 'language=' . $this->config->get('config_language'), true,);
$data['MNT_RETURN_URL'] = $this->url->link('checkout/checkout', 'customer_token=' . $customerToken, true);
$data['moneta_locale'] = $this->language->get('text_locale');
$data['MNT_CMS'] = $this->cmsModuleVersion; $data['MNT_CMS'] = $this->cmsModuleVersion;
$data['btn_confirm'] = $this->language->get('text_paw_pay'); $data['btn_confirm'] = $this->language->get('text_paw_pay');
$data['confirm_url'] = $this->url->link('extension/payanyway/payment/payanyway.confirm', 'customer_token=' . $customerToken, true,); $data['confirm_url'] = $this->url->link('extension/payanyway/payment/payanyway.confirm', 'language=' . $this->config->get('config_language'), true,);
return $this->load->view('extension/payanyway/payment/payanyway', $data); return $this->load->view('extension/payanyway/payment/payanyway', $data);
} }
@ -119,11 +86,7 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
public function confirm(): void public function confirm(): void
{ {
$this->load->language('extension/payanyway/payment/payanyway'); $this->load->language('extension/payanyway/payment/payanyway');
$this->addOrderHistory($this->orderId, $this->pendingOrderStatusId, $this->language->get('text_order_confirmed'),);
$orderStatusId = isset($this->orderInfo['order_status_id']) ? (int)$this->orderInfo['order_status_id'] : null;
if ((null !== $orderStatusId) && ($orderStatusId !== $this->successOrderStatusId)) {
$this->addOrderHistory($this->orderId, $this->pendingOrderStatusId, $this->language->get('text_order_confirmed'),);
}
$json['success'] = true; $json['success'] = true;
@ -134,41 +97,30 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
public function success(): void public function success(): void
{ {
$this->load->language('extension/payanyway/payment/payanyway'); $this->load->language('extension/payanyway/payment/payanyway');
$this->addOrderHistory($this->orderId, $this->pendingOrderStatusId, $this->language->get('text_payment_processing'),);
$orderStatusId = isset($this->orderInfo['order_status_id']) ? (int)$this->orderInfo['order_status_id'] : null; $this->response->redirect($this->url->link('checkout/success', '', true));
if ((null !== $orderStatusId) && ($orderStatusId !== $this->successOrderStatusId)) {
$this->addOrderHistory($this->orderId, $this->pendingOrderStatusId, $this->language->get('text_payment_processing'));
}
$customerToken = $this->request->get['customer_token'] ?? '';
$this->response->redirect($this->url->link('checkout/success', 'customer_token=' . $customerToken, true));
} }
public function fail(): void public function fail(): void
{ {
$this->load->language('extension/payanyway/payment/payanyway'); $this->load->language('extension/payanyway/payment/payanyway');
$this->addOrderHistory($this->orderId, $this->errorOrderStatusId, $this->language->get('text_payment_error'),);
$orderStatusId = isset($this->orderInfo['order_status_id']) ? (int)$this->orderInfo['order_status_id'] : null;
if ((null !== $orderStatusId) && ($orderStatusId !== $this->successOrderStatusId)) {
$this->addOrderHistory($this->orderId, $this->errorOrderStatusId, $this->language->get('text_payment_error'));
}
$this->response->redirect($this->url->link('checkout/failure', '', true)); $this->response->redirect($this->url->link('checkout/failure', '', true));
} }
private function getDescription(): string private function getDescription(): string
{ {
$clientName = trim(($this->orderInfo['firstname'] ?? '') . ' ' . ($this->orderInfo['lastname'] ?? ''));
$orderId = $this->orderInfo['order_id'] ?? null; $orderId = $this->orderInfo['order_id'] ?? null;
$description = "Оплата заказа" . (!empty($orderId) ? "{$orderId}" : ''); $description = "Оплата заказа" . (!empty($orderId) ? "{$orderId}" : '');
$name = $this->getClientName(); if ('' !== $clientName) {
if (null !== $name) { $description .= " от {$clientName}";
$description .= " от {$name}";
} }
$comment = $this->orderInfo['comment'] ?? ''; $comment = $this->orderInfo['comment'] ?? '';
if ('' !== $comment) { if ($comment !== '') {
$description .= ': ' . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8'); $description .= ': ' . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
} }
@ -177,6 +129,13 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
private function getSubscriberId(): string private function getSubscriberId(): string
{ {
return $this->getClientEmail() ?? $this->getClientPhone() ?? ''; $email = filter_var($this->orderInfo['email'] ?? '', FILTER_SANITIZE_EMAIL);
if ('' !== $email) {
return $email;
}
$phoneNumber = preg_replace('/[^0-9+]/', '', $this->orderInfo['telephone'] ?? '');
return $phoneNumber ?: '';
} }
} }

View File

@ -1,6 +1,5 @@
<?php <?php
// Text // Text
$_['text_locale'] = 'en';
$_['text_title'] = 'PayAnyWay (MIR, VISA, MasterCard and 30+ other ways)'; $_['text_title'] = 'PayAnyWay (MIR, VISA, MasterCard and 30+ other ways)';
$_['text_ap_pay_with'] = 'Pay with'; $_['text_ap_pay_with'] = 'Pay with';
$_['text_ap_and_other'] = 'or another payment method'; $_['text_ap_and_other'] = 'or another payment method';

View File

@ -1,6 +1,5 @@
<?php <?php
// Text // Text
$_['text_locale'] = 'ru';
$_['text_title'] = 'PayAnyWay (МИР, Visa, MasterCard и более 30-и способов оплаты)'; $_['text_title'] = 'PayAnyWay (МИР, Visa, MasterCard и более 30-и способов оплаты)';
$_['text_ap_pay_with'] = 'Оплатить с'; $_['text_ap_pay_with'] = 'Оплатить с';
$_['text_ap_and_other'] = 'или другой способ оплаты'; $_['text_ap_and_other'] = 'или другой способ оплаты';

View File

@ -66,7 +66,7 @@ trait PayAnyWay
if (!isset($_REQUEST['MNT_TRANSACTION_ID'])) { if (!isset($_REQUEST['MNT_TRANSACTION_ID'])) {
return null; return null;
} }
$transactionIdData = explode($this->transactionIdDelimiter, $_REQUEST['MNT_TRANSACTION_ID'], 2); $transactionIdData = explode($this->transactionIdDelimiter, $_REQUEST['MNT_TRANSACTION_ID']);
return isset($transactionIdData[0]) ? (int)$transactionIdData[0] : null; return isset($transactionIdData[0]) ? (int)$transactionIdData[0] : null;
} }
@ -87,25 +87,4 @@ trait PayAnyWay
$this->load->model('checkout/order'); $this->load->model('checkout/order');
$this->model_checkout_order->addHistory($orderId, $orderStatusId, $comment); $this->model_checkout_order->addHistory($orderId, $orderStatusId, $comment);
} }
/** @return non-empty-string|null */
private function getClientName(): ?string
{
$name = trim(($this->orderInfo['firstname'] ?? '') . ' ' . ($this->orderInfo['lastname'] ?? ''));
return ('' !== $name) ? $name : null;
}
/** @return non-empty-string|null */
private function getClientEmail(): ?string
{
$email = trim((string)($this->orderInfo['email'] ?? ''));
return ('' !== $email) ? filter_var($email, FILTER_SANITIZE_EMAIL) : null;
}
/** @return non-empty-string|null */
private function getClientPhone(): ?string
{
$digits = preg_replace('/\D/', '', trim((string)($this->orderInfo['telephone'] ?? '')));
return ('' !== $digits) ? '+' . $digits : null;
}
} }

View File

@ -10,7 +10,6 @@
<input type="hidden" name="MNT_SUCCESS_URL" value="{{ MNT_SUCCESS_URL }}"/> <input type="hidden" name="MNT_SUCCESS_URL" value="{{ MNT_SUCCESS_URL }}"/>
<input type="hidden" name="MNT_FAIL_URL" value="{{ MNT_FAIL_URL }}"/> <input type="hidden" name="MNT_FAIL_URL" value="{{ MNT_FAIL_URL }}"/>
<input type="hidden" name="MNT_RETURN_URL" value="{{ MNT_RETURN_URL }}"/> <input type="hidden" name="MNT_RETURN_URL" value="{{ MNT_RETURN_URL }}"/>
<input type="hidden" name="moneta.locale" value="{{ moneta_locale }}"/>
<input type="hidden" name="MNT_CMS" value="{{ MNT_CMS }}"/> <input type="hidden" name="MNT_CMS" value="{{ MNT_CMS }}"/>
<div class="buttons"> <div class="buttons">
<div class="{% if ap_use %} text-center {% else %} pull-right {% endif %}"> <div class="{% if ap_use %} text-center {% else %} pull-right {% endif %}">

View File

@ -1,7 +1,7 @@
{ {
"code": "payanyway", "code": "payanyway",
"name": "PayAnyWay", "name": "PayAnyWay",
"version": "1.0.2", "version": "1.0.0",
"author": "PayAnyWay", "author": "PayAnyWay",
"link": "https://www.payanyway.ru/" "link": "https://www.payanyway.ru/"
} }