531 lines
19 KiB
PHP
Executable File
531 lines
19 KiB
PHP
Executable File
<?php
|
|
|
|
/**
|
|
* @property ModelSettingSetting $model_setting_setting
|
|
* @property ModelLocalisationCurrency $model_localisation_currency
|
|
* @property ModelLocalisationOrderStatus $model_localisation_order_status
|
|
* @property ModelLocalisationGeoZone $model_localisation_geo_zone
|
|
*/
|
|
class ControllerExtensionPaymentPayanyway extends Controller {
|
|
const CMS_NAME = 'OpenCart';
|
|
const MODULE_VERSION = '1.0.6';
|
|
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',
|
|
'vat0' => '1104',
|
|
'vat5' => '1108',
|
|
'vat7' => '1109',
|
|
'vat10' => '1103',
|
|
'vat22' => '1113',
|
|
'vat105' => '1110',
|
|
'vat107' => '1111',
|
|
'vat110' => '1107',
|
|
'vat122' => '1114'
|
|
);
|
|
|
|
private $paymentMethods = array(
|
|
'full_prepayment',
|
|
'prepayment',
|
|
'advance',
|
|
'full_payment',
|
|
'partial_payment',
|
|
'credit',
|
|
'credit_payment'
|
|
);
|
|
|
|
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();
|
|
|
|
/**
|
|
* @return void
|
|
* @throws \Exception
|
|
*/
|
|
public function index() {
|
|
$this->load->language('extension/payment/payanyway');
|
|
$this->document->setTitle($this->language->get('heading_title'));
|
|
|
|
if (($this->request->server['REQUEST_METHOD'] === 'POST') && $this->validate($this->request)) {
|
|
$this->load->model('setting/setting');
|
|
$this->model_setting_setting->editSetting('payment_payanyway', $this->request->post);
|
|
|
|
$this->session->data['success'] = $this->language->get('text_success');
|
|
|
|
$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true));
|
|
}
|
|
|
|
$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);
|
|
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true);
|
|
|
|
$data['payment_payanyway_status'] = $this->retrieveValue('payment_payanyway_status');
|
|
$data['payment_payanyway_account'] = $this->retrieveValue('payment_payanyway_account');
|
|
$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->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();
|
|
$data['payment_payanyway_currency'] = $this->retrieveValue('payment_payanyway_currency');
|
|
$data['order_statuses'] = $this->getAvailableOrderStatuses();
|
|
$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['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');
|
|
$data['footer'] = $this->load->controller('common/footer');
|
|
|
|
$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
|
|
*/
|
|
public function install() {
|
|
$defaultSettings = array(
|
|
'payment_payanyway_status' => 0,
|
|
'payment_payanyway_account' => '',
|
|
'payment_payanyway_integrity_code' => '',
|
|
'payment_payanyway_server' => 'prod',
|
|
'payment_payanyway_test_mode' => 0,
|
|
'payment_payanyway_fiscalization' => 0,
|
|
'payment_payanyway_vat_rate' => $this->availableVatRates['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' => $this->defaultCanceledOrderStatusIds,
|
|
'payment_payanyway_sort_order' => 0,
|
|
'payment_payanyway_cms_name' => $this->getCmsName(),
|
|
);
|
|
|
|
$orderStatuses = $this->getAvailableOrderStatuses();
|
|
|
|
$pendingStatus = array_filter($orderStatuses, function ($status) {
|
|
return (int)$status['order_status_id'] === 1;
|
|
});
|
|
if (!empty($pendingStatus)) {
|
|
$pendingKey = key($pendingStatus);
|
|
$defaultSettings['payment_payanyway_pending_order_status_id'] = $pendingStatus[$pendingKey]['order_status_id'];
|
|
}
|
|
|
|
$successStatus = array_filter($orderStatuses, function ($status) {
|
|
return (int)$status['order_status_id'] === 5;
|
|
});
|
|
if (!empty($successStatus)) {
|
|
$successKey = key($successStatus);
|
|
$defaultSettings['payment_payanyway_success_order_status_id'] = $successStatus[$successKey]['order_status_id'];
|
|
}
|
|
|
|
$errorStatus = array_filter($orderStatuses, function ($status) {
|
|
return (int)$status['order_status_id'] === 10;
|
|
});
|
|
if (!empty($errorStatus)) {
|
|
$errorKey = key($errorStatus);
|
|
$defaultSettings['payment_payanyway_error_order_status_id'] = $errorStatus[$errorKey]['order_status_id'];
|
|
}
|
|
|
|
$this->load->model('setting/setting');
|
|
$this->model_setting_setting->editSetting('payment_payanyway', $defaultSettings);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @throws \Exception
|
|
*/
|
|
public function uninstall() {
|
|
$this->load->model('setting/setting');
|
|
$this->model_setting_setting->deleteSetting('payment_payanyway');
|
|
}
|
|
|
|
/**
|
|
* @param \Request $request
|
|
* @return bool
|
|
*/
|
|
private function validate($request) {
|
|
if (!$this->user->hasPermission('modify', 'extension/payment/payanyway')) {
|
|
$this->error['warning'] = $this->language->get('error_permission');
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
$integrityCode = $this->decodePostedString($request, 'payment_payanyway_integrity_code');
|
|
if (('' === $integrityCode) && $this->hasIntegrityCode()) {
|
|
$integrityCode = (string)$this->config->get('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_filter(array_map('intval', $canceled), function ($statusId) {
|
|
return $statusId > 0;
|
|
})));
|
|
|
|
if (empty($request->post['payment_payanyway_pending_order_status_id'])) {
|
|
$this->error['pending_order_status'] = $this->language->get('error_order_status');
|
|
}
|
|
if (empty($request->post['payment_payanyway_success_order_status_id'])) {
|
|
$this->error['success_order_status'] = $this->language->get('error_order_status');
|
|
}
|
|
if (empty($request->post['payment_payanyway_error_order_status_id'])) {
|
|
$this->error['error_order_status'] = $this->language->get('error_order_status');
|
|
}
|
|
|
|
// Статус ожидания или ошибки среди отменённых: Check и Pay URL ответили бы на настоящую оплату
|
|
// 500, и MONETA.RU прекратила бы уведомления — деньги списаны, заказ не оплачен.
|
|
$moduleStatuses = array();
|
|
foreach (array('pending', 'success', 'error') as $status) {
|
|
$key = 'payment_payanyway_' . $status . '_order_status_id';
|
|
if (!empty($request->post[$key])) {
|
|
$moduleStatuses[] = (int)$request->post[$key];
|
|
}
|
|
}
|
|
if (array_intersect($request->post['payment_payanyway_canceled_order_status_ids'], $moduleStatuses)) {
|
|
$this->error['canceled_order_status'] = $this->language->get('error_canceled_order_status');
|
|
}
|
|
|
|
$sortOrder = isset($request->post['payment_payanyway_sort_order'])
|
|
? (int)$request->post['payment_payanyway_sort_order']
|
|
: self::MODULE_DEFAULT_SORT_ORDER;
|
|
$request->post['payment_payanyway_sort_order'] = $sortOrder;
|
|
$request->post['payment_payanyway_cms_name'] = $this->getCmsName();
|
|
|
|
return empty($this->error);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return void
|
|
*/
|
|
private function applyValidationErrors(array &$data) {
|
|
$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'] : '';
|
|
$data['error_canceled_order_status'] = isset($this->error['canceled_order_status']) ? $this->error['canceled_order_status'] : '';
|
|
}
|
|
|
|
/** @return array<int, array{text: string, href: string}> */
|
|
private function getBreadcrumbs() {
|
|
return array(
|
|
array(
|
|
'text' => $this->language->get('text_home'),
|
|
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true),
|
|
),
|
|
array(
|
|
'text' => $this->language->get('text_extension'),
|
|
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true),
|
|
),
|
|
array(
|
|
'text' => $this->language->get('heading_title'),
|
|
'href' => $this->url->link('extension/payment/payanyway', 'user_token=' . $this->session->data['user_token'], true),
|
|
),
|
|
);
|
|
}
|
|
|
|
/** @return bool код проверки целостности уже сохранён */
|
|
private function hasIntegrityCode() {
|
|
return '' !== (string)$this->config->get('payment_payanyway_integrity_code');
|
|
}
|
|
|
|
/**
|
|
* @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 $result;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function getModuleVatRates() {
|
|
$result = array();
|
|
foreach ($this->availableVatRates as $vat => $value) {
|
|
$label = 'entry_' . $vat . '_label';
|
|
$result[$value] = $this->language->get($label);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
* @throws \Exception
|
|
*/
|
|
private function getModuleCurrencies() {
|
|
$this->load->model('localisation/currency');
|
|
$shopCurrencies = $this->model_localisation_currency->getCurrencies();
|
|
|
|
$result = array();
|
|
foreach ($this->supportedCurrencies as $currencyCode) {
|
|
$result[$currencyCode] = isset($shopCurrencies[$currencyCode])
|
|
? $shopCurrencies[$currencyCode]
|
|
: $this->getDefaultCurrencyDefinition($currencyCode);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param string $currencyCode
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function getDefaultCurrencyDefinition($currencyCode) {
|
|
$definitions = array(
|
|
'RUB' => array(
|
|
'currency_id' => 0,
|
|
'title' => 'Рубль',
|
|
'code' => 'RUB',
|
|
'symbol_left' => '',
|
|
'symbol_right' => '₽',
|
|
'decimal_place' => '2',
|
|
'value' => 1.00000000,
|
|
'status' => '1',
|
|
'date_modified' => date('Y-m-d H:i:s'),
|
|
),
|
|
);
|
|
|
|
return isset($definitions[$currencyCode]) ? $definitions[$currencyCode] : array();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
* @throws \Exception
|
|
*/
|
|
private function getAvailableOrderStatuses() {
|
|
$this->load->model('localisation/order_status');
|
|
return $this->model_localisation_order_status->getOrderStatuses();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
* @throws \Exception
|
|
*/
|
|
private function getAvailableGeoZones() {
|
|
$this->load->model('localisation/geo_zone');
|
|
return $this->model_localisation_geo_zone->getGeoZones();
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
private function getCmsName() {
|
|
$this->load->language('common/footer');
|
|
$textFooter = $this->language->get('text_footer');
|
|
$textFooter = is_string($textFooter) ? $textFooter : '';
|
|
|
|
return $this->detectCmsByPatterns($textFooter);
|
|
}
|
|
|
|
private function detectCmsByPatterns($textFooter) {
|
|
$patterns = [
|
|
'ocStore' => '/ocStore/i',
|
|
'Russian OpenCart' => '/opencart-russia/i',
|
|
self::CMS_NAME => '/opencart\.com/i'
|
|
];
|
|
|
|
if (!empty($textFooter)) {
|
|
foreach ($patterns as $cms => $pattern) {
|
|
if (preg_match($pattern, $textFooter)) {
|
|
return $cms;
|
|
}
|
|
}
|
|
}
|
|
|
|
return self::CMS_NAME;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return mixed
|
|
*/
|
|
private function retrieveValue($name) {
|
|
if (!is_string($name)) {
|
|
throw new \InvalidArgumentException("The name must be a string");
|
|
}
|
|
|
|
if (isset($this->request->post[$name])) {
|
|
return $this->request->post[$name];
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|