Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/modules/fermaofd.ferma/classes/general/ferma-api/
Upload File :
Current File : /var/www/homesaver/www/bitrix/modules/fermaofd.ferma/classes/general/ferma-api/CFermaApiService.php

<?php

require_once __DIR__ . '/../../../const.php';

use Bitrix\Main\Web\Json;
use Bitrix\Sale\BasketBase;
use Bitrix\Sale\Order;
use Bitrix\Sale\OrderBase;

IncludeModuleLangFile(__DIR__ . '/ferma-api.php');

class CFermaApiService
{
    /**
     * @var string
     */
    private $apiHost;
    /**
     * @var string
     */
    private $apiLogin;
    /**
     * @var string
     */
    private $apiPassword;
    /**
     * @var string
     */
    private $agentInn;
    /**
     * @var string
     */
    private $agentTaxation;
    /**
     * @var string
     */
    private $accessToken;
    /**
     * @var \CFermaApiClient
     */
    private $client;
    /**
     * @var bool
     */
    private $logJson = false;

    private static $instances = [];

    public function __construct(
        $apiHost,
        $apiLogin,
        $apiPassword,
        $agentInn,
        $agentTaxation,
        $errorsEmail,
        $timeout = 15
    )
    {
        $this->apiHost = $apiHost;
        $this->apiLogin = $apiLogin;
        $this->apiPassword = $apiPassword;
        $this->agentInn = $agentInn;
        $this->agentTaxation = $agentTaxation;

        $this->client = CFermaApiClient::instance($apiHost, $errorsEmail, $timeout);

        $this->logJson = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'receipt_json_log', 'N') === 'Y';
    }

    /**
     * @param string $apiHost
     * @param string $apiLogin
     * @param string $apiPassword
     * @param string $agentInn
     * @param string $agentTaxation
     * @param string $errorsEmail
     * @return CFermaApiClient
     */
    public static function instance($apiHost, $apiLogin, $apiPassword, $agentInn, $agentTaxation, $errorsEmail)
    {
        $hash = sha1($apiHost . '/' . $apiLogin . '/' . $apiPassword);

        if (!isset(static::$instances[$hash])) {
            static::$instances[$hash] = new static($apiHost, $apiLogin, $apiPassword, $agentInn, $agentTaxation, $errorsEmail);
        }

        return static::$instances[$hash];
    }

    public function getEnvId()
    {
        switch ($this->apiHost) {
            case 'https://ferma.ofd.ru':
                return 'prod';
            case 'https://ferma-test.ofd.ru':
                return 'demo';
            default:
                return null;
        }
    }

    public function getAccessToken()
    {
        if (null === $this->accessToken) {
            $this->renewAccessToken();
        }

        return $this->accessToken;
    }

    /**
     * @param string $receiptId
     * @return array
     * @throws \CFermaApiException
     */
    public function getElectronReceiptStatus($receiptId)
    {
        return $this->client->post('/api/kkt/cloud/status', ['Request' => ['ReceiptId' => $receiptId]], $this->getAccessToken());
    }

    /**
     * @param array $electronReceiptData
     * @return array
     * @throws \CFermaApiException
     */
    public function sendElectronReceipt($orderId, $receiptData)
    {
        global $DB;

        if ($this->logJson) {
            CMainOfdFerma::log(
                GetMessage('FERMAOFD_SUBMIT_RECEIPT_TRY'), null,
                Json::encode($receiptData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), 'DEBUG'
            );
        }

        try {
            $response = $this->client->post('/api/kkt/cloud/receipt', ['Request' => $receiptData], $this->getAccessToken());
        } catch (CFermaApiException $e) {
            throw new \RuntimeException(sprintf(GetMessage('FERMAOFD_FAILED_SENDING') . ': %s.', $e->getMessage()));
        }

        if (empty($response)) {
            throw new \RuntimeException(GetMessage('FERMAOFD_FAILED_SENDING_FERMA_EMPTY_RESPONSE'));
        }

        $total = array_reduce(
            $receiptData['CustomerReceipt']['Items'],
            static function ($result, $item) {
                return $result + $item['Amount'];
            },
            0
        );

        if ($response['Status'] === 'Failed') {

            $description = null;

            if (isset($response['Data']['ValidationErrors'])) {
                $description = implode('<br>', array_map(static function ($error) {
                    return $error['Description'];
                }, $response['Data']['ValidationErrors']));
            }

            COfdFermaReceipt::Add([
                'SITE_ID' => SITE_ID,
                'RECEIPT_ID' => null,
                'ORDER_ID' => $orderId,
                'PAYMENT_METHOD_ID' => $receiptData['CustomerReceipt']['Items'][0]['PaymentMethod'],
                'CONTENT' => Json::encode($receiptData),
                'TYPE' => $receiptData['Type'], // Income, IncomeReturn, IncomePrepayment, IncomeReturnPrepayment
                'TOTAL_PRICE' => $total,
                'ENV' => $this->getEnvId(),
                '=DATE_CREATE' => $DB->GetNowFunction(),
                '=DATE_UPDATE' => $DB->GetNowFunction(),
                'STATUS_ID' => -1,
                'STATUS_NAME' => 'ERROR',
                'STATUS_MESSAGE' => $response['Error']['Message'] ?? '������ �������� ����',
                'DESCRIPTION' => $description,
            ]);
        } else {
            $id = COfdFermaReceipt::Add([
                'SITE_ID' => SITE_ID,
                'RECEIPT_ID' => $response['Data']['ReceiptId'],
                'ORDER_ID' => $orderId,
                'PAYMENT_METHOD_ID' => $receiptData['CustomerReceipt']['Items'][0]['PaymentMethod'],
                'CONTENT' => Json::encode($receiptData),
                'TYPE' => $receiptData['Type'], // Income, IncomeReturn, IncomePrepayment, IncomeReturnPrepayment
                'TOTAL_PRICE' => $total,
                'ENV' => $this->getEnvId(),
                '=DATE_CREATE' => $DB->GetNowFunction(),
                '=DATE_UPDATE' => $DB->GetNowFunction(),
            ]);

            CAgent::AddAgent(
                'COfdFermaAgent::checkReceiptStatusAgent(' . Json::encode($id) . ');',
                FERMAOFD_FERMA_MODULE_ID,
                'Y', 10
            );
        }

        return $response;
    }

    public function prepareElectronReceipt($type, Order $order)
    {
        list($email, $phone) = Ferma_getCustomerContacts($order->getId());

        $deliveryInfo = Ferma_getDeliveryInfo($order);

        switch ($type) {
            case 'IncomeReturn':
                $paymentMethod = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'receipt_refund_default_method', '4');
                break;
            case 'IncomePrepayment':
                $paymentMethod = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'receipt_prepayment_default_method', '1');
                break;
            default:
            case 'Income':
                $paymentMethod = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'receipt_payment_default_method', '4');
                break;
        }

        $siteRes = CSite::GetByID($order->getSiteId());
        $site = $siteRes->Fetch();

        return $this->createElectronReceipt(
            $type, $email, $phone,
            1, //
            $order->getId(),
            $order->getField('DATE_PAYED'),
            $order->getPaymentCollection(),
            $this->formatOrderItems($paymentMethod, $order->getBasket(), $deliveryInfo),
            $site ? $site['SERVER_NAME'] : null
        );
    }

    public static function createInvoiceId($orderId, $type)
    {
        // unique invoice id
        /** @noinspection NonSecureUniqidUsageInspection */
        return $orderId . '/' . $type . '/' . uniqid();
    }

    public function createElectronReceipt(
        $type,
        $email,
        $phone,
        $paymentType,
        $orderId,
        $orderDate,
        $payments,
        $items,
        $billAddress
    )
    {
        $date = new \DateTime;

        if ($type === 'Income') {
            $date = new \DateTime(empty($orderDate) ? 'now' : $orderDate);
        }

        if ($payments === null) {
            $PaymentItems = array_map(function ($item) use ($type) {
                return [
                    'PaymentType' => $type === 'IncomePrepayment' ? '2' : '1',
                    'Sum' => $item['Amount'],
                ];
            }, $items);
        } elseif ($payments instanceof \Bitrix\Sale\PaymentCollection) {
            /*$PaymentItems = array_map(function ($payment) use ($type) {
                return [
                    'PaymentType' => $type === 'IncomePrepayment' ? '2' : $this->getPaymentMethod($payment['PAY_SYSTEM_ID']),
                    'Sum' => $payment['SUM'],
                ];
            }, array_filter($payments->toArray(), static function ($payment) {
                return $payment['PAID'] === 'Y' && $payment['IS_RETURN'] === 'N';
            }));*/
        } else {
            $PaymentItems = [];
        }

        return [
            'Inn' => $this->agentInn,
            'Type' => $type,
            'InvoiceId' => static::createInvoiceId($orderId, $type),
            'LocalDate' => $date->format('Y-m-d\TH:i:s'),
            'CustomerReceipt' => [
                'BillAddress' => $billAddress,
                'Email' => $email,
                'Phone' => $phone,
                'TaxationSystem' => $this->agentTaxation,
                'PaymentType' => $paymentType ? $paymentType : null,
                'Items' => array_values($items),
                'PaymentItems' => $PaymentItems,
            ],
        ];
    }

    private $paymentMethods = [];

    private function getPaymentMethod($paymentSystemId)
    {
        if (isset($this->paymentMethods[$paymentSystemId])) {
            return $this->paymentMethods[$paymentSystemId];
        }

        return $this->paymentMethods[$paymentSystemId] = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'payment_method_' . $paymentSystemId, '1');
    }

    private function formatOrderItems(string $paymentMethod, BasketBase $basket, $deliveryInfo = null)
    {
        $result = [];

        $productsId = [];

        /** @var Order $order */
        $order = $basket->getOrder();

        /** @var \Bitrix\Sale\BasketItem $item */
        foreach ($basket as $item) {
            $productsId[$item->getProductId()] = true;
        }

        if (empty($productsId)) {
            return $result;
        }

        $products = Ferma_getProducts(array_keys($productsId));
        $vats = Ferma_getVats();

        $defaultPaymentType = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'payment_default_type', '1');
        $defaultVat = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'payment_default_vat', 'VatNo');

        /** @var \Bitrix\Sale\BasketItem $item */
        foreach ($basket as $item) {
            $price = $item->getPrice();
            $quantity = $item->getQuantity();

            $vatId = $products[$item->getProductId()]['VAT_ID'];

            $paymentType = $products[$item->getProductId()]['PROPERTIES']['OFD_FERMA_PAYMENT_TYPE']['VALUE_XML_ID'];

            $markingCode = $order
                ? $this->findMarkingCodeInOrder($order, $item->getProductId())
                : false;

            if ($markingCode) {
                for ($i = 0; $i < $quantity; $i++) {
                    $result[] = [
                        'Label' => $item->getField('NAME'),
                        'Price' => $price,
                        'Quantity' => 1,
                        'Amount' => $price * $quantity,
                        'Vat' => empty($vatId) ? $defaultVat : CVatCollection::determinateVat($vats[$vatId]),
                        'PaymentMethod' => $paymentMethod,
                        'PaymentType' => $paymentType ?: $defaultPaymentType,
                        'MarkingCodeData' => $markingCode ? [
                            'Type' => 'UNKNOWN_PRODUCT_CODE',
                            'Code' => $markingCode,
                        ] : null,
                    ];

                    $markingCode = $this->findMarkingCodeInOrder($order, $item->getProductId());
                }
            } else {
                $result[] = [
                    'Label' => $item->getField('NAME'),
                    'Price' => $price,
                    'Quantity' => $quantity,
                    'Amount' => $price * $quantity,
                    'Vat' => empty($vatId) ? $defaultVat : CVatCollection::determinateVat($vats[$vatId]),
                    'PaymentMethod' => $paymentMethod,
                    'PaymentType' => $paymentType ?: $defaultPaymentType,
                ];
            }
        }

        $deliveryReceipt = COption::GetOptionString(FERMAOFD_FERMA_MODULE_ID, 'payment_delivery', 'Include');

        if ($deliveryReceipt === 'Include' && !empty($deliveryInfo) && !empty($deliveryInfo['PRICE'])) {
            $result[] = [
                'Label' => $deliveryInfo['NAME'],
                'Price' => $deliveryInfo['PRICE'],
                'Quantity' => 1,
                'Amount' => $deliveryInfo['PRICE'],
                'Vat' => $defaultVat,
                'PaymentMethod' => $paymentMethod,
                'PaymentType' => '4',
            ];
        }

        return $result;
    }

    private $usedCodes = [];

    private function findMarkingCodeInOrder(Order $order, $productId)
    {
        $shipmentCollection = $order->getShipmentCollection();
        /** @var \Bitrix\Sale\Shipment $shipment */
        foreach ($shipmentCollection as $shipment) {
            $itemCollection = $shipment->getShipmentItemCollection();
            /** @var \Bitrix\Sale\ShipmentItem $item */
            foreach ($itemCollection as $item) {
                $itemStoreCollection = $item->getShipmentItemStoreCollection();
                $basketItem = $item->getBasketItem();

                if (!$basketItem || $productId !== $basketItem->getProductId()) {
                    continue;
                }

                /** @var \Bitrix\Sale\ShipmentItemStore $itemStore */
                foreach ($itemStoreCollection as $itemStore) {
                    $result = $itemStore->getMarkingCode();

                    if (!$result || isset($this->usedCodes[$result])) {
                        continue;
                    }

                    $this->usedCodes[$result] = true;

                    return $result;
                }
            }
        }

        return null;
    }

    private function renewAccessToken()
    {
        $response = $this->client->post('/api/Authorization/CreateAuthToken', [
            'Login' => $this->apiLogin,
            'Password' => $this->apiPassword,
        ]);

        if ($response['Status'] === 'Success') {
            $this->accessToken = $response['Data']['AuthToken'];
        } elseif ($response['Status'] === 'Failed' && isset($response['Error']['Message'])) {
            throw new CFermaApiException(sprintf('Api return failed response: %s.', $response['Error']['Message']));
        } else {
            throw new CFermaApiException('Api return invalid response.');
        }
    }
}

function Ferma_getProducts($productsId)
{
    CModule::IncludeModule('catalog');

    $result = [];

    $products = \Bitrix\Catalog\ProductTable::GetList([
        'select' => ['*'],
        'filter' => [
            '=ID' => $productsId,
        ],
    ]);

    $itemsId = [];
    $flatItemsId = [];
    $props = [];

    while ($product = $products->fetch()) {
        $result[$product['ID']] = $product;

        $data = CCatalogSku::GetProductInfo($product['ID']);

        if ($data) {
            if (!isset($itemsId[$data['IBLOCK_ID']])) {
                $itemsId[$data['IBLOCK_ID']] = [];
            }

            $itemsId[$data['IBLOCK_ID']][$product['ID']] = $data['ID'];
            $flatItemsId[$data['ID']] = $product['ID'];

            $props[$data['ID']] = [];
        }
    }

    foreach ($itemsId as $iBlockId => $map) {
        CIBlockElement::GetPropertyValuesArray($props, $iBlockId, [
            'ID' => array_values($map),
        ]);
    }

    foreach ($props as $itemId => $propsCollection) {
        $productId = $flatItemsId[$itemId];

        if (!isset($result[$productId])) {
            continue;
        }

        $result[$productId]['PROPERTIES'] = $propsCollection;
    }

    return $result;
}

function Ferma_getVats()
{
    $result = [];

    $vats = CCatalogVat::GetList();

    while ($product = $vats->NavNext()) {
        $result[$product['ID']] = $product;
    }

    return $result;
}

function Ferma_getCustomerContacts($orderId)
{
    $email = null;
    $phone = null;

    $props = (new CSaleOrderPropsValue())->GetList([], [
        'ORDER_ID' => $orderId,
        'CODE' => ['EMAIL', 'PHONE'],
    ]);

    while ($item = $props->Fetch()) {
        if ($item['CODE'] === 'EMAIL') {
            $email = $item['VALUE'];
        }

        if ($item['CODE'] === 'PHONE') {
            $phone = $item['VALUE'];
        }
    }

    return [$email, $phone ? Ferma_clearPhone($phone) : null];
}

function Ferma_getDeliveryInfo(OrderBase $order)
{
    $price = $order->getDeliveryPrice();

    if (empty($price)) {
        return null;
    }

    return [
        'NAME' => GetMessage('FERMAOFD_DELIVERY'),
        'PRICE' => $price,
    ];
}

function Ferma_clearPhone($phone)
{
    return preg_replace('/[^0-9+]+/', '', $phone);
}