%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/bitrix/www/bitrix/modules/sale/lib/cashbox/
Upload File :
Create Path :
Current File : /home/bitrix/www/bitrix/modules/sale/lib/cashbox/cashboxorangedata.php

<?php

namespace Bitrix\Sale\Cashbox;

use Bitrix\Main;
use Bitrix\Main\Text;
use Bitrix\Main\Localization;
use Bitrix\Sale\Cashbox\Errors;
use Bitrix\Sale\Result;
use Bitrix\Catalog;

Localization\Loc::loadMessages(__FILE__);

/**
 * Class CashboxOrangeData
 * @package Bitrix\Sale\Cashbox
 */
class CashboxOrangeData extends Cashbox implements IPrintImmediately, ICheckable
{
	const RESPONSE_HTTP_CODE_200 = 200;
	const RESPONSE_HTTP_CODE_201 = 201;

	const HANDLER_MODE_TEST = 'TEST';
	const HANDLER_MODE_ACTIVE = 'ACTIVE';

	const HANDLER_TEST_URL = 'ssl://apip.orangedata.ru:2443/api/v2';
	const HANDLER_ACTIVE_URL = 'ssl://api.orangedata.ru:12003/api/v2';

	private $pathToSslCertificate = '';
	private $pathToSslCertificateKey = '';

	/**
	 * @return string
	 */
	public static function getName()
	{
		return Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_TITLE');
	}

	/**
	 * @return array
	 */
	private function getCheckTypeMap()
	{
		return array(
			SellCheck::getType() => 4,
			SellReturnCashCheck::getType() => 4,
			SellReturnCheck::getType() => 4,
			AdvancePaymentCheck::getType() => 3,
			AdvanceReturnCashCheck::getType() => 3,
			AdvanceReturnCheck::getType() => 3,
			PrepaymentCheck::getType() => 2,
			PrepaymentReturnCheck::getType() => 2,
			PrepaymentReturnCashCheck::getType() => 2,
			FullPrepaymentCheck::getType() => 1,
			FullPrepaymentReturnCheck::getType() => 1,
			FullPrepaymentReturnCashCheck::getType() => 1,
			CreditCheck::getType() => 6,
			CreditReturnCheck::getType() => 6,
			CreditPaymentCheck::getType() => 7,
		);
	}

	/**
	 * @return array
	 */
	private function getCalculatedSignMap()
	{
		return array(
			Check::CALCULATED_SIGN_INCOME => 1,
			Check::CALCULATED_SIGN_CONSUMPTION => 2
		);
	}

	/**
	 * @param Check $check
	 * @return array
	 * @throws Main\NotImplementedException
	 */
	public function buildCheckQuery(Check $check)
	{
		$checkInfo = $check->getDataForCheck();

		$calculatedSignMap = $this->getCalculatedSignMap();

		$result = array(
			'id' => static::buildUuid(static::UUID_TYPE_CHECK, $checkInfo['unique_id']),
			'inn' => $this->getValueFromSettings('SERVICE', 'INN'),
			'group' => $this->getField('NUMBER_KKM') ?: null,
			'key' => $this->getValueFromSettings('SECURITY', 'KEY_SIGN') ?: null,
			'content' => array(
				'type' => $calculatedSignMap[$check::getCalculatedSign()],
				'positions' => array(),
				'checkClose' => array(
					'payments' => array(),
					'taxationSystem' => $this->getValueFromSettings('TAX', 'SNO'),
				),
				'customerContact' => $this->getCustomerContact($checkInfo),
			)
		);

		$checkType = $this->getCheckTypeMap();
		foreach ($checkInfo['items'] as $item)
		{
			$vat = $this->getValueFromSettings('VAT', $item['vat']);
			if ($vat === null)
			{
				$vat = $this->getValueFromSettings('VAT', 'NOT_VAT');
			}

			$result['content']['positions'][] = array(
				'text' => $item['name'],
				'quantity' => $item['quantity'],
				'price' => $item['price'],
				'tax' => $vat,
				'paymentMethodType' => $checkType[$check::getType()],
				'paymentSubjectType' => null
			);
		}

		$paymentTypeMap = $this->getPaymentTypeMap();
		foreach ($checkInfo['payments'] as $payment)
		{
			$result['content']['checkClose']['payments'][] = array(
				'type' => $paymentTypeMap[$payment['type']],
				'amount' => $payment['sum'],
			);
		}

		return $result;
	}

	/**
	 * @param array $data
	 * @return mixed|string
	 */
	private function getCustomerContact(array $data)
	{
		$customerContact = $this->getValueFromSettings('CLIENT', 'INFO');

		if ($customerContact === 'EMAIL')
		{
			return $data['client_email'];
		}
		elseif ($customerContact === 'PHONE')
		{
			$phone = \NormalizePhone($data['client_phone']);
			if ($phone[0] !== '7')
			{
				$phone = '7'.$phone;
			}

			return '+'.$phone;
		}

		if ($data['client_phone'])
		{
			$phone = \NormalizePhone($data['client_phone']);
			if ($phone[0] !== '7')
			{
				$phone = '7'.$phone;
			}

			return '+'.$phone;
		}

		return $data['client_email'];
	}

	/**
	 * @return array
	 */
	private function getPaymentTypeMap()
	{
		return array(
			Check::PAYMENT_TYPE_CASH => 1,
			Check::PAYMENT_TYPE_CASHLESS => 2,
			Check::PAYMENT_TYPE_ADVANCE => 14,
			Check::PAYMENT_TYPE_CREDIT => 15,
		);
	}

	/**
	 * @param $url
	 * @param $data
	 * @return string
	 */
	private function getPrintQueryHeaders($url, $data)
	{
		$sign = $this->sign($data);
		if ($sign === false)
		{
			return false;
		}

		$urlObj = new Main\Web\Uri($url);

		$header = "POST /api/v2/documents/ HTTP/1.0\r\n";
		$header .= "Host: ".$urlObj->getHost()."\r\n";
		$header .= "Accept: application/json\r\n";
		$header .= "Content-Type: application/json\r\n";
		$header .= "X-Signature: ".$sign."\r\n";
		$header .= sprintf("Content-length: %s\r\n", Text\BinaryString::getLength($data));
		$header .= "\r\n";

		return $header;
	}

	/**
	 * @param $id
	 * @return array
	 */
	public function buildZReportQuery($id)
	{
		return array();
	}

	/**
	 * @param Check $check
	 * @return Result
	 * @throws Main\ArgumentException
	 * @throws Main\NotImplementedException
	 */
	public function printImmediately(Check $check)
	{
		$result = new Result();

		$url = $this->getUrl();
		$url .= '/documents/';

		$data = $this->buildCheckQuery($check);
		$encodedData = $this->encode($data);

		$headers = $this->getPrintQueryHeaders($url, $encodedData);
		if ($headers === false)
		{
			$result->addError(new Errors\Error(Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_SIGN')));
			return $result;
		}

		$queryResult = $this->sendQuery($url, $headers, $encodedData);

		if (!$queryResult->isSuccess())
		{
			$result->addErrors($queryResult->getErrors());
			return $result;
		}

		$response = $queryResult->getData();
		$httpCode = $response['http_code'];
		if ($httpCode === static::RESPONSE_HTTP_CODE_201)
		{
			$result->setData(array('UUID' => $data['id']));
		}
		else
		{
			$error = '';

			if (isset($response['content']))
			{
				$content = $this->decode($response['content']);
				if (isset($content['errors']))
				{
					$error = implode("\n", $content['errors']);
				}
				else
				{
					$error = Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_RESPONSE_'.$httpCode);
				}
			}

			if (!$error)
			{
				$error = Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_CHECK_PRINT');
			}

			$result->addError(new Errors\Error($error));
		}

		return $result;
	}

	/**
	 * @return string
	 */
	private function getUrl()
	{
		if ($this->getValueFromSettings('INTERACTION', 'MODE_HANDLER') === static::HANDLER_MODE_ACTIVE)
		{
			return static::HANDLER_ACTIVE_URL;
		}

		return static::HANDLER_TEST_URL;
	}

	/**
	 * @param $url
	 * @param $headers
	 * @param string $data
	 * @return Result
	 */
	private function sendQuery($url, $headers, $data = '')
	{
		$context = $this->createStreamContext();

		$errNumber = '';
		$errString = '';
		$client = stream_socket_client($url, $errNumber, $errString, 5, STREAM_CLIENT_CONNECT, $context);

		$result = new Result();
		if ($client !== false)
		{
			fputs($client, $headers.$data);
			$response = stream_get_contents($client);
			fclose($client);

			list($responseHeaders, $content) = explode("\r\n\r\n", $response);
			$httpCode = $this->extractResponseStatus($responseHeaders);
			$result->addData(array('http_code' => $httpCode, 'content' => $content));
		}
		else
		{
			$result->addError(
				new Errors\Error(
					Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_SEND_QUERY')
				)
			);

			$error = new Errors\Error($errNumber.': '.$errString);
			Manager::writeToLog($this->getField('ID'), $error);
		}

		return $result;
	}

	/**
	 * @param $headers
	 * @return int
	 */
	private function extractResponseStatus($headers)
	{
		$headers = explode("\n", $headers);
		preg_match('#HTTP\S+ (\d+)#', $headers[0], $find);

		return (int)$find[1];
	}

	/**
	 * @return void
	 */
	public function __destruct()
	{
		if ($this->pathToSslCertificate !== ''
			&& Main\IO\File::isFileExists($this->pathToSslCertificate)
		)
		{
			unlink($this->pathToSslCertificate);
		}

		if ($this->pathToSslCertificateKey !== ''
			&& Main\IO\File::isFileExists($this->pathToSslCertificateKey)
		)
		{
			unlink($this->pathToSslCertificateKey);
		}
	}

	/**
	 * @return resource
	 */
	private function createStreamContext()
	{
		$sslCert = $this->getValueFromSettings('SECURITY', 'SSL_CERT');
		$this->pathToSslCertificate = $this->createTmpFile($sslCert);

		$sslKey = $this->getValueFromSettings('SECURITY', 'SSL_KEY');
		$this->pathToSslCertificateKey = $this->createTmpFile($sslKey);

		return stream_context_create(array(
			'ssl' => array(
				'local_cert' => $this->pathToSslCertificate,
				'local_pk' => $this->pathToSslCertificateKey,
				'passphrase' => $this->getValueFromSettings('SECURITY', 'SSL_KEY_PASS'),
			)
		));
	}

	/**
	 * @param Check $check
	 * @return Result
	 */
	public function check(Check $check)
	{
		$result = new Result();

		$url = $this->getUrl();
		$url .= '/documents/'.$this->getValueFromSettings('SERVICE', 'INN').'/status/'.$check->getField('EXTERNAL_UUID');

		$header = $this->getCheckQueryHeaders($url);
		$queryResult = $this->sendQuery($url, $header);

		$data = $queryResult->getData();
		if ($data['http_code'] !== static::RESPONSE_HTTP_CODE_200)
		{
			$error = Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_RESPONSE_'.$data['http_code']);
			if (!$error)
			{
				$error = implode("\n", $queryResult->getErrorMessages());
			}

			$result->addError(new Errors\Error($error));

			return $result;
		}

		$response = $this->decode($data['content']);
		if ($response === false)
		{
			$result->addError(new Errors\Error(Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_ERROR_CHECK_CHECK')));
			return $result;
		}

		return static::applyCheckResult($response);}

	/**
	 * @param $url
	 * @return string
	 */
	private function getCheckQueryHeaders($url)
	{
		$urlObj = new Main\Web\Uri($url);

		$header = "GET ".$urlObj->getPath()." HTTP/1.0\r\n";
		$header .= "Host: ".$urlObj->getHost()."\r\n";
		$header .= "Accept: application/json\r\n";
		$header .= "Content-Type: application/json\r\n";
		$header .= "\r\n";

		return $header;
	}

	/**
	 * @param array $data
	 * @return array
	 * @throws Main\ArgumentException
	 * @throws Main\NotImplementedException
	 * @throws Main\ObjectException
	 */
	protected static function extractCheckData(array $data)
	{
		$result = array();

		if (!$data['id'])
		{
			return $result;
		}

		$checkInfo = CheckManager::getCheckInfoByExternalUuid($data['id']);

		$result['ID'] = $checkInfo['ID'];
		$result['CHECK_TYPE'] = $checkInfo['TYPE'];

		$check = CheckManager::getObjectById($checkInfo['ID']);
		$dateTime = new Main\Type\DateTime($data['processedAt'], 'Y-m-d\TH:i:s.u');
		$result['LINK_PARAMS'] = array(
			Check::PARAM_REG_NUMBER_KKT => $data['deviceRN'],
			Check::PARAM_FISCAL_DOC_ATTR => $data['fp'],
			Check::PARAM_FISCAL_DOC_NUMBER => $data['documentNumber'],
			Check::PARAM_FISCAL_RECEIPT_NUMBER => $data['documentIndex'],
			Check::PARAM_FN_NUMBER => $data['fsNumber'],
			Check::PARAM_SHIFT_NUMBER => $data['shiftNumber'],
			Check::PARAM_DOC_SUM => $data['total'],
			Check::PARAM_DOC_TIME => $dateTime->getTimestamp(),
			Check::PARAM_CALCULATION_ATTR => $check::getCalculatedSign()
		);

		return $result;
	}

	/**
	 * @param $data
	 * @return string
	 */
	public function sign($data)
	{
		if (!function_exists('openssl_get_privatekey') || !function_exists('openssl_private_encrypt'))
		{
			return false;
		}

		$data = pack('H*', '3031300d060960864801650304020105000420') . hash('sha256', $data, true);
		$pk = openssl_get_privatekey($this->getValueFromSettings('SECURITY', 'PKEY'));

		openssl_private_encrypt($data, $res, $pk);
		return base64_encode($res);
	}

	/**
	 * @param array $data
	 * @return mixed
	 * @throws Main\ArgumentException
	 */
	private function encode(array $data)
	{
		return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
	}

	/**
	 * @param string $data
	 * @return mixed
	 */
	private function decode($data)
	{
		try
		{
			return Main\Web\Json::decode($data);
		}
		catch (Main\ArgumentException $exception)
		{
			return false;
		}
	}

	/**
	 * @param $data
	 * @return mixed
	 */
	private function createTmpFile($data)
	{
		$filePath = tempnam(sys_get_temp_dir(), 'orange_data');
		if ($data !== null)
		{
			file_put_contents($filePath, $data);
		}

		return $filePath;
	}

	/**
	 * @param int $modelId
	 * @return array
	 * @throws Main\ArgumentException
	 * @throws Main\LoaderException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getSettings($modelId = 0)
	{
		$settings = array(
			'SECURITY' => array(
				'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY'),
				'ITEMS' => array(
					'PKEY' => array(
						'TYPE' => 'SECURITY_FILE_CONTROL',
						'CLASS' => 'adm-designed-file',
						'REQUIRED' => 'Y',
						'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY_PKEY'),
					),
					'SSL_CERT' => array(
						'TYPE' => 'SECURITY_FILE_CONTROL',
						'CLASS' => 'adm-designed-file',
						'REQUIRED' => 'Y',
						'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY_SSL_CERT'),
					),
					'SSL_KEY' => array(
						'TYPE' => 'SECURITY_FILE_CONTROL',
						'CLASS' => 'adm-designed-file',
						'REQUIRED' => 'Y',
						'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY_SSL_KEY'),
					),
					'SSL_KEY_PASS' => array(
						'TYPE' => 'STRING',
						'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY_SSL_KEY_PASS'),
					),
					'KEY_SIGN' => array(
						'TYPE' => 'STRING',
						'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SECURITY_KEY_SIGN'),
					),
				)
			)
		);

		$settings['SERVICE'] = array(
			'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SERVICE'),
			'REQUIRED' => 'Y',
			'ITEMS' => array(
				'INN' => array(
					'TYPE' => 'STRING',
					'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SERVICE_INN_LABEL')
				)
			)
		);

		$settings['CLIENT'] = [
			'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_CLIENT'),
			'ITEMS' => array(
				'INFO' => array(
					'TYPE' => 'ENUM',
					'VALUE' => 'NONE',
					'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_CLIENT_INFO'),
					'OPTIONS' => array(
						'DEFAULT' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_CLIENT_DEFAULT'),
						'PHONE' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_CLIENT_PHONE'),
						'EMAIL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_CLIENT_EMAIL'),
					)
				),
			)
		];

		$settings['VAT'] = array(
			'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_VAT'),
			'REQUIRED' => 'Y',
			'ITEMS' => array(
				'NOT_VAT' => array(
					'TYPE' => 'STRING',
					'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_VAT_LABEL_NOT_VAT'),
					'VALUE' => 6
				)
			)
		);

		if (Main\Loader::includeModule('catalog'))
		{
			$dbRes = Catalog\VatTable::getList(array('filter' => array('ACTIVE' => 'Y')));
			$vatList = $dbRes->fetchAll();
			if ($vatList)
			{
				$defaultVat = array(0 => 5, 10 => 2, 18 => 1, 20 => 1);
				foreach ($vatList as $vat)
				{
					$value = '';
					if (isset($defaultVat[(int)$vat['RATE']]))
						$value = $defaultVat[(int)$vat['RATE']];

					$settings['VAT']['ITEMS'][(int)$vat['ID']] = array(
						'TYPE' => 'STRING',
						'LABEL' => $vat['NAME'].' ['.(int)$vat['RATE'].'%]',
						'VALUE' => $value
					);
				}
			}
		}

		$settings['TAX'] = array(
			'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SNO'),
			'REQUIRED' => 'Y',
			'ITEMS' => array(
				'SNO' => array(
					'TYPE' => 'ENUM',
					'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_SNO_LABEL'),
					'VALUE' => 0,
					'OPTIONS' => array(
						0 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_OSN'),
						1 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_UI'),
						2 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_UIO'),
						3 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_ENVD'),
						4 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_ESN'),
						5 => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SNO_PATENT')
					)
				)
			)
		);

		$settings['INTERACTION'] = array(
			'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_INTERACTION'),
			'ITEMS' => array(
				'MODE_HANDLER' => array(
					'TYPE' => 'ENUM',
					'LABEL' => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_SETTINGS_MODE_HANDLER_LABEL'),
					'OPTIONS' => array(
						static::HANDLER_MODE_ACTIVE => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_MODE_ACTIVE'),
						static::HANDLER_MODE_TEST => Localization\Loc::getMessage('SALE_CASHBOX_ORANGE_DATA_MODE_TEST'),
					)
				)
			)
		);

		return $settings;
	}

	/**
	 * @param Main\HttpRequest $request
	 * @return array
	 */
	public static function extractSettingsFromRequest(Main\HttpRequest $request)
	{
		global $APPLICATION;

		$settings = parent::extractSettingsFromRequest($request);
		$files = $request->getFile('SETTINGS');

		foreach ($settings['SECURITY'] as $fieldId => $field)
		{
			if ($files['error']['SECURITY'][$fieldId.'_FILE'] === 0
				&& $files['tmp_name']['SECURITY'][$fieldId.'_FILE']
			)
			{
				$content = $APPLICATION->GetFileContent($files['tmp_name']['SECURITY'][$fieldId.'_FILE']);
				$settings['SECURITY'][$fieldId] = $content ?: '';
			}
		}

		return $settings;
	}

	/**
	 * @return bool
	 */
	public static function isSupportedFFD105()
	{
		return true;
	}

}

Zerion Mini Shell 1.0