<?php

/**
 * @package JoomShopping for Joomla!
 * @subpackage payment
 * @author YooMoney
 * @copyright Copyright (C) 2025 YooMoney. All rights reserved.
 */

use YooKassa\Model\Confirmation\ConfirmationRedirect;
use YooKassa\Model\Notification\AbstractNotification;
use YooKassa\Model\Receipt\PaymentMode;
use YooKassa\Model\Receipt\PaymentSubject;
use YooMoney\Helpers\DatabaseHelper;
use YooMoney\Helpers\OauthHelpers\HttpClient;
use YooMoney\Helpers\OauthHelpers\OauthDataStorageHelper;
use YooMoney\Helpers\OauthHelpers\OauthWebhooksHelper;
use YooMoney\Helpers\OauthHelpers\YookassaShopInfoHelper;
use YooMoney\Helpers\OrderHelper;
use YooMoney\Helpers\StorageHelper;
use YooMoney\Helpers\YooKassaTaxRateEnumHelper;
use YooMoney\Model\OauthModel;
use YooMoney\Model\KassaPaymentMethod;
use YooMoney\Helpers\YookassaClientFactory;
use YooMoney\Helpers\ReceiptHelper;
use YooMoney\Helpers\LoggerHelper;
use YooMoney\Helpers\TransactionHelper;
use YooMoney\Helpers\JVersionDependenciesHelper;
use YooMoney\Helpers\YookassaNotificationFactory;
use YooMoney\Updater\BitbucketConnector;

defined('_JEXEC') or die('Restricted access');

define('JSH_DIR', realpath(dirname(__FILE__).'/../..'));
define('DIR_DOWNLOAD', JSH_DIR.'/log');

require_once dirname(__FILE__).'/lib/autoload.php';

if (!defined('_JSHOP_YOO_VERSION')) {
    define('_JSHOP_YOO_VERSION', '2.10.5');
}

class pm_yoomoney extends PaymentRoot
{
    private $debugLog = true;
    private $formParams;

    private $element = 'pm_yoomoney';
    private $repository = 'cms/repos/cms-joomshopping';
    private $downloadDirectory = 'pm_yoomoney';
    private $backupDirectory = 'pm_yoomoney/backups';
    private $versionDirectory = 'pm_yoomoney/download';

    /**
     * @var LoggerHelper
     */
    private $logger;

    /**
     * @var TransactionHelper
     */
    private $transactionHelper;

    /**
     * @var JVersionDependenciesHelper
     */
    private $versionHelper;

    /**
     * @var YookassaNotificationFactory
     */
    private $yooNotificationHelper;

    /**
     * @var YooMoney\Model\OrderModel
     */
    private $orderModel;

    /**
     * @var KassaPaymentMethod
     */
    private $kassa;

    /**
     * @var ReceiptHelper
     */
    private $receiptHelper;

    /**
     * @var OrderHelper
     */
    private $orderHelper;

    /**
     * @var StorageHelper
     */
    private $storageHelper;

    public function __construct()
    {
        $this->versionHelper = new JVersionDependenciesHelper();
        $this->logger = new LoggerHelper((new DatabaseHelper())->getPmConfig()['shop_id']);
        $this->transactionHelper = new TransactionHelper($this->logger);
        $this->yooNotificationHelper = new YookassaNotificationFactory($this->logger);
        $this->receiptHelper = new ReceiptHelper($this->logger);
        $this->orderHelper = new OrderHelper();
        $this->storageHelper = new StorageHelper();
    }

    /**
     * get save payment params
     * @return bool
     */
    function getSavePaymentParams()
    {
        return true;
    }

    function showPaymentForm($params, $pmConfigs)
    {
        $this->loadLanguageFile();
    }

    /**
     * Проверяет параметры указанные пользователем на странице выбора способа оплаты
     *
     * @param array $params Массив параметров, указанных в params[pm_yoomoney] на странице выбора способа оплаты
     * @param array $pmConfigs Настройки модуля оплаты
     *
     * @return bool True если все параметры вылидны, false если нет
     */
    public function checkPaymentInfo($params, $pmConfigs)
    {
        return true;
    }

    /**
     * function call in admin
     *
     * @param string|array $params
     * @return void
     *
     * @throws Exception
     */
    public function showAdminFormParams($params)
    {
        $this->loadLanguageFile();
        $this->installExtension();

        if (isset($_GET['subaction'])) {
            $this->formParams = $params;
            $this->checkSubaction();
        }

        $array_params = array(
            'kassa_send_check',
            'testmode',
            'kassamode',
            'shoppassword',
            'shopid',
            'transaction_end_status',
            'yookassa_description_template',
            'yookassa_send_check',
            'yookassa_default_tax',
            'yookassa_default_tax_system',
            'yookassa_default_payment_mode',
            'yookassa_default_payment_subject',
            'yookassa_default_delivery_payment_mode',
            'yookassa_default_delivery_payment_subject',
            'access_token',
            'state',
            'expires_in',
            'debug_log',
        );

        $paymentModeEnum = array(
            PaymentMode::FULL_PREPAYMENT    => _JSHOP_YOO_KASSA_PAYMENT_MODE_FULL_PREPAYMENT,
            PaymentMode::PARTIAL_PREPAYMENT => _JSHOP_YOO_KASSA_PAYMENT_MODE_PARTIAL_PREPAYMENT,
            PaymentMode::ADVANCE            => _JSHOP_YOO_KASSA_PAYMENT_MODE_ADVANCE,
            PaymentMode::FULL_PAYMENT       => _JSHOP_YOO_KASSA_PAYMENT_MODE_FULL_PAYMENT,
            PaymentMode::PARTIAL_PAYMENT    => _JSHOP_YOO_KASSA_PAYMENT_MODE_PARTIAL_PAYMENT,
            PaymentMode::CREDIT             => _JSHOP_YOO_KASSA_PAYMENT_MODE_CREDIT,
            PaymentMode::CREDIT_PAYMENT     => _JSHOP_YOO_KASSA_PAYMENT_MODE_CREDIT_PAYMENT,
        );

        $paymentSubjectEnum = array(
            PaymentSubject::COMMODITY             => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_COMMODITY,
            PaymentSubject::EXCISE                => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_EXCISE,
            PaymentSubject::JOB                   => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_JOB,
            PaymentSubject::SERVICE               => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_SERVICE,
            PaymentSubject::GAMBLING_BET          => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_GAMBLING_BET,
            PaymentSubject::GAMBLING_PRIZE        => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_GAMBLING_PRIZE,
            PaymentSubject::LOTTERY               => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_LOTTERY,
            PaymentSubject::LOTTERY_PRIZE         => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_LOTTERY_PRIZE,
            PaymentSubject::INTELLECTUAL_ACTIVITY => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_INTELLECTUAL_ACTIVITY,
            PaymentSubject::PAYMENT               => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_PAYMENT,
            PaymentSubject::AGENT_COMMISSION      => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_AGENT_COMMISSION,
            PaymentSubject::COMPOSITE             => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_COMPOSITE,
            PaymentSubject::ANOTHER               => _JSHOP_YOO_KASSA_PAYMENT_SUBJECT_ANOTHER,
        );

        if (!is_array($params)) {
            $params = array();
        }

        $params['paymentModeEnum']    = $paymentModeEnum;
        $params['paymentSubjectEnum'] = $paymentSubjectEnum;

        $taxes = JSFactory::getAllTaxes();
        $taxesRate = (new YooKassaTaxRateEnumHelper())->getTaxRate();
        $taxRatesToShow = [YooKassaTaxRateEnumHelper::VAT_20, YooKassaTaxRateEnumHelper::VAT_120];
        $showTaxInformer = (isset($params['yookassa_default_tax']) &&
            in_array((int)$params['yookassa_default_tax'], $taxRatesToShow));

        foreach ($taxes as $k => $tax) {
            $paramName = 'yookassa_tax_'.$k;

            if (!isset($params[$paramName])) {
                $params[$paramName] = '';
            }

            if (!$showTaxInformer && in_array((int)$params[$paramName], $taxRatesToShow)) {
                $showTaxInformer = true;
            }
        }

        if (!isset($params['use_ssl'])) {
            $params['use_ssl'] = 0;
        }

        $orders = JModelLegacy::getInstance('orders', 'JshoppingModel'); //admin model
        $filename = $this->versionHelper->getFilesVersionPostfix();

        $this->versionHelper->registerEventListener('onBeforeEditPayments', array($this, 'onBeforeEditPayments'));

        $zip_enabled  = function_exists('zip_open');
        $curl_enabled = function_exists('curl_init');
        if ($zip_enabled && $curl_enabled) {
            $force       = false;
            $versionInfo = $this->checkModuleVersion($force);
            if (version_compare($versionInfo['version'], _JSHOP_YOO_VERSION) > 0) {
                $new_version_available = true;
                $changelog             = $this->getChangeLog(_JSHOP_YOO_VERSION, $versionInfo['version']);
                $newVersion            = $versionInfo['version'];
            } else {
                $new_version_available = false;
                $changelog             = '';
                $newVersion            = _JSHOP_YOO_VERSION;
            }
            $newVersionInfo = $versionInfo;

            $backups = $this->getBackupList();
        }

        $kassa = $this->getKassaPaymentMethod($params);
        $kassa->setShopAndWebhooksInfo();
        $yookassa_notify_url = $kassa->getNotificationUrl();

        include(dirname(__FILE__)."/adminparamsform".$filename.".php");
    }

    /**
     * Отрисовка логов
     * @return void
     */
    public function viewModuleLogs()
    {
        $fileName = $this->getLogFileName();
        $fd       = @fopen($fileName, 'r');
        $logs     = array();
        if ($fd) {
            flock($fd, LOCK_SH);
            $size = filesize($fileName);
            if ($size > 0) {
                $logs = array_map('trim', explode("\n", fread($fd, $size)));
            }
            flock($fd, LOCK_UN);
            fclose($fd);
        }
        $this->sendResponseJson($logs);
    }

    /**
     * Очистка файла с логами
     * @return void
     */
    public function clearModuleLogs()
    {
        $fileName = $this->getLogFileName();
        $fd       = @fopen($fileName, 'w');
        $success  = false;
        if ($fd) {
            flock($fd, LOCK_SH);
            flock($fd, LOCK_UN);
            fclose($fd);
            $success = true;
        }
        $this->sendResponseJson(array('success' => $success));
    }

    /**
     * Возвращает путь к файлу лога
     *
     * @return string
     */
    private function getLogFileName()
    {
        return $this->logger->getLogFileName();
    }

    private function restoreBackup()
    {
        if (!empty($_POST['file_name'])) {
            $fileName = DIR_DOWNLOAD.'/'.$this->backupDirectory.'/'.$_POST['file_name'];
            if (!file_exists($fileName)) {
                $this->log('error', 'File "'.$fileName.'" not exists');

                return array(
                    'message' => 'Файл резервной копии '.$fileName.' не найден',
                    'success' => false,
                );
            }
            try {
                $sourceDirectory = dirname(dirname(realpath(JSH_DIR)));
                $archive         = new \YooMoney\Updater\Archive\RestoreZip($fileName);
                $archive->restore('file_map.map', $sourceDirectory);
            } catch (Exception $e) {
                $this->log('error', $e->getMessage());
                if ($e->getPrevious() !== null) {
                    $this->log('error', $e->getPrevious()->getMessage());
                }

                return array(
                    'message' => _JSHOP_YOO_UPDATER_ERROR_RESTORE.$e->getMessage(),
                    'success' => false,
                );
            }

            return array(
                'message' => _JSHOP_YOO_UPDATER_SUCCESS_MESSAGE.$_POST['file_name'],
                'success' => true,
            );
        }

        return array(
            'message' => _JSHOP_YOO_UPDATER_ERROR_REMOVE,
            'success' => false,
        );
    }

    public function removeBackup()
    {
        if (!empty($_POST['file_name'])) {
            $fileName = DIR_DOWNLOAD.'/'.$this->backupDirectory.'/'.str_replace(array('/', '\\'), array('', ''),
                    $_POST['file_name']);
            if (!file_exists($fileName)) {
                $this->log('error', 'File "'.$fileName.'" not exists');

                return array(
                    'message' => sprintf(_JSHOP_YOO_ERROR_BACKUP_NOT_FOUND, $fileName),
                    'success' => false,
                );
            }

            if (!unlink($fileName) || file_exists($fileName)) {
                $this->log('error', 'Failed to unlink file "'.$fileName.'"');

                return array(
                    'message' => _JSHOP_YOO_ERROR_REMOVE_BACKUP.$fileName,
                    'success' => false,
                );
            }

            return array(
                'message' => sprintf(_JSHOP_YOO_SUCCESS_REMOVE_BECKUP, $fileName),
                'success' => true,
            );
        }

        return array(
            'message' => _JSHOP_YOO_UPDATER_ERROR_REMOVE,
            'success' => false,
        );
    }

    /**
     * @return array
     * @throws JsonException
     */
    public function updateVersion()
    {
        $this->logger->sendHeka(array('module.update.init'));
        $this->loadLanguageFile();
        $versionInfo = $this->checkModuleVersion(false);
        $fileName    = $this->downloadLastVersion($versionInfo['tag']);

        if (empty($fileName)) {
            $this->logger->sendHeka(array('module.update.skip'));
            return array(
                'message' => _JSHOP_YOO_ERROR_DOWNLOAD_NEW_VERSION,
                'success' => false,
            );
        }

        if (!$this->createBackup(_JSHOP_YOO_VERSION)) {
            $this->logger->sendHeka(array('module.update.fail'));
            return array(
                'message' => _JSHOP_YOO_ERROR_CREATE_BACKUP,
                'success' => false,
            );
        }

        if (!$this->unpackLastVersion($fileName)) {
            $this->logger->sendHeka(array('module.update.fail'));
            return array(
                'message' => sprintf(_JSHOP_YOO_ERROR_UNPACK_NEW_VERSION, $fileName),
                'success' => false,
            );
        }

        $this->logger->sendHeka(array('module.update.success'));
        return array(
            'message' => sprintf(_JSHOP_YOO_SUCCESS_UPDATE_VERSION, $_POST['version'], $fileName),
            'success' => true,
        );

    }

    public function onBeforeEditPayments($view)
    {
        $view->tmp_html_start = '';
        $view->tmp_html_end   = '';
    }

    /**
     *
     */
    private function loadLanguageFile()
    {
        $lang    = JFactory::getLanguage();
        $langTag = $lang->getTag();
        if (file_exists(JPATH_ROOT.'/components/com_jshopping/payments/pm_yoomoney/lang/'.$langTag.'.php')) {
            require_once(JPATH_ROOT.'/components/com_jshopping/payments/pm_yoomoney/lang/'.$langTag.'.php');
        } else {
            require_once(JPATH_ROOT.'/components/com_jshopping/payments/pm_yoomoney/lang/ru-RU.php');
        }
    }

    /**
     * Метод проверки валидности платежа, вызывается при создании платежа после возврата пользователя на страницу
     * подтверждения заказа, так же вызывается при приходе нотификации от платёжной системы
     *
     * @param array $pmConfigs Массив настроек платёжной системы
     * @param object $order Инстанс заказа
     * @param string $act Значение параметра "act" в ссылке
     *
     * @return array Массив с информацией о транзакции
     */
    function checkTransaction($pmConfigs, $order, $act)
    {
        $kassa = $this->getKassaPaymentMethod($pmConfigs);

        if ($act === 'notify') {
            $this->log('debug', 'Notification callback called');

            try {
                $result = $this->transactionHelper->processNotification($kassa, $pmConfigs, $order);
            } catch (Exception $e) {
                $this->log('debug', $e->getMessage());
                header('HTTP/1.1 500 Internal Server Error');
                die;
            }
            if (!$result) {
                $this->log('debug', 'Notification error: wrong payment status');
                header('HTTP/1.1 401 Payment does not exists');
            }

            header('HTTP/1.1 200');
            exit();
        }

        $this->log('debug', 'Check transaction for order#'.$order->order_id);

        if (!$this->checkPaymentByOrderId($order->order_id, $pmConfigs)) {
            return array(3, 'Transaction not exists', '', 'Transaction not exists');
        }
        $transactionId = $this->getOrderModel()->getPaymentIdByOrderId($order->order_id);
        if (empty($transactionId)) {
            $this->log('debug', 'Payment id for order#'.$order->order_id.' not exists');

            return array(3, 'Transaction not exists', '', 'Transaction not exists');
        }

        return array(
            -1,
            sprintf(_JSHOP_YOO_PAYMENT_CAPTURED_TEXT, $transactionId),
            $transactionId,
            _JSHOP_YOO_PAYMENT_CAPTURED,
        );
    }

    /**
     * @param $pmConfigs
     * @param jshopOrder $order
     */
    function showEndForm($pmConfigs, $order)
    {
        if ($this->processKassaPayment($pmConfigs, $order)) {
            return;
        }
        // если произошла ошибка, редиректим на шаг выбора метода оплаты
        $redirectUrl = JRoute::_(JURI::root().'index.php?option=com_jshopping&controller=checkout&task=step5');
        $app         = JFactory::getApplication();
        $app->redirect($redirectUrl);
    }

    public function processKassaPayment($pmConfigs, $order)
    {
        $this->loadLanguageFile();
        $app         = JFactory::getApplication();
        $uri         = JURI::getInstance();
        $redirectUrl = $uri->toString(array('scheme', 'host', 'port'))
            .$this->versionHelper->getSefLink("index.php?option=com_jshopping&controller=checkout&task=step7&act=return&js_paymentclass=pm_yoomoney&no_lang=1&order_id=".$order->order_id);
        $redirectUrl = htmlspecialchars_decode($redirectUrl);

        /** @var jshopCart $cart */
        $cart = JSFactory::getModel('cart', 'jshop');
        if (method_exists($cart, 'init')) {
            $cart->init('cart', 1);
        } else {
            $cart->load('cart');
        }

        try {
            $payment = $this->getKassaPaymentMethod($pmConfigs)->createPayment($order, $cart, $redirectUrl);
        } catch (\Exception $e) {
            $redirect = JRoute::_(JURI::root().'index.php?option=com_jshopping&controller=checkout&task=step3');
            $app->enqueueMessage(_JSHOP_YOO_ERROR_MESSAGE_CREATE_PAYMENT, 'error');
            $app->redirect($redirect);
        }

        $redirect = $redirectUrl;
        if ($payment !== null) {
            $confirmation = $payment->getConfirmation();
            $this->getOrderModel()->savePayment($order->order_id, $payment);

            if ($confirmation instanceof ConfirmationRedirect) {
                $redirect = $confirmation->getConfirmationUrl();
                $this->logger->sendHeka(array('payment.redirect.init'));
            }
        } else {
            $redirect = JRoute::_(JURI::root().'index.php?option=com_jshopping&controller=checkout&task=step3');
            $app->enqueueMessage(_JSHOP_YOO_ERROR_MESSAGE_CREATE_PAYMENT, 'error');
        }

        $app->redirect($redirect);
    }

    /**
     * Инициализирует параметры для обработки процессором заказа из URL запроса при возврате на сайт
     *
     * @param array $pmConfigs Настройки модуля оплаты
     *
     * @return array Массив параметров, который будет использоваться в процессоре заказа
     */
    public function getUrlParams($pmConfigs)
    {
        $params = array();

        $this->log('debug', 'Get URL parameters for payment');
        if (isset($_GET['order_id'])) {

            $this->log('debug', 'Order id exists in return url: '.$_GET['order_id']);

            $params['order_id'] = (int)$_GET['order_id'];

            if (!$this->checkPaymentByOrderId($params['order_id'], $pmConfigs)) {
                $redirectUrl = JRoute::_(JURI::root().'index.php?option=com_jshopping&controller=checkout&task=step3');
                $app         = JFactory::getApplication();
                $app->redirect($redirectUrl);
            }

            $params['hash']              = "";
            $params['checkHash']         = 0;
            $params['checkReturnParams'] = 1;
            $this->log('debug', 'Return url params is: '.json_encode($params));

            return $params;
        }

        if (isset($_GET['act']) && $_GET['act'] === 'notify') {
            $this->log('debug', 'Notification callback check URL parameters');

            try {
                $notification = $this->yooNotificationHelper->getNotificationObject();
                $orderId = $this->getOrderIdByNotification($notification);
            } catch (Exception $e) {
                $this->log('debug', 'Notification error: '.$e->getMessage());
                header('HTTP/1.1 400 Invalid body');
                die();
            }
            $params['order_id']          = $orderId;
            $params['hash']              = "";
            $params['checkHash']         = 0;
            $params['checkReturnParams'] = 1;
            $this->log('debug', 'Notify url params is: '.json_encode($params));

            return $params;
        }

        $this->log('debug', 'Order id not exists in return url: '.json_encode($_GET));

        return $params;
    }

    /**
     * Возвращает id заказа по значению metadata.order_id в уведомлении, или, если уведомление о статусе
     * refund.succeeded, то вызывает метод поиска refund.succeeded в БД по id платежа
     *
     * @param AbstractNotification $notification
     * @return string
     * @throws Exception
     */
    private function getOrderIdByNotification($notification)
    {
        $object = $notification->getObject();
        if (method_exists($object, 'getMetadata')) {
            $meta = $object->getMetadata();
            $orderId = $meta['order_id'];
        } else {
            $orderId = $this->getOrderModel()->getOrderIdByPaymentId($object->getPaymentId());
        }

        if (empty($orderId)) {
            throw new \Exception('Notification error: order_id was not found');
        }

        return $orderId;
    }

    /**
     * Проверяет, что платеж существует для переданного id заказа и оплачен
     *
     * @param $orderId
     * @param $pmConfigs
     * @return bool
     */
    private function checkPaymentByOrderId($orderId, $pmConfigs)
    {
        $paymentId = $this->getOrderModel()->getPaymentIdByOrderId($orderId);
        if (empty($paymentId)) {
            $this->log('debug', 'Redirect user to payment method page: payment id not exists');

            return false;
        }
        $payment = $this->getKassaPaymentMethod($pmConfigs)->fetchPayment($paymentId);
        if ($payment === null) {
            $this->log('debug', 'Redirect user to payment method page: payment not exists');

            return false;
        }
        if (!$payment->getPaid()) {
            $this->log('debug', 'Redirect user to payment method page: payment not paid');
            $redirectUrl = JRoute::_(JURI::root().'index.php?option=com_jshopping&controller=checkout&task=step3');
            $app         = JFactory::getApplication();
            $app->redirect($redirectUrl);

            return false;
        }

        $this->log('debug', 'Payment '.$payment->getId().' for order#' . $orderId . ' paid');

        return true;
    }

    private function fixOrderTotal($order)
    {
        $total = $order->order_total;
        if ($order->currency_code_iso == 'HUF') {
            $total = round($total);
        } else {
            $total = number_format($total, 2, '.', '');
        }

        return $total;
    }

    /**
     * @param string $tpl
     * @param jshopOrder $order
     *
     * @return string
     */
    private function parseTemplate($tpl, $order)
    {
        $replace = array();
        foreach ($order as $property => $value) {
            $replace['%'.$property.'%'] = $value;
        }

        return strtr($tpl, $replace);
    }

    /**
     * @param jshopOrder $order
     * @param int $endStatus
     *
     * @return int
     */
    private function finishOrder($order, $endStatus)
    {
        $act            = 'finish';
        $payment_method = 'pm_yoomoney';
        $no_lang        = '1';

        if ($this->versionHelper->getJoomlaVersion() === 2) {
            // joomla 2.x order finish
            $jshopConfig = JSFactory::getConfig();

            /** @var jshopCheckout $checkout */
            $checkout = JSFactory::getModel('checkout', 'jshop');

            $order->order_created = 1;
            $order->order_status  = $endStatus;
            $order->store();
            try {
                if ($jshopConfig->send_order_email) {
                    $checkout->sendOrderEmail($order->order_id);
                }
            } catch (\Exception $exception) {
                $this->log('debug', $exception->getMessage());
            }
            if ($jshopConfig->order_stock_removed_only_paid_status) {
                $product_stock_removed = (in_array($endStatus, $jshopConfig->payment_status_enable_download_sale_file));
            } else {
                $product_stock_removed = 1;
            }
            if ($product_stock_removed) {
                $order->changeProductQTYinStock("-");
            }
            $checkout->changeStatusOrder($order->order_id, $endStatus, 0);

            $checkout->deleteSession();
        } else {
            // joomla 3.x order finish
            /** @var jshopCheckoutBuy $checkout */
            $checkout = JSFactory::getModel('checkoutBuy', 'jshop');
            $checkout->saveToLogPaymentData();
            $checkout->setSendEndForm(0);

            $checkout->setAct($act);
            $checkout->setPaymentMethodClass($payment_method);
            $checkout->setNoLang($no_lang);
            $checkout->loadUrlParams();
            $checkout->setOrderId($order->order_id);

            $codebuy = $checkout->buy();
            if ($codebuy == 0) {
                JError::raiseWarning('', $checkout->getError());

                return 0;
            }

            /** @var jshopCheckoutFinish $checkout */
            $checkout = JSFactory::getModel('checkoutFinish', 'jshop');
            $order_id = $checkout->getEndOrderId();
            $text     = $checkout->getFinishStaticText();
            if ($order_id) {
                $checkout->paymentComplete($order_id, $text);
            }
            $checkout->clearAllDataCheckout();
        }
    }

    /**
     * @param $level
     * @param $message
     * @param array $context
     */
    public function log($level, $message, $context = array())
    {
        if (!$this->debugLog) {
            return;
        }

        $this->logger->log($level, $message, $context);
    }

    /**
     * @return LoggerHelper
     */
    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * @return \YooMoney\Model\OrderModel
     */
    public function getOrderModel()
    {
        if ($this->orderModel === null) {
            $this->orderModel = new \YooMoney\Model\OrderModel($this->logger);
        }

        return $this->orderModel;
    }

    /**
     * @param $pmConfigs
     * @return KassaPaymentMethod
     */
    public function getKassaPaymentMethod($pmConfigs)
    {
        $this->loadLanguageFile();
        if ($this->kassa === null) {
            $this->kassa = new KassaPaymentMethod($this, $pmConfigs);
        }

        return $this->kassa;
    }

    public function checkModuleVersion($useCache = true)
    {
        $this->preventDirectoryCreation();

        $file = DIR_DOWNLOAD.'/'.$this->downloadDirectory.'/version_log.txt';

        if ($useCache) {
            if (file_exists($file)) {
                $content = preg_replace('/\s+/', '', file_get_contents($file));
                if (!empty($content)) {
                    $parts = explode(':', $content);
                    if (count($parts) === 2) {
                        if (time() - $parts[1] < 3600 * 8) {
                            return array(
                                'tag'     => $parts[0],
                                'version' => preg_replace('/[^\d\.]+/', '', $parts[0]),
                                'time'    => $parts[1],
                                'date'    => $this->dateDiffToString($parts[1]),
                            );
                        }
                    }
                }
            }
        }

        $connector = new BitbucketConnector();
        $version   = $connector->getLatestRelease($this->repository);
        if (empty($version)) {
            return array();
        }

        $cache = $version.':'.time();
        file_put_contents($file, $cache);

        return array(
            'tag'     => $version,
            'version' => preg_replace('/[^\d\.]+/', '', $version),
            'time'    => time(),
            'date'    => $this->dateDiffToString(time()),
        );
    }

    public function getChangeLog($currentVersion, $newVersion)
    {
        $this->preventDirectoryCreation();

        $connector = new BitbucketConnector();

        $dir          = DIR_DOWNLOAD.'/'.$this->downloadDirectory;
        $newChangeLog = $dir.'/CHANGELOG-'.$newVersion.'.md';
        if (!file_exists($newChangeLog)) {
            $fileName = $connector->downloadLatestChangeLog($this->repository, $dir);
            if (!empty($fileName)) {
                rename($dir.'/'.$fileName, $newChangeLog);
            }
        }

        $oldChangeLog = $dir.'/CHANGELOG-'.$currentVersion.'.md';
        if (!file_exists($oldChangeLog)) {
            $fileName = $connector->downloadLatestChangeLog($this->repository, $dir);
            if (!empty($fileName)) {
                rename($dir.'/'.$fileName, $oldChangeLog);
            }
        }

        $result = '';
        if (file_exists($newChangeLog)) {
            $result = $connector->diffChangeLog($oldChangeLog, $newChangeLog);
        }

        return $result;
    }

    private function dateDiffToString($timestamp)
    {
        return date('d.m.Y H:i', $timestamp);
    }

    public function getBackupList()
    {
        $result = array();

        $this->preventDirectoryCreation();
        $dir = DIR_DOWNLOAD.'/'.$this->backupDirectory;

        $handle = opendir($dir);
        while (($entry = readdir($handle)) !== false) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            $ext = pathinfo($entry, PATHINFO_EXTENSION);
            if ($ext === 'zip') {
                $backup            = array(
                    'name' => pathinfo($entry, PATHINFO_FILENAME).'.zip',
                    'size' => $this->formatSize(filesize($dir.'/'.$entry)),
                );
                $parts             = explode('-', $backup['name'], 3);
                $backup['version'] = $parts[0];
                $backup['time']    = $parts[1];
                $backup['date']    = date('d.m.Y H:i:s', $parts[1]);
                $backup['hash']    = $parts[2];
                $result[]          = $backup;
            }
        }

        return $result;
    }

    private function preventDirectoryCreation()
    {
        if (!file_exists(DIR_DOWNLOAD)) {
            mkdir(DIR_DOWNLOAD);
        }
        $dir = DIR_DOWNLOAD.'/'.$this->downloadDirectory;
        if (!file_exists($dir)) {
            mkdir($dir);
        }
        $dir = DIR_DOWNLOAD.'/'.$this->backupDirectory;
        if (!file_exists($dir)) {
            mkdir($dir);
        }
        $dir = DIR_DOWNLOAD.'/'.$this->versionDirectory;
        if (!file_exists($dir)) {
            mkdir($dir);
        }
    }

    private function formatSize($size)
    {
        static $sizes = array(
            'B',
            'kB',
            'MB',
            'GB',
            'TB',
        );

        $i = 0;
        while ($size > 1024) {
            $size /= 1024.0;
            $i++;
        }

        return number_format($size, 2, '.', ',').'&nbsp;'.$sizes[$i];
    }

    private function downloadLastVersion($tag, $useCache = true)
    {
        $this->preventDirectoryCreation();

        $dir = DIR_DOWNLOAD.'/'.$this->versionDirectory;
        if (!file_exists($dir)) {
            if (!mkdir($dir)) {
                $this->log('error', _JSHOP_YOO_FAILED_CREATE_DIRECTORY.$dir);

                return false;
            }
        } elseif ($useCache) {
            $fileName = $dir.'/'.$tag.'.zip';
            if (file_exists($fileName)) {
                return $fileName;
            }
        }

        $connector = new BitbucketConnector();
        $fileName  = $connector->downloadRelease($this->repository, $tag, $dir);
        if (empty($fileName)) {
            $this->log('error', _JSHOP_YOO_FAILED_DOWNLOAD_UPDATE);

            return false;
        }

        return $fileName;
    }

    public function createBackup($version)
    {
        $this->preventDirectoryCreation();

        $sourceDirectory = dirname(dirname(JSH_DIR));
        $reader          = new \YooMoney\Updater\ProjectStructure\ProjectStructureReader();
        $root            = $reader->readFile(JSH_DIR.'/payments/pm_yoomoney/lib/joomshopping.map',
            $sourceDirectory);

        $rootDir  = $version.'-'.time();
        $fileName = $rootDir.'-'.uniqid('', true).'.zip';
        $dir      = DIR_DOWNLOAD.'/'.$this->backupDirectory;
        try {
            $fileName = $dir.'/'.$fileName;
            $archive  = new \YooMoney\Updater\Archive\BackupZip($fileName, $rootDir);
            $archive->backup($root);
        } catch (Exception $e) {
            $this->log('error', 'Failed to create backup: '.$e->getMessage());

            return false;
        }

        return true;
    }

    public function unpackLastVersion($fileName)
    {
        if (!file_exists($fileName)) {
            $this->log('error', 'File "'.$fileName.'" not exists');

            return false;
        }

        try {
            $sourceDirectory = dirname(dirname(JSH_DIR));
            $archive         = new \YooMoney\Updater\Archive\RestoreZip($fileName, $this);
            $archive->restore('joomshopping.map', $sourceDirectory);
        } catch (Exception $e) {
            $this->log('error', $e->getMessage());
            if ($e->getPrevious() !== null) {
                $this->log('error', $e->getPrevious()->getMessage());
            }

            return false;
        }

        return true;
    }

    private function installExtension()
    {
        $this->logger->sendHeka(array('module.install.init'));
        $addon = $this->versionHelper->getAddonTableObj();

        $manifest = '{"name":"plg_pm_yoomoney","type":"plugin","creationDate":"20.07.2018","author":"YooMoney","authorEmail":"cms@yoomoney.ru","authorUrl":"https://yookassa.ru","version":"'._JSHOP_YOO_VERSION.'"}';
        if ($addon->installJoomlaExtension(array(
            'name'           => 'plg_pm_yoomoney',
            'type'           => 'plugin',
            'element'        => $this->element,
            'folder'         => 'jshoppingadmin',
            'client_id'      => 0,
            'enabled'        => 1,
            'access'         => 1,
            'protected'      => 0,
            'manifest_cache' => $manifest,
        ))) {
            $this->logger->sendHeka(array('module.install.success'));
        } else {
            $this->logger->sendHeka(array('module.install.fail'));
        }
    }

    /**
     * @param $order
     * @param $comments
     * @return mixed
     */
    public function saveOrderHistory($order, $comments)
    {
        return $this->orderHelper->saveOrderHistory($order, $comments);
    }

    /**
     * @param $orderId
     * @param KassaPaymentMethod $kassa
     * @param $status
     */
    public function sendSecondReceipt($orderId, $kassa, $status)
    {
        $this->receiptHelper->sendSecondReceipt($orderId, $kassa, $status);
    }

    /**
     * Определение экшена и его выполнение
     *
     * @return void
     * @throws Exception
     */
    public function checkSubaction()
    {
        if ($_GET['subaction'] === 'get_log_messages') {
            $this->viewModuleLogs();
        }

        if ($_GET['subaction'] === 'clear_log_messages') {
            $this->clearModuleLogs();
        }

        if ($_GET['subaction'] === 'restore_backup') {
            ob_start();
            $result = $this->restoreBackup();
            $data   = ob_get_clean();
            if (!empty($data)) {
                $result['output'] = $data;
            }
            $this->sendResponseJson($result);
        }

        if ($_GET['subaction'] === 'remove_backup') {
            ob_start();
            $result = $this->removeBackup();
            $data   = ob_get_clean();
            if (!empty($data)) {
                $result['output'] = $data;
            }
            $this->sendResponseJson($result);
        }

        if ($_GET['subaction'] === 'update') {
            $this->logger->sendHeka(array('module.update.init'));
            ob_start();
            $result = $this->updateVersion();
            $data   = ob_get_clean();
            if (!empty($data)) {
                $result['output'] = $data;
            }
            if ($result['success']) {
                $this->logger->sendHeka(array('module.update.success'));
            } else {
                $this->logger->sendHeka(array('module.update.fail'));
            }
            $this->sendResponseJson($result);
        }

        if ($_GET['subaction'] === 'get_oauth_url') {
            $this->getOauthUrl();
        }

        if ($_GET['subaction'] === 'get_oauth_token') {
            $this->getOauthToken();
        }
    }

    /**
     * Обработка ajax запроса на получение ссылки для авторизации по oauth
     *
     * @return void
     * @throws Exception
     */
    public function getOauthUrl()
    {
        if (!$this->isRequestAjaxAndAllowed()) {
            $this->sendResponseJson(['status' => 'error', 'error' => 'Unknown', 'code' => 'unknown']);
        }

        if (!$this->formParams && ($app = $this->getApplication())) {
            $this->formParams = $this->storageHelper->stringToArray($app->input->request->get('form', '', 'string'), 'pm_params');
        }
        $this->logger->sendHeka(array('oauth.process.init'));
        $oauth = $this->getOauthModel();
        $result = $oauth->getOauthConnectUrl();
        $code = isset($result['error']) ? 502 : 200;
        if ($code === 200) {
            $this->logger->sendHeka(array('oauth.process.success'));
        } else {
            $this->logger->sendHeka(array('oauth.process.fail'));
        }
        $this->sendResponseJson($result, $code);
    }

    /**
     * Обработка ajax запроса на получение токена для авторизации по oauth
     *
     * @return void
     * @throws Exception
     */
    public function getOauthToken()
    {
        if (!$this->isRequestAjaxAndAllowed()) {
            $this->sendResponseJson(['status' => 'error', 'error' => 'Unknown', 'code' => 'unknown']);
        }
        $this->logger->sendHeka(array('oauth.callback.init'));
        $oauth = $this->getOauthModel();
        $result = $oauth->getOauthToken();
        $code = isset($result['error']) ? 502 : 200;
        if ($code === 200) {
            $this->logger->sendHeka(array('oauth.callback.success'));
        } else {
            $this->logger->sendHeka(array('oauth.callback.fail'));
        }
        $this->sendResponseJson($result, $code);
    }

    /**
     * Проверка на корректность запроса со стороны юзера и типа запроса
     *
     * @return bool
     */
    private function isRequestAjaxAndAllowed()
    {
        return $this->isAllowedUser() || $this->isAjaxRequest();
    }

    /**
     * Отправка ответа в формате json
     *
     * @param array $result
     * @param int $code
     * @return void
     */
    private function sendResponseJson($result, $code = 200)
    {
        echo json_encode($result);
        exit($code);
    }

    /**
     * Проверка есть ли доступ отправлять запросы у текущего юзера
     *
     * @return bool
     */
    private function isAllowedUser()
    {
        $app = $this->getApplication();
        return $app && $app->getIdentity()->authorise('core.admin');
    }

    /**
     * Проверка на ajax
     *
     * @return bool
     */
    private function isAjaxRequest()
    {
        $app = $this->getApplication();
        return $app && strtolower($app->input->server->get('HTTP_X_REQUESTED_WITH', '')) === 'xmlhttprequest';
    }

    /**
     * Получение глобального объекта приложения
     *
     * @return \Joomla\CMS\Application\CMSApplicationInterface|void|null
     */
    private function getApplication()
    {
        try {
            return JFactory::getApplication();
        } catch (Exception $e) {
            return;
        }
    }

    /**
     * @return OauthDataStorageHelper
     */
    public function getOauthDataStorageHelper()
    {
        return new OauthDataStorageHelper($this->kassa);
    }

    /**
     * @return YookassaClientFactory
     */
    public function getYookassaClientFactory()
    {
        return new YookassaClientFactory($this->logger, $this->kassa);
    }

    /**
     * @return OauthWebhooksHelper
     */
    public function getOauthWebhooksHelper()
    {
        return new OauthWebhooksHelper(
            $this->logger,
            $this->getYookassaClientFactory(),
            $this->versionHelper
        );
    }

    /**
     * @return YookassaShopInfoHelper
     */
    public function getYookassaShopInfoHelper()
    {
        return new YookassaShopInfoHelper($this->getYookassaClientFactory());
    }

    /**
     * @return HttpClient
     */
    private function getHttpClient()
    {
        return new HttpClient();
    }

    /**
     * Получение новой модели Oauth
     *
     * @return OauthModel
     */
    private function getOauthModel()
    {
        $this->getKassaPaymentMethod($this->formParams);
        return new OauthModel(
            $this->logger,
            $this->getOauthDataStorageHelper(),
            $this->getOauthWebhooksHelper(),
            $this->getYookassaShopInfoHelper(),
            $this->getHttpClient()
        );
    }
}
