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

This commit is contained in:
2026-10-26 17:54:23 +03:00
committed by Git Moneta
parent a9bc7f315c
commit b1acb1a1de
13 changed files with 1585 additions and 335 deletions
+235 -24
View File
@@ -10,10 +10,12 @@ namespace Opencart\Admin\Controller\Extension\PayAnyWay\Payment;
class PayAnyWay extends \Opencart\System\Engine\Controller
{
public const CMS_NAME = 'OpenCart';
public const MODULE_NAME = 'payanyway';
public const MODULE_VERSION = '1.0.2';
public const MODULE_VERSION = '1.0.3';
public const MODULE_DEFAULT_SORT_ORDER = 0;
private const ROUTE_STARTUP_CODE = 'payanyway_post_route';
private const ROUTE_STARTUP_ACTION = 'catalog/extension/payanyway/startup/payanyway';
private const SUPPORTED_CURRENCIES = ['RUB'];
private const AVAILABLE_VAT_RATES = [
'vat_none' => '1105',
@@ -28,6 +30,45 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
'vat122' => '1114',
];
private const PAYMENT_METHODS = [
'full_prepayment',
'prepayment',
'advance',
'full_payment',
'partial_payment',
'credit',
'credit_payment',
];
private const PAYMENT_OBJECTS = [
'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 const DEFAULT_CANCELED_ORDER_STATUS_IDS = [7, 9, 16];
private const RECEIPT_EMAIL_MAX_LENGTH = 64;
private const DEFAULT_PAYMENT_METHOD = 'full_payment';
private const DEFAULT_PAYMENT_OBJECT = 'commodity';
private array $errors = [];
/**
@@ -42,13 +83,25 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->applyValidationErrors($data);
$data['breadcrumbs'] = $this->getBreadcrumbs();
$warnings = $this->getWarnings($this->isFiscalizationEnabled(), (string)$this->config->get('payment_payanyway_receipt_email'));
$data['warning_startup'] = $warnings['startup'];
$data['warning_receipt_email'] = $warnings['receipt_email'];
$data['save'] = $this->url->link('extension/payanyway/payment/payanyway.save', 'user_token=' . $this->session->data['user_token']);
$data['back'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment');
$data['payment_payanyway_status'] = $this->retrieveValue('payment_payanyway_status');
$data['payment_payanyway_account'] = $this->retrieveValue('payment_payanyway_account');
$data['payment_payanyway_integrity_code'] = $this->getIntegrityCode();
$data['integrity_code_placeholder'] = $this->hasIntegrityCode()
? $this->language->get('text_integrity_code_saved')
: $this->language->get('entry_integrity_code_description');
$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->config->get('payment_payanyway_receipt_email'), ENT_QUOTES, 'UTF-8');
$data['payment_payanyway_payment_methods'] = $this->getOptions(self::PAYMENT_METHODS, '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(self::PAYMENT_OBJECTS, '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();
@@ -57,10 +110,11 @@ class PayAnyWay extends \Opencart\System\Engine\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'] = HTTP_CATALOG . 'index.php?route=extension/payanyway/payment/callback';
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
@@ -100,8 +154,15 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
if (!$json) {
$this->load->model('setting/setting');
$this->model_setting_setting->editSetting('payment_payanyway', $this->request->post);
if (!$this->isRouteStartupRegistered()) {
$this->registerRouteStartup();
}
$json['success'] = $this->language->get('text_success');
$json['payanyway_warnings'] = $this->getWarnings(
(bool)$this->request->post['payment_payanyway_fiscalization'],
(string)$this->request->post['payment_payanyway_receipt_email']
);
}
$this->response->addHeader('Content-Type: application/json');
@@ -122,8 +183,15 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
'payment_payanyway_account' => '',
'payment_payanyway_integrity_code' => '',
'payment_payanyway_server' => 'prod',
'payment_payanyway_test_mode' => 0,
'payment_payanyway_fiscalization' => 0,
'payment_payanyway_vat_rate' => self::AVAILABLE_VAT_RATES['vat_none'],
'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' => self::DEFAULT_CANCELED_ORDER_STATUS_IDS,
'payment_payanyway_sort_order' => 0,
'payment_payanyway_cms_module_version' => $this->getCmsModuleVersion(),
'payment_payanyway_cms_name' => $this->getCmsName(),
];
$orderStatuses = $this->getAvailableOrderStatuses();
@@ -154,6 +222,8 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->load->model('setting/setting');
$this->model_setting_setting->editSetting('payment_payanyway', $defaultSettings);
$this->registerRouteStartup();
}
public function uninstall(): void
@@ -164,6 +234,69 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->load->model('setting/setting');
$this->model_setting_setting->deleteSetting('payment_payanyway');
$this->load->model('setting/startup');
$this->model_setting_startup->deleteStartupByCode(self::ROUTE_STARTUP_CODE);
}
/**
* Регистрирует startup витрины. Сигнатура addStartup() менялась: 4.0.0–4.0.1 —
* позиционные аргументы, 4.0.2 — массив, 4.1 — массив с обязательным description
* (лишний ключ 4.0.2 игнорирует).
*/
private function registerRouteStartup(): void
{
$this->load->model('setting/startup');
$this->model_setting_startup->deleteStartupByCode(self::ROUTE_STARTUP_CODE);
if (version_compare(VERSION, '4.0.2.0', '<')) {
$this->model_setting_startup->addStartup(self::ROUTE_STARTUP_CODE, self::ROUTE_STARTUP_ACTION, true, 0);
return;
}
$this->model_setting_startup->addStartup([
'code' => self::ROUTE_STARTUP_CODE,
'description' => 'PayAnyWay: Check/Pay URL методом POST',
'action' => self::ROUTE_STARTUP_ACTION,
'status' => 1,
'sort_order' => 0,
]);
}
private function isRouteStartupRegistered(): bool
{
$this->load->model('setting/startup');
return [] !== $this->model_setting_startup->getStartupByCode(self::ROUTE_STARTUP_CODE);
}
private function isRouteStartupActive(): bool
{
$this->load->model('setting/startup');
$startup = $this->model_setting_startup->getStartupByCode(self::ROUTE_STARTUP_CODE);
return !empty($startup['status']) && (($startup['action'] ?? '') === self::ROUTE_STARTUP_ACTION);
}
/**
* Предупреждения над формой настроек; пустая строка — предупреждения нет.
*
* @return array{startup: string, receipt_email: string}
*/
private function getWarnings(bool $fiscalization, string $receiptEmail): array
{
return [
'startup' => $this->isRouteStartupActive()
? ''
: sprintf(
$this->language->get('warning_startup_route'),
$this->url->link('marketplace/startup', 'user_token=' . $this->session->data['user_token'])
),
'receipt_email' => ($fiscalization && ('' === $receiptEmail))
? $this->language->get('warning_receipt_email')
: '',
];
}
private function validate(): bool
@@ -171,14 +304,42 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->errors = [];
if (!isset($this->request->post['payment_payanyway_account']) ||
!preg_match('/^[0-9]{1,20}$/D', (string)$this->request->post['payment_payanyway_account']) ||
((int)$this->request->post['payment_payanyway_account'] <= 0)
) {
$this->errors['account'] = $this->language->get('error_account');
}
if (empty($this->request->post['payment_payanyway_integrity_code'])) {
// OpenCart экранирует весь POST (htmlspecialchars в system/library/request.php): код с & " < >
// сохранился бы экранированным, и подписи не сошлись бы с MONETA.RU.
$integrityCode = $this->decodePostedString('payment_payanyway_integrity_code');
if (('' === $integrityCode) && $this->hasIntegrityCode()) {
$integrityCode = (string)$this->config->get('payment_payanyway_integrity_code');
}
if ('' === $integrityCode) {
$this->errors['integrity_code'] = $this->language->get('error_integrity_code');
}
$this->request->post['payment_payanyway_integrity_code'] = $integrityCode;
$fiscalization = !empty($this->request->post['payment_payanyway_fiscalization']);
$this->request->post['payment_payanyway_fiscalization'] = $fiscalization ? 1 : 0;
$this->request->post['payment_payanyway_test_mode'] = empty($this->request->post['payment_payanyway_test_mode']) ? 0 : 1;
$receiptEmail = $this->decodePostedString('payment_payanyway_receipt_email');
if (($fiscalization || ('' !== $receiptEmail)) && !$this->isValidReceiptEmail($receiptEmail)) {
$this->errors['receipt_email'] = $this->language->get('error_receipt_email');
}
$this->request->post['payment_payanyway_receipt_email'] = $receiptEmail;
if (!in_array($this->request->post['payment_payanyway_payment_method'] ?? '', self::PAYMENT_METHODS, true)) {
$this->request->post['payment_payanyway_payment_method'] = self::DEFAULT_PAYMENT_METHOD;
}
if (!in_array($this->request->post['payment_payanyway_payment_object'] ?? '', self::PAYMENT_OBJECTS, true)) {
$this->request->post['payment_payanyway_payment_object'] = self::DEFAULT_PAYMENT_OBJECT;
}
$canceled = (array)($this->request->post['payment_payanyway_canceled_order_status_ids'] ?? []);
$this->request->post['payment_payanyway_canceled_order_status_ids'] = array_values(array_unique(array_filter(array_map('intval', $canceled), static fn (int $statusId): bool => $statusId > 0)));
if (empty($this->request->post['payment_payanyway_pending_order_status_id'])) {
$this->errors['pending_order_status'] = $this->language->get('error_pending_order_status');
@@ -190,11 +351,20 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$this->errors['error_order_status'] = $this->language->get('error_error_order_status');
}
$moduleStatuses = [
(int)($this->request->post['payment_payanyway_pending_order_status_id'] ?? 0),
(int)($this->request->post['payment_payanyway_success_order_status_id'] ?? 0),
(int)($this->request->post['payment_payanyway_error_order_status_id'] ?? 0),
];
if ([] !== array_intersect($this->request->post['payment_payanyway_canceled_order_status_ids'], array_diff($moduleStatuses, [0]))) {
$this->errors['canceled_order_status'] = $this->language->get('error_canceled_order_status');
}
$sortOrder = isset($this->request->post['payment_payanyway_sort_order'])
? (int)$this->request->post['payment_payanyway_sort_order']
: self::MODULE_DEFAULT_SORT_ORDER;
$this->request->post['payment_payanyway_sort_order'] = $sortOrder;
$this->request->post['payment_payanyway_cms_module_version'] = $this->getCmsModuleVersion();
$this->request->post['payment_payanyway_cms_name'] = $this->getCmsName();
return empty($this->errors);
}
@@ -205,6 +375,7 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
$data['error_warning'] = $this->errors['warning'] ?? '';
$data['error_account'] = $this->errors['account'] ?? '';
$data['error_integrity_code'] = $this->errors['integrity_code'] ?? '';
$data['error_receipt_email'] = $this->errors['receipt_email'] ?? '';
$data['error_pending_order_status'] = $this->errors['pending_order_status'] ?? '';
$data['error_success_order_status'] = $this->errors['success_order_status'] ?? '';
$data['error_error_order_status'] = $this->errors['error_order_status'] ?? '';
@@ -229,14 +400,57 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
];
}
private function getIntegrityCode(): string
private function hasIntegrityCode(): bool
{
$value = $this->retrieveValue('payment_payanyway_integrity_code');
if (null === $value) {
return '';
return '' !== (string)$this->config->get('payment_payanyway_integrity_code');
}
/** Строка с формы без экранирования OpenCart: htmlspecialchars_decode обращает ENT_COMPAT из system/library/request.php. */
private function decodePostedString(string $name): string
{
$value = $this->request->post[$name] ?? '';
return is_string($value) ? trim(htmlspecialchars_decode($value, ENT_COMPAT)) : '';
}
private function isValidReceiptEmail(string $email): bool
{
return ('' !== $email)
&& (strlen($email) <= self::RECEIPT_EMAIL_MAX_LENGTH)
&& (false !== filter_var($email, FILTER_VALIDATE_EMAIL));
}
private function isFiscalizationEnabled(): bool
{
// Config::get() в OpenCart 4 для отсутствующего ключа возвращает '' — наличие через has().
return !$this->config->has('payment_payanyway_fiscalization')
|| (bool)$this->config->get('payment_payanyway_fiscalization');
}
/**
* @return int[]
*/
private function getCanceledOrderStatusIds(): array
{
if (!$this->config->has('payment_payanyway_canceled_order_status_ids')) {
return self::DEFAULT_CANCELED_ORDER_STATUS_IDS;
}
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
return array_map('intval', (array)$this->config->get('payment_payanyway_canceled_order_status_ids'));
}
/**
* @param string[] $values
* @return array<string, string>
*/
private function getOptions(array $values, string $labelPrefix): array
{
$result = [];
foreach ($values as $value) {
$result[$value] = $this->language->get($labelPrefix . $value);
}
return $result;
}
/** @return array<string, mixed> */
@@ -308,23 +522,13 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
return $this->model_localisation_geo_zone->getGeoZones();
}
/** @return string */
private function getCmsModuleVersion(): string
private function getCmsName(): string
{
$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): string
@@ -350,4 +554,11 @@ class PayAnyWay extends \Opencart\System\Engine\Controller
{
return $this->request->post[$name] ?? $this->config->get($name);
}
private function retrieveValueOrDefault(string $name, string $default): string
{
$value = (string)$this->retrieveValue($name);
return ('' !== $value) ? $value : $default;
}
}
+53 -2
View File
@@ -1,6 +1,6 @@
<?php
// Heading
$_['heading_title'] = 'PayAnyWay';
$_['heading_title'] = 'PayAnyWay (MONETA)';
// Text
$_['text_extension'] = 'Extensions';
@@ -15,6 +15,7 @@ $_['entry_account_label'] = 'Account number';
$_['entry_account_description'] = 'Your extended account number in the MONETA.RU system';
$_['entry_integrity_code_label'] = 'Integrity Code';
$_['entry_integrity_code_description'] = 'Data integrity verification code specified in the extended account settings in the MONETA.RU system';
$_['text_integrity_code_saved'] = 'The code is saved. Leave the field empty to keep it';
$_['entry_server_label'] = 'Payment server';
$_['entry_server_prod_label'] = 'Production';
$_['entry_server_demo_label'] = 'Demo';
@@ -44,6 +45,40 @@ $_['entry_error_order_status_label'] = 'Error';
$_['entry_geo_zone_label'] = 'Area of display';
$_['entry_sort_order_label'] = 'Sort Order';
$_['entry_payment_method_label'] = 'Payment method';
$_['entry_payment_object_label'] = 'Payment object';
$_['entry_test_mode_label'] = 'Test mode';
$_['entry_fiscalization_label'] = 'Receipt fiscalization';
$_['entry_receipt_email_label'] = 'Email for receipts';
$_['entry_canceled_order_status_label'] = 'Canceled';
$_['entry_callback_url_label'] = 'Notification URL';
$_['entry_payment_method_full_prepayment'] = 'Full prepayment';
$_['entry_payment_method_prepayment'] = 'Partial prepayment';
$_['entry_payment_method_advance'] = 'Advance';
$_['entry_payment_method_full_payment'] = 'Full payment';
$_['entry_payment_method_partial_payment'] = 'Partial payment and credit';
$_['entry_payment_method_credit'] = 'Credit';
$_['entry_payment_method_credit_payment'] = 'Credit payment';
$_['entry_payment_object_commodity'] = 'Commodity';
$_['entry_payment_object_excise'] = 'Excise commodity';
$_['entry_payment_object_job'] = 'Job';
$_['entry_payment_object_service'] = 'Service';
$_['entry_payment_object_gambling_bet'] = 'Gambling bet';
$_['entry_payment_object_gambling_prize'] = 'Gambling prize';
$_['entry_payment_object_lottery'] = 'Lottery ticket';
$_['entry_payment_object_lottery_prize'] = 'Lottery prize';
$_['entry_payment_object_intellectual_activity'] = 'Intellectual activity';
$_['entry_payment_object_payment'] = 'Payment (advance, deposit, prepayment, credit)';
$_['entry_payment_object_agent_commission'] = 'Agent commission';
$_['entry_payment_object_composite'] = 'Composite';
$_['entry_payment_object_another'] = 'Another';
$_['entry_payment_object_property_right'] = 'Property right';
$_['entry_payment_object_sales_tax'] = 'Sales tax';
$_['entry_payment_object_resort_fee'] = 'Resort fee';
$_['entry_payment_object_award'] = 'Contribution, penalty, fine, reward, bonus';
$_['entry_payment_object_non_operating'] = 'Non-operating income';
$_['entry_payment_object_insurance_premium'] = 'Insurance premium';
// Help
$_['help_status'] = 'Enable or disable the payment module';
$_['help_pending_order_status'] = 'Specify the order status that is set when the order is created';
@@ -51,12 +86,28 @@ $_['help_success_order_status'] = 'Specify the order status that is set upon suc
$_['help_error_order_status'] = 'Specify the order status that is set upon payment error';
$_['help_geo_zone'] = 'Geographic area where the payment method will be displayed';
$_['help_sort_order'] = 'Determines the display order of the payment method. The smaller the number, the higher the position in the list';
$_['help_test_mode'] = 'Simulates payments without charging. Must match the account setting in MONETA.RU';
$_['help_fiscalization'] = 'Enable only if the kassa.payanyway.ru cash register service is connected to the account';
$_['help_receipt_email'] = 'Required with fiscalization. Used in the receipt only when the customer has neither e-mail nor phone: the cash register does not accept a receipt without a contact, so it is sent to this address (the customer name stays in the receipt)';
$_['help_canceled_order_status'] = 'Orders in these statuses are not paid: the module rejects the check request and the payment notification';
$_['help_callback_url'] = 'Set this address as Check URL and Pay URL in the extended account settings';
// Button
$_['button_integrity_code_toggle'] = 'Show or hide';
$_['button_copy'] = 'Copy';
$_['text_copied'] = 'Copied';
// Error
$_['error_permission'] = 'Warning: You do not have permission to modify PayAnyWay payment method!';
$_['error_validation'] = 'Warning: To modify the PayAnyWay payment method, you need to fill in all required fields!';
$_['error_account'] = 'Account Number is required and must be greater than 0';
$_['error_account'] = 'Account number is required and must contain digits only';
$_['error_integrity_code'] = 'Account Integrity Code Required';
$_['error_pending_order_status'] = 'Select Pending order status';
$_['error_success_order_status'] = 'Select Success order status';
$_['error_error_order_status'] = 'Select Error order status';
$_['error_canceled_order_status'] = 'Pending, Success and Error order statuses cannot be among Canceled';
$_['error_receipt_email'] = 'Enter a valid e-mail no longer than 64 characters';
// Warning
$_['warning_startup_route'] = 'The PayAnyWay startup for POST notifications is not registered or is disabled: Check URL and Pay URL notifications sent by POST will not reach the module. Save the module settings (the startup will be registered) or enable <code>payanyway_post_route</code> in <a href="%s">Extensions → Startup</a>. It also releases a paid order from the customer session so that the next purchase in the same browser creates a new order.';
$_['warning_receipt_email'] = 'Receipt fiscalization is enabled but "Email for receipts" is empty: a customer without e-mail and phone will not get a receipt. Set the address below or disable fiscalization if no cash register service is connected.';
+54 -3
View File
@@ -1,6 +1,6 @@
<?php
// Heading
$_['heading_title'] = 'PayAnyWay';
$_['heading_title'] = 'PayAnyWay (МОНЕТА)';
// Text
$_['text_extension'] = 'Расширения';
@@ -15,9 +15,10 @@ $_['entry_account_label'] = 'Номер счёта';
$_['entry_account_description'] = 'Ваш номер расширенного счёта в системе MONETA.RU';
$_['entry_integrity_code_label'] = 'Код проверки целостности данных';
$_['entry_integrity_code_description'] = 'Код проверки целостности данных, указанный в настройках расширенного счёта в системе MONETA.RU';
$_['text_integrity_code_saved'] = 'Код сохранён. Оставьте поле пустым, чтобы не менять его';
$_['entry_server_label'] = 'Сервер платежей';
$_['entry_server_prod_label'] = 'Боевой';
$_['entry_server_demo_label'] = 'Тестовый';
$_['entry_server_demo_label'] = 'Демо';
$_['entry_vat_rate_label'] = 'НДС';
$_['entry_vat_none_label'] = 'Без НДС';
@@ -44,6 +45,40 @@ $_['entry_error_order_status_label'] = 'Неуспешный';
$_['entry_geo_zone_label'] = 'Регион отображения';
$_['entry_sort_order_label'] = 'Порядок сортировки';
$_['entry_payment_method_label'] = 'Признак способа расчёта';
$_['entry_payment_object_label'] = 'Признак предмета расчёта';
$_['entry_test_mode_label'] = 'Тестовый режим';
$_['entry_fiscalization_label'] = 'Фискализация чеков';
$_['entry_receipt_email_label'] = 'Email для чеков';
$_['entry_canceled_order_status_label'] = 'Отменённые';
$_['entry_callback_url_label'] = 'URL для уведомлений';
$_['entry_payment_method_full_prepayment'] = 'Полная предоплата';
$_['entry_payment_method_prepayment'] = 'Частичная предоплата';
$_['entry_payment_method_advance'] = 'Аванс';
$_['entry_payment_method_full_payment'] = 'Полный расчёт';
$_['entry_payment_method_partial_payment'] = 'Частичный расчёт и кредит';
$_['entry_payment_method_credit'] = 'Передача в кредит';
$_['entry_payment_method_credit_payment'] = 'Оплата кредита';
$_['entry_payment_object_commodity'] = 'Товар';
$_['entry_payment_object_excise'] = 'Подакцизный товар';
$_['entry_payment_object_job'] = 'Работа';
$_['entry_payment_object_service'] = 'Услуга';
$_['entry_payment_object_gambling_bet'] = 'Ставка азартной игры';
$_['entry_payment_object_gambling_prize'] = 'Выигрыш азартной игры';
$_['entry_payment_object_lottery'] = 'Лотерейный билет';
$_['entry_payment_object_lottery_prize'] = 'Выигрыш лотереи';
$_['entry_payment_object_intellectual_activity'] = 'Результаты интеллектуальной деятельности';
$_['entry_payment_object_payment'] = 'Платёж (аванс, задаток, предоплата, кредит)';
$_['entry_payment_object_agent_commission'] = 'Агентское вознаграждение';
$_['entry_payment_object_composite'] = 'Составной предмет расчёта';
$_['entry_payment_object_another'] = 'Иной предмет расчёта';
$_['entry_payment_object_property_right'] = 'Имущественное право';
$_['entry_payment_object_sales_tax'] = 'Торговый сбор';
$_['entry_payment_object_resort_fee'] = 'Курортный сбор';
$_['entry_payment_object_award'] = 'Взнос в счёт оплаты, пени, штраф, вознаграждение, бонус';
$_['entry_payment_object_non_operating'] = 'Внереализационный доход';
$_['entry_payment_object_insurance_premium'] = 'Страховой сбор';
// Help
$_['help_status'] = 'Включите или выключите модуль оплаты';
$_['help_pending_order_status'] = 'Укажите статус заказа, который выставляется при его создании';
@@ -51,12 +86,28 @@ $_['help_success_order_status'] = 'Укажите статус заказа, к
$_['help_error_order_status'] = 'Укажите статус заказа, который выставляется при ошибке оплаты';
$_['help_geo_zone'] = 'Географическая зона, в которой будет отображаться способ оплаты';
$_['help_sort_order'] = 'Определяет порядок отображения платежного метода в списке методов оплаты при оформлении заказа. Чем меньше число, тем выше позиция в списке';
$_['help_test_mode'] = 'Имитация оплаты без реального списания средств. Режим должен совпадать с настройкой счёта в MONETA.RU';
$_['help_fiscalization'] = 'Включайте, только если к счёту подключён кассовый сервис kassa.payanyway.ru';
$_['help_receipt_email'] = 'Обязателен при фискализации. Подставляется в чек, только если у покупателя нет ни e-mail, ни телефона: без контакта касса чек не примет, поэтому он уйдёт на этот адрес (ФИО покупателя в чеке остаётся)';
$_['help_canceled_order_status'] = 'Заказ в этих статусах не оплачивается: на проверочный запрос и уведомление об оплате модуль отвечает отказом';
$_['help_callback_url'] = 'Укажите этот адрес в настройках расширенного счёта как Check URL и Pay URL';
// Button
$_['button_integrity_code_toggle'] = 'Показать или скрыть';
$_['button_copy'] = 'Скопировать';
$_['text_copied'] = 'Скопировано';
// Error
$_['error_permission'] = 'Внимание: У вас нет прав на изменение платежного метода PayAnyWay!';
$_['error_validation'] = 'Внимание: Чтобы изменить платежный метод PayAnyWay, вам необходимо заполнить все обязательные поля!';
$_['error_account'] = 'Номер счёта обязателен';
$_['error_account'] = 'Номер счёта обязателен и состоит только из цифр';
$_['error_integrity_code'] = 'Код проверки целостности данных обязателен';
$_['error_pending_order_status'] = 'Выберите статус Обрабатываемого заказа';
$_['error_success_order_status'] = 'Выберите статус Успешного заказа';
$_['error_error_order_status'] = 'Выберите статус Неуспешного заказа';
$_['error_canceled_order_status'] = 'Статусы Обрабатываемого, Успешного и Неуспешного заказа не могут быть среди Отменённых';
$_['error_receipt_email'] = 'Укажите корректный e-mail не длиннее 64 символов';
// Warning
$_['warning_startup_route'] = 'Startup PayAnyWay для приёма уведомлений методом POST не зарегистрирован или выключен: Check URL и Pay URL методом POST не дойдут до модуля. Сохраните настройки модуля (startup зарегистрируется) или включите <code>payanyway_post_route</code> в <a href="%s">Расширения → Startup</a>. Он же снимает оплаченный заказ из сессии покупателя, чтобы следующая покупка в том же браузере создала новый заказ.';
$_['warning_receipt_email'] = 'Фискализация чеков включена, а «Email для чеков» не задан: покупателю без e-mail и телефона чек не будет пробит. Укажите адрес в настройках ниже или выключите фискализацию, если кассовый сервис не подключён.';
+162 -9
View File
@@ -22,6 +22,12 @@
</div>
</div>
<div class="container-fluid">
<div id="payanyway-warning-startup" class="alert alert-warning{% if not warning_startup %} d-none{% endif %}">
<i class="fa-solid fa-triangle-exclamation"></i> <span>{{ warning_startup }}</span>
</div>
<div id="payanyway-warning-receipt-email" class="alert alert-warning{% if not warning_receipt_email %} d-none{% endif %}">
<i class="fa-solid fa-triangle-exclamation"></i> <span>{{ warning_receipt_email }}</span>
</div>
<div class="card">
<div class="card-header">
<i class="fa-solid fa-pencil"></i>
@@ -68,8 +74,13 @@
<div class="row mb-3 required">
<label class="col-sm-2 col-form-label" for="input-integrity-code">{{ entry_integrity_code_label }}</label>
<div class="col-sm-10">
<input type="text" name="payment_payanyway_integrity_code" value="{{ payment_payanyway_integrity_code }}"
placeholder="{{ entry_integrity_code_description }}" id="input-integrity-code" class="form-control"/>
<div class="input-group">
<input type="password" name="payment_payanyway_integrity_code" value=""
placeholder="{{ integrity_code_placeholder }}" id="input-integrity-code" class="form-control"
autocomplete="off"/>
<button type="button" class="btn btn-light" id="button-integrity-code-toggle"
data-bs-toggle="tooltip" title="{{ button_integrity_code_toggle }}"><i class="fa-solid fa-eye"></i></button>
</div>
</div>
</div>
@@ -84,15 +95,85 @@
</div>
</div>
{# НДС #}
{# Тестовый режим #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-vat-rate">{{ entry_vat_rate_label }}</label>
<label class="col-sm-2 col-form-label" for="input-test-mode">
<span>{{ entry_test_mode_label }}</span>
<i class="fa-regular fa-circle-question text-info" data-bs-toggle="tooltip" title="{{ help_test_mode }}"></i>
</label>
<div class="col-sm-10">
<select name="payment_payanyway_vat_rate" id="input-vat-rate" class="form-select">
{% for vat_id, label in payment_payanyway_vat_rates %}
<option value="{{ vat_id }}" {% if vat_id == payment_payanyway_vat_rate %}selected="selected"{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<div class="form-check form-switch form-switch-lg">
<input type="hidden" name="payment_payanyway_test_mode" value="0"/>
<input type="checkbox" name="payment_payanyway_test_mode" value="1" id="input-test-mode"
class="form-check-input" {% if payment_payanyway_test_mode == "1" %} checked{% endif %}>
</div>
</div>
</div>
{# Фискализация чеков #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-fiscalization">
<span>{{ entry_fiscalization_label }}</span>
<i class="fa-regular fa-circle-question text-info" data-bs-toggle="tooltip" title="{{ help_fiscalization }}"></i>
</label>
<div class="col-sm-10">
<div class="form-check form-switch form-switch-lg">
<input type="hidden" name="payment_payanyway_fiscalization" value="0"/>
<input type="checkbox" name="payment_payanyway_fiscalization" value="1" id="input-fiscalization"
class="form-check-input" {% if payment_payanyway_fiscalization == "1" %} checked{% endif %}>
</div>
</div>
</div>
{# Поля чека — только при фискализации. Скрываются, а не блокируются: заблокированное
поле браузер не отправляет, и значение потерялось бы. #}
<div id="fiscalization-fields">
{# Email для чеков #}
<div class="row mb-3 required">
<label class="col-sm-2 col-form-label" for="input-receipt-email">
<span>{{ entry_receipt_email_label }}</span>
<i class="fa-regular fa-circle-question text-info" data-bs-toggle="tooltip" title="{{ help_receipt_email }}"></i>
</label>
<div class="col-sm-10">
<input type="email" name="payment_payanyway_receipt_email" value="{{ payment_payanyway_receipt_email }}"
placeholder="shop@example.ru" id="input-receipt-email" class="form-control" maxlength="64"/>
</div>
</div>
{# НДС #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-vat-rate">{{ entry_vat_rate_label }}</label>
<div class="col-sm-10">
<select name="payment_payanyway_vat_rate" id="input-vat-rate" class="form-select">
{% for vat_id, label in payment_payanyway_vat_rates %}
<option value="{{ vat_id }}" {% if vat_id == payment_payanyway_vat_rate %}selected="selected"{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
{# Признак способа расчёта #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-payment-method">{{ entry_payment_method_label }}</label>
<div class="col-sm-10">
<select name="payment_payanyway_payment_method" id="input-payment-method" class="form-select">
{% for value, label in payment_payanyway_payment_methods %}
<option value="{{ value }}" {% if value == payment_payanyway_payment_method %}selected="selected"{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
{# Признак предмета расчёта #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-payment-object">{{ entry_payment_object_label }}</label>
<div class="col-sm-10">
<select name="payment_payanyway_payment_object" id="input-payment-object" class="form-select">
{% for value, label in payment_payanyway_payment_objects %}
<option value="{{ value }}" {% if value == payment_payanyway_payment_object %}selected="selected"{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
@@ -187,6 +268,29 @@
</select>
</div>
</div>
{# canceled #}
<div class="row mb-3">
<label class="col-sm-3 col-form-label">
<span>{{ entry_canceled_order_status_label }}</span>
<i class="fa-regular fa-circle-question text-info"
data-bs-toggle="tooltip"
title="{{ help_canceled_order_status }}"
></i>
</label>
<div class="col-sm-9">
<div class="form-control p-0" style="height: 150px; overflow: auto;">
{% for order_status in order_statuses %}
<div class="form-check m-2">
<input type="checkbox" name="payment_payanyway_canceled_order_status_ids[]"
value="{{ order_status.order_status_id }}" id="input-canceled-{{ order_status.order_status_id }}"
class="form-check-input"{% if order_status.order_status_id in payment_payanyway_canceled_order_status_ids %} checked{% endif %}/>
<label for="input-canceled-{{ order_status.order_status_id }}" class="form-check-label">{{ order_status.name }}</label>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
@@ -216,6 +320,20 @@
</div>
</div>
{# URL для уведомлений #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-callback-url">
<span>{{ entry_callback_url_label }}</span>
<i class="fa-regular fa-circle-question text-info" data-bs-toggle="tooltip" title="{{ help_callback_url }}"></i>
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" value="{{ callback_url }}" id="input-callback-url" class="form-control" readonly/>
<button type="button" class="btn btn-light" id="button-callback-url-copy">{{ button_copy }}</button>
</div>
</div>
</div>
{# sort-order #}
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="input-sort-order">
@@ -238,4 +356,39 @@
</div>
</div>
</div>
<script>
$(function () {
function toggleFiscalizationFields() {
$('#fiscalization-fields').toggle($('#input-fiscalization').is(':checked'));
}
$('#input-fiscalization').on('change', toggleFiscalizationFields);
toggleFiscalizationFields();
// Форму отправляет common.js OpenCart; после сохранения обновляем предупреждения без перезагрузки.
$(document).on('ajaxSuccess', function (event, xhr, settings) {
var json = xhr.responseJSON;
if (!json || !json['payanyway_warnings'] || (settings.url.indexOf('payanyway.save') === -1)) {
return;
}
$.each({'startup': '#payanyway-warning-startup', 'receipt_email': '#payanyway-warning-receipt-email'}, function (key, selector) {
var text = json['payanyway_warnings'][key] || '';
$(selector).toggleClass('d-none', text === '').find('span').html(text);
});
});
$('#button-integrity-code-toggle').on('click', function () {
var $input = $('#input-integrity-code');
$input.attr('type', $input.attr('type') === 'password' ? 'text' : 'password');
$(this).find('i').toggleClass('fa-eye fa-eye-slash');
});
$('#button-callback-url-copy').on('click', function () {
var $button = $(this);
$('#input-callback-url').trigger('select');
document.execCommand('copy');
$button.text('{{ text_copied }}');
setTimeout(function () { $button.text('{{ button_copy }}'); }, 2000);
});
});
</script>
{{ footer }}