'1105', 'vat0' => '1104', 'vat5' => '1108', 'vat7' => '1109', 'vat10' => '1103', 'vat22' => '1113', 'vat105' => '1110', 'vat107' => '1111', 'vat110' => '1107', '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(); /** * @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['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(); $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' => 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_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'); } // 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'); } 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'); } $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 $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'] : ''; } /** @return array */ 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), ), ); } /** * @param string $key * @return string */ private function getIntegrityCode($key) { 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 */ private function getOptions(array $values, $labelPrefix) { $result = array(); foreach ($values as $value) { $result[$value] = $this->language->get($labelPrefix . $value); } return $result; } /** @return array */ 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 * @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 */ 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 * @throws \Exception */ private function getAvailableOrderStatuses() { $this->load->model('localisation/order_status'); return $this->model_localisation_order_status->getOrderStatuses(); } /** * @return array * @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; } }