4 Commits

6 changed files with 163 additions and 33 deletions

View File

@@ -52,9 +52,14 @@ $kkt->getGroup();
## Тестовый режим ## Тестовый режим
На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе. На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе.
АТОЛ предоставляет нам отдельную тестовую ККТ. АТОЛ предоставляет нам отдельную тестовую среду (ККТ).
[Её настройки](https://online.atol.ru/files/ffd/test_sreda.txt) уже указаны в коде библиотеки. [Её настройки](https://online.atol.ru/files/ffd/test_sreda.txt) уже указаны в коде библиотеки.
*Под тестовым режимом работы подразумевается использование этой тестовой ККТ.* *Под тестовым режимом работы подразумевается использование другой (тестовой) ККТ.*
При включенном тестовом режиме:
* меняется логин, пароль и группа (для обращения на тестовую ККТ)
* между авторизацией и операцией над документом, в `Company` документа переопределяется ИНН, СНО и адрес места
расчётов на те, что указаны в [параметрах тестовой среды](https://online.atol.ru/files/ffd/test_sreda.txt).
В библиотеке есть переключатель настроек ККТ. В библиотеке есть переключатель настроек ККТ.
С его помощью можете поменять вашу боевую ККТ на тестовую и наоборот. С его помощью можете поменять вашу боевую ККТ на тестовую и наоборот.
@@ -70,13 +75,43 @@ $kkt->setTestMode(false); // выключить
> Если вы включили тестовый режим (как показано выше), то используются именно эта ККТ, а не ваша. > Если вы включили тестовый режим (как показано выше), то используются именно эта ККТ, а не ваша.
> После выключения тестового режима настройки доступа к ККТ меняются на ваши (используется уже ваша ККТ). > После выключения тестового режима настройки доступа к ККТ меняются на ваши (используется уже ваша ККТ).
Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом режиме (на собственной ККТ). Для включения тестового режима необязательно задавать параметры боевой ККТ.
Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом
режиме (на собственной ККТ).
В этом случае важно понимать следующее: В этом случае важно понимать следующее:
1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**; 1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**;
2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html); 2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html);
3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах; 3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах;
4. вся ответственность за пп. 1-3 и последствия ложится только на вас. 4. вся ответственность за пп. 1-3 и последствия ложится только на вас.
## Авторизация ККТ
Перед первым запросом на ККТ происходит авторизация на сервере по логину и паролю.
В ответ приходит авторизационный токен, срок жизни коего равен 24 часам.
После первой успешной операции возможно получить этот токен следующим образом:
```php
$kkt->getAuthToken(); // вернёт строку длиной 128 символа
```
Этот токен можно сохранить и переиспользовать в течение всего срока его жизни.
Спустя это время следует получить новый токен.
Однажды полученный токен, то для дальнейшего использования следует указывать его следующим образом:
```php
$kkt->setAuthToken($token_string);
```
Если токен был установлен перед выполнением операции, то при выполнении операции будет использоваться именно он, а новый
запрашиваться не будет. Если операция завершится ошибочно из-за истёкшего токена, следует повторить операцию без
использования метода `setAuthToken()`, либо обнулив его следующим образом:
```php
$kkt->setAuthToken(null);
```
## Регистрация документа ## Регистрация документа
Для регистрации документа **прихода** необходимо вызвать метод `sell()`: Для регистрации документа **прихода** необходимо вызвать метод `sell()`:
@@ -106,23 +141,44 @@ $result = $kkt->buyRefund($document);
Для операций, перечисленных выше, документы не должны содержать [данных коррекции](/docs/documents.md#correction). Для операций, перечисленных выше, документы не должны содержать [данных коррекции](/docs/documents.md#correction).
Тогда как для операций коррекции, которые описаны ниже, эти данные должны присутствовать. Тогда как для операций коррекции, которые описаны ниже, эти данные должны присутствовать.
Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellRefund()`: Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellCorrection()`:
```php ```php
$result = $kkt->sellCorrection($document); $result = $kkt->sellCorrection($document);
``` ```
Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyRefund()`: Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyCorrection()`:
```php ```php
$result = $kkt->buyCorrection($document); $result = $kkt->buyCorrection($document);
``` ```
### Собственный идентификатор документа
Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки.
По умолчанию это UUID версии 4.
Чтобы использовать собственный идентификатор, следует передать нужное строковое значение вторым параметром в любой из
шести описанных выше методов, например:
```php
$kkt->sell($document, $my_unique_id);
$kkt->sellRefund($document, $my_unique_id);
$kkt->buy($document, $my_unique_id);
$kkt->buyRefund($document, $my_unique_id);
$kkt->sellCorrection($document, $my_unique_id);
$kkt->buyCorrection($document, $my_unique_id);
```
Если `$my_unique_id` равен `null` или пустой строке, то будет сгенерирован новый UUID.
Узнать его можно будет только в ответе от ККТ.
### Передача callback_url ### Передача callback_url
Перед регистрацией документа можно указать `callback_url`. Перед регистрацией документа можно указать `callback_url`.
АТОЛ отправит на указанный URL результат регистрации. АТОЛ отправит на указанный URL результат регистрации.
Вам необходимо расположить по этому адресу скрипт, обрабатывающий этот результат. По этому адресу должен располагаться код, который будет обрабатывать этот результат.
```php ```php
$kkt->setCallbackUrl('http://example.com/process-kkt-result'); $kkt->setCallbackUrl('http://example.com/process-kkt-result');
@@ -158,7 +214,7 @@ $err_text = $result->error->text;
Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`: Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`:
```php ```php
$kkt->isValid(); // вернёт true, если ошибок нет $kkt->getLastResponse()->isValid(); // вернёт true, если ошибок нет
``` ```
## Проверка статуса документа ## Проверка статуса документа

View File

@@ -9,7 +9,9 @@
namespace AtolOnline\Api; namespace AtolOnline\Api;
use AtolOnline\{Entities\Document, use AtolOnline\{Constants\TestEnvParams,
Entities\Company,
Entities\Document,
Exceptions\AtolCorrectionInfoException, Exceptions\AtolCorrectionInfoException,
Exceptions\AtolInvalidUuidException, Exceptions\AtolInvalidUuidException,
Exceptions\AtolKktLoginEmptyException, Exceptions\AtolKktLoginEmptyException,
@@ -222,104 +224,115 @@ class Kkt extends Client
* Регистрирует документ прихода * Регистрирует документ прихода
* *
* @param \AtolOnline\Entities\Document $document * @param \AtolOnline\Entities\Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
* @throws \GuzzleHttp\Exception\GuzzleException
*/ */
public function sell(Document $document) public function sell(Document $document, ?string $external_id = null)
{ {
if ($document->getCorrectionInfo()) { if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции'); throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
} }
return $this->registerDocument('sell', 'receipt', $document); return $this->registerDocument('sell', 'receipt', $document, $external_id);
} }
/** /**
* Регистрирует документ возврата прихода * Регистрирует документ возврата прихода
* *
* @param \AtolOnline\Entities\Document $document * @param \AtolOnline\Entities\Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
*/ */
public function sellRefund(Document $document) public function sellRefund(Document $document, ?string $external_id = null)
{ {
if ($document->getCorrectionInfo()) { if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции'); throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
} }
return $this->registerDocument('sell_refund', 'receipt', $document->clearVats()); return $this->registerDocument('sell_refund', 'receipt', $document->clearVats(), $external_id);
} }
/** /**
* Регистрирует документ коррекции прихода * Регистрирует документ коррекции прихода
* *
* @param \AtolOnline\Entities\Document $document * @param \AtolOnline\Entities\Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
* @throws \GuzzleHttp\Exception\GuzzleException
*/ */
public function sellCorrection(Document $document) public function sellCorrection(Document $document, ?string $external_id = null)
{ {
if (!$document->getCorrectionInfo()) { if (!$document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException(); throw new AtolCorrectionInfoException();
} }
$document->setClient(null)->setItems([]); $document->setClient(null)->setItems([]);
return $this->registerDocument('sell_correction', 'correction', $document); return $this->registerDocument('sell_correction', 'correction', $document, $external_id);
} }
/** /**
* Регистрирует документ расхода * Регистрирует документ расхода
* *
* @param \AtolOnline\Entities\Document $document * @param \AtolOnline\Entities\Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
* @throws \GuzzleHttp\Exception\GuzzleException
*/ */
public function buy(Document $document) public function buy(Document $document, ?string $external_id = null)
{ {
if ($document->getCorrectionInfo()) { if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции'); throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
} }
return $this->registerDocument('buy', 'receipt', $document); return $this->registerDocument('buy', 'receipt', $document, $external_id);
} }
/** /**
* Регистрирует документ возврата расхода * Регистрирует документ возврата расхода
* *
* @param \AtolOnline\Entities\Document $document * @param \AtolOnline\Entities\Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
* @throws \GuzzleHttp\Exception\GuzzleException
*/ */
public function buyRefund(Document $document) public function buyRefund(Document $document, ?string $external_id = null)
{ {
if ($document->getCorrectionInfo()) { if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции'); throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
} }
return $this->registerDocument('buy_refund', 'receipt', $document->clearVats()); return $this->registerDocument('buy_refund', 'receipt', $document->clearVats(), $external_id);
} }
/** /**
* Регистрирует документ коррекции расхода * Регистрирует документ коррекции расхода
* *
* @param Document $document * @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse * @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
* @throws \GuzzleHttp\Exception\GuzzleException
*/ */
public function buyCorrection(Document $document) public function buyCorrection(Document $document, ?string $external_id = null)
{ {
if (!$document->getCorrectionInfo()) { if (!$document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException(); throw new AtolCorrectionInfoException();
} }
$document->setClient(null)->setItems([]); $document->setClient(null)->setItems([]);
return $this->registerDocument('buy_correction', 'correction', $document); return $this->registerDocument('buy_correction', 'correction', $document, $external_id);
} }
/** /**
@@ -396,9 +409,9 @@ class Kkt extends Client
$this->kkt_config['prod']['pass'] = ''; $this->kkt_config['prod']['pass'] = '';
$this->kkt_config['prod']['url'] = 'https://online.atol.ru/possystem/v4'; $this->kkt_config['prod']['url'] = 'https://online.atol.ru/possystem/v4';
$this->kkt_config['prod']['callback_url'] = ''; $this->kkt_config['prod']['callback_url'] = '';
$this->kkt_config['test']['group'] = 'v4-online-atol-ru_4179'; $this->kkt_config['test']['group'] = TestEnvParams::GROUP;
$this->kkt_config['test']['login'] = 'v4-online-atol-ru'; $this->kkt_config['test']['login'] = TestEnvParams::LOGIN;
$this->kkt_config['test']['pass'] = 'iGFFuihss'; $this->kkt_config['test']['pass'] = TestEnvParams::PASSWORD;
$this->kkt_config['test']['url'] = 'https://testonline.atol.ru/possystem/v4'; $this->kkt_config['test']['url'] = 'https://testonline.atol.ru/possystem/v4';
$this->kkt_config['test']['callback_url'] = ''; $this->kkt_config['test']['callback_url'] = '';
} }
@@ -507,6 +520,12 @@ class Kkt extends Client
throw new AtolWrongDocumentTypeException($type); throw new AtolWrongDocumentTypeException($type);
} }
$this->auth(); $this->auth();
if ($this->isTestMode()) {
$document->setCompany(($document->getCompany() ?: new Company())
->setInn(TestEnvParams::INN)
->setSno(TestEnvParams::SNO)
->setPaymentAddress(TestEnvParams::PAYMENT_ADDRESS));
}
$data['timestamp'] = date('d.m.y H:i:s'); $data['timestamp'] = date('d.m.y H:i:s');
$data['external_id'] = $external_id ?: Uuid::uuid4()->toString(); $data['external_id'] = $external_id ?: Uuid::uuid4()->toString();
$data[$type] = $document; $data[$type] = $document;

View File

@@ -0,0 +1,49 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Constants;
/**
* Константы, определяющие параметры тестовой среды
*
* @see https://online.atol.ru/files/ffd/test_sreda.txt
* @package AtolOnline\Constants
*/
class TestEnvParams
{
/**
* Логин
*/
const LOGIN = 'v4-online-atol-ru';
/**
* Пароль
*/
const PASSWORD = 'iGFFuihss';
/**
* Группа
*/
const GROUP = 'v4-online-atol-ru_4179';
/**
* Система налогообложения
*/
const SNO = SnoTypes::OSN;
/**
* ИНН
*/
const INN = '5544332219';
/**
* Адрес места расчётов
*/
const PAYMENT_ADDRESS = 'https://v4.online.atol.ru';
}

View File

@@ -106,7 +106,7 @@ class ItemArray extends Entity
protected function validateCount(?array $items = null): bool protected function validateCount(?array $items = null): bool
{ {
if ((!empty($items) && count($items) >= self::MAX_COUNT) || count($this->items) >= self::MAX_COUNT) { if ((!empty($items) && count($items) >= self::MAX_COUNT) || count($this->items) >= self::MAX_COUNT) {
throw new AtolTooManyItemsException(self::MAX_COUNT); throw new AtolTooManyItemsException(count($items), self::MAX_COUNT);
} }
return true; return true;
} }

View File

@@ -9,7 +9,6 @@
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Api\SellSchema;
use AtolOnline\Exceptions\AtolTooManyPaymentsException; use AtolOnline\Exceptions\AtolTooManyPaymentsException;
/** /**
@@ -19,6 +18,11 @@ use AtolOnline\Exceptions\AtolTooManyPaymentsException;
*/ */
class PaymentArray extends Entity class PaymentArray extends Entity
{ {
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 10;
/** /**
* @var Payment[] Массив оплат * @var Payment[] Массив оплат
*/ */
@@ -99,9 +103,8 @@ class PaymentArray extends Entity
*/ */
protected function validateCount(?array $payments = null): bool protected function validateCount(?array $payments = null): bool
{ {
$max_items = SellSchema::get()->properties->receipt->properties->payments->maxItems; if ((!empty($payments) && count($payments) >= self::MAX_COUNT) || count($this->payments) >= self::MAX_COUNT) {
if ((!empty($payments) && count($payments) >= $max_items) || count($this->payments) >= $max_items) { throw new AtolTooManyPaymentsException(count($payments), self::MAX_COUNT);
throw new AtolTooManyPaymentsException($max_items);
} }
return true; return true;
} }

View File

@@ -9,7 +9,6 @@
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Api\SellSchema;
use AtolOnline\Exceptions\AtolTooManyVatsException; use AtolOnline\Exceptions\AtolTooManyVatsException;
/** /**
@@ -19,6 +18,11 @@ use AtolOnline\Exceptions\AtolTooManyVatsException;
*/ */
class VatArray extends Entity class VatArray extends Entity
{ {
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 6;
/** /**
* @var Vat[] Массив ставок НДС * @var Vat[] Массив ставок НДС
*/ */
@@ -103,9 +107,8 @@ class VatArray extends Entity
*/ */
protected function validateCount(?array $vats = null): bool protected function validateCount(?array $vats = null): bool
{ {
$max_items = SellSchema::get()->properties->receipt->properties->vats->maxItems; if ((!empty($vats) && count($vats) >= self::MAX_COUNT) || count($this->vats) >= self::MAX_COUNT) {
if ((!empty($vats) && count($vats) >= $max_items) || count($this->vats) >= $max_items) { throw new AtolTooManyVatsException(count($vats), self::MAX_COUNT);
throw new AtolTooManyVatsException(count($vats), $max_items);
} }
return true; return true;
} }