feat: add fiscalization settings, POST callbacks and fix callback issues

This commit is contained in:
2026-10-21 11:41:23 +03:00
committed by Git Moneta
parent d1ac3d989b
commit 6598b95bb8
9 changed files with 1103 additions and 210 deletions
@@ -9,9 +9,13 @@
class ControllerExtensionPaymentPayanyway extends Controller {
const CMS_NAME = 'OpenCart';
const MODULE_NAME = 'payanyway';
const MODULE_VERSION = '1.0.3';
const MODULE_VERSION = '1.0.4';
const MODULE_DEFAULT_SORT_ORDER = 0;
/** Маркер вставки OCMOD из install.xml в роутере витрины (приём Check/Pay URL методом POST). */
const OCMOD_ROUTER_MARKER = 'payanyway-post-route';
const OCMOD_ROUTER_FILE = 'catalog/controller/startup/router.php';
private $supportedCurrencies = array('RUB');
private $availableVatRates = array(
'vat_none' => '1105',
@@ -26,6 +30,47 @@ class ControllerExtensionPaymentPayanyway extends Controller {
'vat122' => '1114'
);
/** Признак способа расчёта (kassaspecification.pdf, pm). */
private $paymentMethods = array(
'full_prepayment',
'prepayment',
'advance',
'full_payment',
'partial_payment',
'credit',
'credit_payment'
);
/** Признак предмета расчёта (kassaspecification.pdf, po). */
private $paymentObjects = array(
'commodity',
'excise',
'job',
'service',
'gambling_bet',
'gambling_prize',
'lottery',
'lottery_prize',
'intellectual_activity',
'payment',
'agent_commission',
'composite',
'another',
'property_right',
'sales_tax',
'resort_fee',
'award',
'non_operating',
'insurance_premium'
);
/** Canceled, Canceled Reversal, Voided штатной установки OpenCart. */
private $defaultCanceledOrderStatusIds = array(7, 9, 16);
const RECEIPT_EMAIL_MAX_LENGTH = 64;
const DEFAULT_PAYMENT_METHOD = 'full_payment';
const DEFAULT_PAYMENT_OBJECT = 'commodity';
private $error = array();
/**
@@ -47,6 +92,15 @@ class ControllerExtensionPaymentPayanyway extends Controller {
$data = array();
$this->applyValidationErrors($data);
$data['warning_ocmod'] = $this->isRouterModificationApplied()
? ''
: sprintf(
$this->language->get('warning_ocmod_router'),
$this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'], true)
);
$data['warning_receipt_email'] = ($this->isFiscalizationEnabled() && ('' === (string)$this->config->get('payment_payanyway_receipt_email')))
? $this->language->get('warning_receipt_email')
: '';
$data['breadcrumbs'] = $this->getBreadcrumbs();
$data['action'] = $this->url->link('extension/payment/payanyway', 'user_token=' . $this->session->data['user_token'], true);
@@ -56,6 +110,13 @@ class ControllerExtensionPaymentPayanyway extends Controller {
$data['payment_payanyway_account'] = $this->retrieveValue('payment_payanyway_account');
$data['payment_payanyway_integrity_code'] = $this->getIntegrityCode('payment_payanyway_integrity_code');
$data['payment_payanyway_server'] = $this->retrieveValue('payment_payanyway_server');
$data['payment_payanyway_test_mode'] = (string)$this->retrieveValue('payment_payanyway_test_mode');
$data['payment_payanyway_fiscalization'] = $this->isFiscalizationEnabled() ? '1' : '0';
$data['payment_payanyway_receipt_email'] = htmlspecialchars((string)$this->retrieveRawValue('payment_payanyway_receipt_email'), ENT_QUOTES, 'UTF-8');
$data['payment_payanyway_payment_methods'] = $this->getOptions($this->paymentMethods, 'entry_payment_method_');
$data['payment_payanyway_payment_method'] = $this->retrieveValueOrDefault('payment_payanyway_payment_method', self::DEFAULT_PAYMENT_METHOD);
$data['payment_payanyway_payment_objects'] = $this->getOptions($this->paymentObjects, 'entry_payment_object_');
$data['payment_payanyway_payment_object'] = $this->retrieveValueOrDefault('payment_payanyway_payment_object', self::DEFAULT_PAYMENT_OBJECT);
$data['payment_payanyway_vat_rates'] = $this->getModuleVatRates();
$data['payment_payanyway_vat_rate'] = $this->retrieveValue('payment_payanyway_vat_rate');
$data['payment_payanyway_currencies'] = $this->getModuleCurrencies();
@@ -64,10 +125,11 @@ class ControllerExtensionPaymentPayanyway extends Controller {
$data['payment_payanyway_pending_order_status_id'] = $this->retrieveValue('payment_payanyway_pending_order_status_id');
$data['payment_payanyway_success_order_status_id'] = $this->retrieveValue('payment_payanyway_success_order_status_id');
$data['payment_payanyway_error_order_status_id'] = $this->retrieveValue('payment_payanyway_error_order_status_id');
$data['payment_payanyway_canceled_order_status_ids'] = $this->getCanceledOrderStatusIds();
$data['geo_zones'] = $this->getAvailableGeoZones();
$data['payment_payanyway_geo_zone_id'] = $this->retrieveValue('payment_payanyway_geo_zone_id');
$data['payment_payanyway_sort_order'] = $this->retrieveValue('payment_payanyway_sort_order');
$data['payment_payanyway_cms_module_version'] = $this->getCmsModuleVersion();
$data['callback_url'] = HTTPS_CATALOG . 'index.php?route=extension/payment/payanyway/callback';
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
@@ -76,6 +138,39 @@ class ControllerExtensionPaymentPayanyway extends Controller {
$this->response->setOutput($this->load->view('extension/payment/payanyway', $data));
}
/**
* @return bool
*/
private function isFiscalizationEnabled() {
$value = $this->retrieveValue('payment_payanyway_fiscalization');
return (null === $value) ? true : (bool)$value;
}
/**
* @return int[]
*/
private function getCanceledOrderStatusIds() {
$value = $this->retrieveValue('payment_payanyway_canceled_order_status_ids');
if (null === $value) {
return $this->defaultCanceledOrderStatusIds;
}
return array_map('intval', (array)$value);
}
/**
* @return bool
*/
private function isRouterModificationApplied() {
$file = DIR_MODIFICATION . self::OCMOD_ROUTER_FILE;
if (!is_file($file)) {
return false;
}
return false !== strpos((string)file_get_contents($file), self::OCMOD_ROUTER_MARKER);
}
/**
* @return void
* @throws \Exception
@@ -86,8 +181,14 @@ class ControllerExtensionPaymentPayanyway extends Controller {
'payment_payanyway_account' => '',
'payment_payanyway_integrity_code' => '',
'payment_payanyway_server' => 'prod',
'payment_payanyway_test_mode' => 0,
'payment_payanyway_fiscalization' => 1,
'payment_payanyway_receipt_email' => '',
'payment_payanyway_payment_method' => self::DEFAULT_PAYMENT_METHOD,
'payment_payanyway_payment_object' => self::DEFAULT_PAYMENT_OBJECT,
'payment_payanyway_canceled_order_status_ids' => $this->defaultCanceledOrderStatusIds,
'payment_payanyway_sort_order' => 0,
'payment_payanyway_cms_module_version' => $this->getCmsModuleVersion(),
'payment_payanyway_cms_name' => $this->getCmsName(),
);
$orderStatuses = $this->getAvailableOrderStatuses();
@@ -139,14 +240,45 @@ class ControllerExtensionPaymentPayanyway extends Controller {
}
if (!isset($request->post['payment_payanyway_account']) ||
!preg_match('/^[0-9]{1,20}$/', (string)$request->post['payment_payanyway_account']) ||
((int)$request->post['payment_payanyway_account'] <= 0)
) {
$this->error['account'] = $this->language->get('error_account');
}
if (empty($request->post['payment_payanyway_integrity_code'])) {
// OpenCart экранирует весь POST (htmlspecialchars в system/library/request.php): код с & " < >
// сохранился бы экранированным, и подписи не сошлись бы с MONETA.RU.
$integrityCode = $this->decodePostedString($request, 'payment_payanyway_integrity_code');
if ('' === $integrityCode) {
$this->error['integrity_code'] = $this->language->get('error_integrity_code');
}
$request->post['payment_payanyway_integrity_code'] = $integrityCode;
$fiscalization = !empty($request->post['payment_payanyway_fiscalization']);
$request->post['payment_payanyway_fiscalization'] = $fiscalization ? 1 : 0;
$request->post['payment_payanyway_test_mode'] = empty($request->post['payment_payanyway_test_mode']) ? 0 : 1;
$receiptEmail = $this->decodePostedString($request, 'payment_payanyway_receipt_email');
if (($fiscalization || ('' !== $receiptEmail)) && !$this->isValidReceiptEmail($receiptEmail)) {
$this->error['receipt_email'] = $this->language->get('error_receipt_email');
}
$request->post['payment_payanyway_receipt_email'] = $receiptEmail;
if (!isset($request->post['payment_payanyway_payment_method']) ||
!in_array($request->post['payment_payanyway_payment_method'], $this->paymentMethods, true)
) {
$request->post['payment_payanyway_payment_method'] = self::DEFAULT_PAYMENT_METHOD;
}
if (!isset($request->post['payment_payanyway_payment_object']) ||
!in_array($request->post['payment_payanyway_payment_object'], $this->paymentObjects, true)
) {
$request->post['payment_payanyway_payment_object'] = self::DEFAULT_PAYMENT_OBJECT;
}
$canceled = isset($request->post['payment_payanyway_canceled_order_status_ids'])
? (array)$request->post['payment_payanyway_canceled_order_status_ids']
: array();
$request->post['payment_payanyway_canceled_order_status_ids'] = array_values(array_unique(array_map('intval', $canceled)));
if (empty($request->post['payment_payanyway_pending_order_status_id'])) {
$this->error['pending_order_status'] = $this->language->get('error_order_status');
@@ -162,7 +294,7 @@ class ControllerExtensionPaymentPayanyway extends Controller {
? (int)$request->post['payment_payanyway_sort_order']
: self::MODULE_DEFAULT_SORT_ORDER;
$request->post['payment_payanyway_sort_order'] = $sortOrder;
$request->post['payment_payanyway_cms_module_version'] = $this->getCmsModuleVersion();
$request->post['payment_payanyway_cms_name'] = $this->getCmsName();
return empty($this->error);
}
@@ -175,6 +307,7 @@ class ControllerExtensionPaymentPayanyway extends Controller {
$data['error_warning'] = isset($this->error['warning']) ? $this->error['warning'] : '';
$data['error_account'] = isset($this->error['account']) ? $this->error['account'] : '';
$data['error_integrity_code'] = isset($this->error['integrity_code']) ? $this->error['integrity_code'] : '';
$data['error_receipt_email'] = isset($this->error['receipt_email']) ? $this->error['receipt_email'] : '';
$data['error_pending_order_status'] = isset($this->error['pending_order_status']) ? $this->error['pending_order_status'] : '';
$data['error_success_order_status'] = isset($this->error['success_order_status']) ? $this->error['success_order_status'] : '';
$data['error_error_order_status'] = isset($this->error['error_order_status']) ? $this->error['error_order_status'] : '';
@@ -203,12 +336,31 @@ class ControllerExtensionPaymentPayanyway extends Controller {
* @return string
*/
private function getIntegrityCode($key) {
$value = $this->retrieveValue($key);
if (null === $value) {
return '';
return htmlspecialchars((string)$this->retrieveRawValue($key), ENT_QUOTES, 'UTF-8');
}
/**
* @param string $email
* @return bool
*/
private function isValidReceiptEmail($email) {
return ('' !== $email)
&& (strlen($email) <= self::RECEIPT_EMAIL_MAX_LENGTH)
&& (false !== filter_var($email, FILTER_VALIDATE_EMAIL));
}
/**
* @param string[] $values
* @param string $labelPrefix
* @return array<string, string>
*/
private function getOptions(array $values, $labelPrefix) {
$result = array();
foreach ($values as $value) {
$result[$value] = $this->language->get($labelPrefix . $value);
}
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
return $result;
}
/** @return array<string, mixed> */
@@ -280,22 +432,15 @@ class ControllerExtensionPaymentPayanyway extends Controller {
return $this->model_localisation_geo_zone->getGeoZones();
}
/** @return string */
private function getCmsModuleVersion() {
/**
* @return string
*/
private function getCmsName() {
$this->load->language('common/footer');
$textFooter = $this->language->get('text_footer');
$textFooter = is_string($textFooter) ? $textFooter : '';
$cmsName = $this->detectCmsByPatterns($textFooter);
$cmsVersion = defined('VERSION') ? constant('VERSION') : '.unknown';
return sprintf('%s v%s|PHP %s|%s v%s',
$cmsName,
$cmsVersion,
PHP_VERSION,
self::MODULE_NAME,
self::MODULE_VERSION
);
return $this->detectCmsByPatterns($textFooter);
}
private function detectCmsByPatterns($textFooter) {
@@ -331,4 +476,41 @@ class ControllerExtensionPaymentPayanyway extends Controller {
return $this->config->get($name);
}
/**
* Строка с формы без экранирования OpenCart: htmlspecialchars_decode обращает ENT_COMPAT
* из system/library/request.php.
*
* @param \Request $request
* @param string $name
* @return string
*/
private function decodePostedString($request, $name) {
$value = isset($request->post[$name]) && is_string($request->post[$name]) ? $request->post[$name] : '';
return trim(htmlspecialchars_decode($value, ENT_COMPAT));
}
/**
* @param string $name
* @return string
*/
private function retrieveRawValue($name) {
if (isset($this->request->post[$name]) && is_string($this->request->post[$name])) {
return $this->request->post[$name];
}
return (string)$this->config->get($name);
}
/**
* @param string $name
* @param string $default
* @return string
*/
private function retrieveValueOrDefault($name, $default) {
$value = (string)$this->retrieveValue($name);
return ('' !== $value) ? $value : $default;
}
}