2020-01-11 06:30:25 +00:00
|
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
|
|
|
|
|
*
|
|
|
|
|
* This code is licensed under MIT.
|
|
|
|
|
* Этот код распространяется по лицензии MIT.
|
|
|
|
|
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace AtolOnline\Api;
|
|
|
|
|
|
2020-06-07 11:36:52 +00:00
|
|
|
|
use AtolOnline\{Constants\Constraints,
|
|
|
|
|
Constants\TestEnvParams,
|
2020-06-04 13:39:11 +00:00
|
|
|
|
Entities\Company,
|
|
|
|
|
Entities\Document,
|
2020-06-07 11:36:52 +00:00
|
|
|
|
Exceptions\AtolAuthFailedException,
|
|
|
|
|
Exceptions\AtolCallbackUrlTooLongException,
|
2020-01-11 06:30:25 +00:00
|
|
|
|
Exceptions\AtolCorrectionInfoException,
|
2020-06-07 11:36:52 +00:00
|
|
|
|
Exceptions\AtolInvalidCallbackUrlException,
|
2020-05-27 16:39:42 +00:00
|
|
|
|
Exceptions\AtolInvalidUuidException,
|
2020-01-11 06:30:25 +00:00
|
|
|
|
Exceptions\AtolKktLoginEmptyException,
|
|
|
|
|
Exceptions\AtolKktLoginTooLongException,
|
|
|
|
|
Exceptions\AtolKktPasswordEmptyException,
|
2020-06-07 11:36:52 +00:00
|
|
|
|
Exceptions\AtolKktPasswordTooLongException,
|
2020-01-11 06:30:25 +00:00
|
|
|
|
Exceptions\AtolWrongDocumentTypeException
|
|
|
|
|
};
|
|
|
|
|
use GuzzleHttp\Client;
|
|
|
|
|
use Ramsey\Uuid\Uuid;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Класс для отправки запросов на ККТ
|
|
|
|
|
*
|
|
|
|
|
* @package AtolOnline\Api
|
|
|
|
|
*/
|
|
|
|
|
class Kkt extends Client
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* @var bool Флаг тестового режима работы
|
|
|
|
|
*/
|
|
|
|
|
protected $is_test_mode = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var array Настройки доступа к ККТ
|
|
|
|
|
*/
|
|
|
|
|
protected $kkt_config = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var \AtolOnline\Api\KktResponse|null Последний ответ сервера АТОЛ
|
|
|
|
|
*/
|
|
|
|
|
protected $last_response;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var string|null Токен авторизации
|
|
|
|
|
*/
|
|
|
|
|
private $auth_token;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Kkt constructor.
|
|
|
|
|
*
|
|
|
|
|
* @param string|null $group
|
|
|
|
|
* @param string|null $login
|
|
|
|
|
* @param string|null $pass
|
|
|
|
|
* @param bool $test_mode Флаг тестового режима
|
|
|
|
|
* @param array $guzzle_config Конфигурация GuzzleHttp
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktLoginEmptyException Логин ККТ не может быть пустым
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktLoginTooLongException Слишком длинный логин ККТ
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktPasswordEmptyException Пароль ККТ не может быть пустым
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktPasswordTooLongException Слишком длинный пароль ККТ
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(
|
|
|
|
|
?string $group = null,
|
|
|
|
|
?string $login = null,
|
|
|
|
|
?string $pass = null,
|
|
|
|
|
bool $test_mode = false,
|
|
|
|
|
array $guzzle_config = []
|
|
|
|
|
) {
|
|
|
|
|
$this->resetKktConfig();
|
|
|
|
|
if ($group) {
|
|
|
|
|
$this->setGroup($group);
|
|
|
|
|
}
|
|
|
|
|
if ($login) {
|
|
|
|
|
$this->setLogin($login);
|
|
|
|
|
}
|
|
|
|
|
if ($login) {
|
|
|
|
|
$this->setPassword($pass);
|
|
|
|
|
}
|
|
|
|
|
$this->setTestMode($test_mode);
|
|
|
|
|
$guzzle_config['base_uri'] = $this->getEndpoint();
|
|
|
|
|
$guzzle_config['http_errors'] = $guzzle_config['http_errors'] ?? false;
|
|
|
|
|
parent::__construct($guzzle_config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает группу доступа к ККТ
|
|
|
|
|
*
|
|
|
|
|
* @param string $group
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
|
|
|
|
public function setGroup(string $group)
|
|
|
|
|
{
|
|
|
|
|
$this->kkt_config['prod']['group'] = $group;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает группу доступа к ККТ в соответствии с флагом тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getGroup(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['group'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает логин доступа к ККТ
|
|
|
|
|
*
|
|
|
|
|
* @param string $login
|
|
|
|
|
* @return $this
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktLoginEmptyException Логин ККТ не может быть пустым
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktLoginTooLongException Слишком длинный логин ККТ
|
|
|
|
|
*/
|
|
|
|
|
public function setLogin(string $login)
|
|
|
|
|
{
|
2020-06-07 11:36:52 +00:00
|
|
|
|
if (empty($login)) {
|
|
|
|
|
throw new AtolKktLoginEmptyException();
|
|
|
|
|
} elseif (valid_strlen($login) > Constraints::MAX_LENGTH_LOGIN) {
|
|
|
|
|
throw new AtolKktLoginTooLongException($login, Constraints::MAX_LENGTH_LOGIN);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
$this->kkt_config['prod']['login'] = $login;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает логин доступа к ККТ в соответствии с флагом тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getLogin(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['login'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает пароль доступа к ККТ
|
|
|
|
|
*
|
|
|
|
|
* @param string $password
|
|
|
|
|
* @return $this
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktPasswordEmptyException Пароль ККТ не может быть пустым
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolKktPasswordTooLongException Слишком длинный пароль ККТ
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
|
|
|
|
public function setPassword(string $password)
|
|
|
|
|
{
|
2020-06-07 11:36:52 +00:00
|
|
|
|
if (empty($password)) {
|
|
|
|
|
throw new AtolKktPasswordEmptyException();
|
|
|
|
|
} elseif (valid_strlen($password) > Constraints::MAX_LENGTH_PASSWORD) {
|
|
|
|
|
throw new AtolKktPasswordTooLongException($password, Constraints::MAX_LENGTH_PASSWORD);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
$this->kkt_config['prod']['pass'] = $password;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает логин ККТ в соответствии с флагом тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getPassword(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['pass'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает URL для приёма колбеков
|
|
|
|
|
*
|
|
|
|
|
* @param string $url
|
|
|
|
|
* @return $this
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCallbackUrlTooLongException Слишком длинный Callback URL
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInvalidCallbackUrlException Невалидный Callback URL
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
|
|
|
|
public function setCallbackUrl(string $url)
|
|
|
|
|
{
|
2020-06-07 11:36:52 +00:00
|
|
|
|
if (valid_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
|
|
|
|
|
throw new AtolCallbackUrlTooLongException($url, Constraints::MAX_LENGTH_CALLBACK_URL);
|
2020-06-14 05:44:35 +00:00
|
|
|
|
} elseif (!preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
|
|
|
|
|
throw new AtolInvalidCallbackUrlException('Callback URL not matches with pattern');
|
2020-06-07 11:36:52 +00:00
|
|
|
|
}
|
2020-05-29 17:57:38 +00:00
|
|
|
|
$this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'] = $url;
|
2020-01-11 06:30:25 +00:00
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает URL для приёма колбеков
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getCallbackUrl(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает последний ответ сервера
|
|
|
|
|
*
|
|
|
|
|
* @return mixed
|
|
|
|
|
*/
|
|
|
|
|
public function getLastResponse()
|
|
|
|
|
{
|
|
|
|
|
return $this->last_response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает флаг тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isTestMode(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->is_test_mode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает флаг тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @param bool $test_mode
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
|
|
|
|
public function setTestMode(bool $test_mode = true)
|
|
|
|
|
{
|
|
|
|
|
$this->is_test_mode = $test_mode;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ прихода
|
|
|
|
|
*
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @param \AtolOnline\Entities\Document $document Объект документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function sell(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if ($document->getCorrectionInfo()) {
|
2020-04-15 13:39:48 +00:00
|
|
|
|
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('sell', 'receipt', $document, $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ возврата прихода
|
|
|
|
|
*
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @param \AtolOnline\Entities\Document $document Объект документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function sellRefund(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if ($document->getCorrectionInfo()) {
|
2020-06-07 11:36:52 +00:00
|
|
|
|
throw new AtolCorrectionInfoException('Invalid operation on correction document');
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('sell_refund', 'receipt', $document->clearVats(), $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ коррекции прихода
|
|
|
|
|
*
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @param \AtolOnline\Entities\Document $document Объект документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function sellCorrection(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if (!$document->getCorrectionInfo()) {
|
|
|
|
|
throw new AtolCorrectionInfoException();
|
|
|
|
|
}
|
|
|
|
|
$document->setClient(null)->setItems([]);
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('sell_correction', 'correction', $document, $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ расхода
|
|
|
|
|
*
|
|
|
|
|
* @param \AtolOnline\Entities\Document $document
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function buy(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if ($document->getCorrectionInfo()) {
|
2020-06-07 11:36:52 +00:00
|
|
|
|
throw new AtolCorrectionInfoException('Invalid operation on correction document');
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('buy', 'receipt', $document, $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ возврата расхода
|
|
|
|
|
*
|
|
|
|
|
* @param \AtolOnline\Entities\Document $document
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function buyRefund(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if ($document->getCorrectionInfo()) {
|
2020-06-07 11:36:52 +00:00
|
|
|
|
throw new AtolCorrectionInfoException('Invalid operation on correction document');
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('buy_refund', 'receipt', $document->clearVats(), $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Регистрирует документ коррекции расхода
|
|
|
|
|
*
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @param \AtolOnline\Entities\Document $document
|
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длтина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
2020-05-31 20:12:23 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-31 20:12:23 +00:00
|
|
|
|
public function buyCorrection(Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
if (!$document->getCorrectionInfo()) {
|
|
|
|
|
throw new AtolCorrectionInfoException();
|
|
|
|
|
}
|
|
|
|
|
$document->setClient(null)->setItems([]);
|
2020-05-31 20:12:23 +00:00
|
|
|
|
return $this->registerDocument('buy_correction', 'correction', $document, $external_id);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Проверяет статус чека на ККТ один раз
|
|
|
|
|
*
|
|
|
|
|
* @param string $uuid UUID регистрации
|
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-05-27 16:39:42 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInvalidUuidException Некорректный UUID документа
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
|
|
|
|
public function getDocumentStatus(string $uuid)
|
|
|
|
|
{
|
|
|
|
|
$uuid = trim($uuid);
|
|
|
|
|
if (!Uuid::isValid($uuid)) {
|
2020-05-27 16:39:42 +00:00
|
|
|
|
throw new AtolInvalidUuidException($uuid);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
$this->auth();
|
|
|
|
|
return $this->sendAtolRequest('GET', 'report/'.$uuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
|
|
|
|
|
* Вернёт результат как только при очередной проверке сменится статус регистрации документа.
|
|
|
|
|
*
|
|
|
|
|
* @param string $uuid UUID регистрации
|
|
|
|
|
* @param int $retry_count Количество попыток
|
|
|
|
|
* @param int $timeout Таймаут в секундах между попытками
|
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInvalidUuidException Некорректный UUID документа
|
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
|
|
|
|
public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1)
|
|
|
|
|
{
|
|
|
|
|
$try = 0;
|
|
|
|
|
do {
|
|
|
|
|
$response = $this->getDocumentStatus($uuid);
|
|
|
|
|
if ($response->isValid() && $response->getContent()->status == 'done') {
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
sleep($timeout);
|
|
|
|
|
}
|
|
|
|
|
++$try;
|
|
|
|
|
} while ($try < $retry_count);
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-31 19:26:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Возвращает текущий токен авторизации
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getAuthToken(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->auth_token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Устанавливает заранее известный токен авторизации
|
|
|
|
|
*
|
|
|
|
|
* @param string|null $auth_token
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
|
|
|
|
public function setAuthToken(?string $auth_token)
|
|
|
|
|
{
|
|
|
|
|
$this->auth_token = $auth_token;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-11 06:30:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Сбрасывает настройки ККТ по умолчанию
|
|
|
|
|
*/
|
|
|
|
|
protected function resetKktConfig(): void
|
|
|
|
|
{
|
|
|
|
|
$this->kkt_config['prod']['group'] = '';
|
|
|
|
|
$this->kkt_config['prod']['login'] = '';
|
|
|
|
|
$this->kkt_config['prod']['pass'] = '';
|
|
|
|
|
$this->kkt_config['prod']['url'] = 'https://online.atol.ru/possystem/v4';
|
|
|
|
|
$this->kkt_config['prod']['callback_url'] = '';
|
2020-06-04 13:39:11 +00:00
|
|
|
|
$this->kkt_config['test']['group'] = TestEnvParams::GROUP;
|
|
|
|
|
$this->kkt_config['test']['login'] = TestEnvParams::LOGIN;
|
|
|
|
|
$this->kkt_config['test']['pass'] = TestEnvParams::PASSWORD;
|
2020-01-11 06:30:25 +00:00
|
|
|
|
$this->kkt_config['test']['url'] = 'https://testonline.atol.ru/possystem/v4';
|
|
|
|
|
$this->kkt_config['test']['callback_url'] = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает набор заголовков для HTTP-запроса
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
protected function getHeaders()
|
|
|
|
|
{
|
|
|
|
|
$headers['Content-type'] = 'application/json; charset=utf-8';
|
|
|
|
|
if ($this->getAuthToken()) {
|
2020-05-29 17:58:40 +00:00
|
|
|
|
$headers['Token'] = $this->getAuthToken();
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
return $headers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает адрес сервера в соответствии с флагом тестового режима
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function getEndpoint(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['url'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Возвращает полный URL до метода API
|
|
|
|
|
*
|
|
|
|
|
* @param string $to_method
|
|
|
|
|
* @param array|null $get_parameters
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function makeUrl(string $to_method, array $get_parameters = null)
|
|
|
|
|
{
|
|
|
|
|
$url = $this->getEndpoint().($this->getAuthToken() ? '/'.$this->getGroup() : '').'/'.$to_method;
|
|
|
|
|
if ($get_parameters && is_array($get_parameters)) {
|
|
|
|
|
$url .= '?'.http_build_query($get_parameters);
|
|
|
|
|
}
|
|
|
|
|
return $url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Делает запрос, возвращает декодированный ответ
|
|
|
|
|
*
|
|
|
|
|
* @param string $http_method Метод HTTP (GET, POST и пр)
|
|
|
|
|
* @param string $api_method Метод API
|
|
|
|
|
* @param mixed $data Данные для передачи
|
|
|
|
|
* @param array|null $options Параметры Guzzle
|
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-05-29 17:58:40 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
|
|
|
|
|
*/
|
|
|
|
|
protected function sendAtolRequest(string $http_method, string $api_method, $data = null, array $options = null)
|
|
|
|
|
{
|
|
|
|
|
$http_method = strtoupper($http_method);
|
|
|
|
|
$options['headers'] = $this->getHeaders();
|
|
|
|
|
$url = $http_method == 'GET'
|
|
|
|
|
? $this->makeUrl($api_method, $data)
|
|
|
|
|
: $this->makeUrl($api_method, ['token' => $this->getAuthToken()]);
|
|
|
|
|
if ($http_method != 'GET') {
|
|
|
|
|
$options['json'] = $data;
|
|
|
|
|
}
|
|
|
|
|
$response = $this->request($http_method, $url, $options);
|
|
|
|
|
return $this->last_response = new KktResponse($response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Производит авторизацию на ККТ и получает токен доступа для дальнейших HTTP-запросов
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
2020-05-29 17:58:40 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
|
|
|
|
protected function auth()
|
|
|
|
|
{
|
|
|
|
|
if (!$this->getAuthToken()) {
|
|
|
|
|
$result = $this->sendAtolRequest('GET', 'getToken', [
|
|
|
|
|
'login' => $this->getLogin(),
|
|
|
|
|
'pass' => $this->getPassword(),
|
|
|
|
|
]);
|
|
|
|
|
if (!$result->isValid() || !$result->getContent()->token) {
|
2020-06-07 11:36:52 +00:00
|
|
|
|
throw new AtolAuthFailedException($result);
|
2020-01-11 06:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
$this->auth_token = $result->getContent()->token;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Отправляет документ на регистрацию
|
|
|
|
|
*
|
2020-05-29 20:29:44 +00:00
|
|
|
|
* @param string $api_method Метод API
|
|
|
|
|
* @param string $type Тип документа: receipt, correction
|
|
|
|
|
* @param \AtolOnline\Entities\Document $document Объект документа
|
|
|
|
|
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
* @return \AtolOnline\Api\KktResponse
|
2020-06-07 11:36:52 +00:00
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
|
|
|
|
|
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
|
2020-05-29 20:29:44 +00:00
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
2020-01-11 06:30:25 +00:00
|
|
|
|
*/
|
2020-05-29 20:29:44 +00:00
|
|
|
|
protected function registerDocument(string $api_method, string $type, Document $document, ?string $external_id = null)
|
2020-01-11 06:30:25 +00:00
|
|
|
|
{
|
|
|
|
|
$type = trim($type);
|
|
|
|
|
if (!in_array($type, ['receipt', 'correction'])) {
|
|
|
|
|
throw new AtolWrongDocumentTypeException($type);
|
|
|
|
|
}
|
|
|
|
|
$this->auth();
|
2020-06-04 13:39:11 +00:00
|
|
|
|
if ($this->isTestMode()) {
|
|
|
|
|
$document->setCompany(($document->getCompany() ?: new Company())
|
|
|
|
|
->setInn(TestEnvParams::INN)
|
|
|
|
|
->setSno(TestEnvParams::SNO)
|
|
|
|
|
->setPaymentAddress(TestEnvParams::PAYMENT_ADDRESS));
|
|
|
|
|
}
|
2020-05-29 20:29:44 +00:00
|
|
|
|
$data['timestamp'] = date('d.m.y H:i:s');
|
|
|
|
|
$data['external_id'] = $external_id ?: Uuid::uuid4()->toString();
|
|
|
|
|
$data[$type] = $document;
|
|
|
|
|
if ($this->getCallbackUrl()) {
|
|
|
|
|
$data['service'] = ['callback_url' => $this->getCallbackUrl()];
|
|
|
|
|
}
|
2020-01-11 06:30:25 +00:00
|
|
|
|
return $this->sendAtolRequest('POST', trim($api_method), $data);
|
|
|
|
|
}
|
|
|
|
|
}
|