38 Commits

Author SHA1 Message Date
6551366d84 Полное покрытие тестами классов AtolClient + KktMonitor + половины исключений
Часть тестов завязаны на тестовый API мониторинга Атола. Иногда он закашливается и не отвечает, возможно, там рейтлимит. Да и пофиг, моки -- злейшее зло, и мне лень их писать.
2021-11-19 18:42:14 +08:00
2c5144caac KktMonitor::getAll() теперь возвращает коллекцию объектов ККТ
При этом остаётся возможность получить полный ответ через KktMonitor::getResponse()
2021-11-19 18:29:09 +08:00
92a2c6cc48 Переработаны существующие и дописаны новые тест AtolClient
Используется тестовый API АТОЛ Онлайн. 94% покрытие AtolClient, но ещё не закончено
2021-11-19 13:42:51 +08:00
949b31a85a [WIP] Начало работы над тестом KktMonitorTest 2021-11-18 19:07:32 +08:00
03591600dd Поддержка мониторинга (#8) и рефакторинг
- абстрактный класс AtolClient:
    - больше не наследуется от клиента guzzle, но содержит его объект
    - из Kkt вынесены методы, отвечающие за формирование запроса, отправку и получение ответа, в т.ч. авторизацию
- переименованы исключения TooLongKktLoginException, TooLongKktPasswordException, EmptyKktLoginException и EmptyKktPasswordException
- мелочи по AuthFailedException
- заготовки тестов AtolClient и KktMonitor
2021-11-18 12:24:44 +08:00
77481884ad Начало работы по #5 и #6
- строгая типизация
- переработан класс `TestEnvParams`:
    - вынесен на уровень выше из под `AtolOnline\Constants`
    - вместо констант - две функции для получения актуальных параметров подключения по ФФД1.05 и ФФД1.2
- актуализированы `PaymentObjects` согласно #5
- исходники вынесены не уровень выше в `src`
- константы теперь enum через `myclabs/php-enum`
- новые константы `DocumentTypes`
- классы констант финализированы
- все исключения переименованы, а многие так или иначе отрефакторены (не полностью)
- новые исключения `InvalidSnoException`, `InvalidPaymentAddressException`
- `helpers.php` стал полноценным классом `Helpers`
- удалены трейты `HasEmail`, `HasInn`, `RublesKopeksConverter` (конвертация перенесена в `Helpers`)
- удалён хелпер `valid_strlen()`, вместо него теперь везде `mb_strlen()`
- сущности `Client` и `Company` получили свои имплементации для `email` и `inn`
- доработки в `BasicTestCase`
- полное покрытие тестами: `Client`, `Company`, `Helpers`
- поправлен `phpunit.xml`
- везде обновлены копирайты
- актуализированы и исправлены phpdoc, return types
- начато введение `strict_types=1`
- минимальный php теперь 8.0
- обновлены все зависимости
- подключен пакет коллекций laravel для будущего использования
- теперь можно `composer test` и `composer test-cov`
2021-11-18 12:24:30 +08:00
cc944a1dbb Бейджи в README 2021-05-25 11:48:37 +08:00
bb05f0c752 Merge branch 'master' into dev 2021-05-25 00:58:53 +08:00
1c76608468 Github Actions разграничены для веток master и dev 2021-05-24 23:40:46 +08:00
bd6e208216 Обновление composer.json и первичных текстовок 2021-05-24 23:00:34 +08:00
7b4411ec01 Обновление зависимостей 2021-05-24 22:45:03 +08:00
23fa1f7eb9 Update FUNDING.yml 2021-05-24 12:38:39 +08:00
929bf84c97 Merge pull request #3 from komantnick/develop
Исправление багов при отправке сырого JSON для чека коррекции с исправлениями
2021-05-24 10:55:09 +08:00
Nikita Saiapin
5c1c4dba12 Фикс размера НДС 2021-05-23 22:35:45 +08:00
Nikita Saiapin
4c40bebe14 Исправление багов при отправке сырого JSON чека коррекции 2021-05-22 21:48:12 +08:00
d321205ac9 Фикс выброса AtolAuthFailedException 2020-10-13 02:06:13 +08:00
387e6e445f Удалён сервер discord и его упоминание в README 2020-06-14 13:52:07 +08:00
3bd043bde7 Kkt::setCallbackUrl() - Фикс проверки callback_url по регулярке 2020-06-14 13:44:35 +08:00
7c8ee85b89 Фикс регулярки callback_url 2020-06-14 13:44:04 +08:00
aab68646e6 Фикс сообщений в исключениях 2020-06-14 13:43:32 +08:00
e1fb74ac01 Доудалено упоминание о Schemas 2020-06-07 20:28:14 +08:00
8d9f218280 Мелкофиксы по документации и roadmap 2020-06-07 20:12:50 +08:00
9ed999f9fc Улучшен класс Kkt
+ setCallbackUrl(): валидация
* setLogin() и setPassword() более не зависят от тестового режима
* setPassword(): выброс AtolKktPasswordTooLongException
* auth(): выброс AtolAuthFailedException
* valid_strlen()
* Constraints::MAX_LENGTH_LOGIN
* Constraints::MAX_LENGTH_PASSWORD
* Constraints::PATTERN_CALLBACK_URL
* phpdoc
* исправление опечаток при выбросе некоторых исключений
2020-06-07 20:12:50 +08:00
989c06a383 Улучшен класс Item
* valid_strlen()
* Constraints::MAX_LENGTH_ITEM_NAME
* Constraints::MAX_LENGTH_MEASUREMENT_UNIT
* Constraints::MAX_LENGTH_USER_DATA
2020-06-07 20:12:23 +08:00
9bd99c81a9 Улучшен класс Document
* валидация в setCashier() только если передан не-null
* valid_strlen()
* Constraints::MAX_LENGTH_CASHIER_NAME
* phpdoc
2020-06-07 20:12:23 +08:00
2943d93962 Улучшен класс Company
* valid_strlen()
* Constraints::MAX_LENGTH_PAYMENT_ADDRESS
* phpdoc
2020-06-07 20:12:23 +08:00
8eb309bc58 Улучшен класс Сlient
* valid_strlen()
* Constraints::MAX_LENGTH_CLIENT_NAME
* Constraints::MAX_LENGTH_CLIENT_PHONE
* phpdoc
2020-06-07 20:12:23 +08:00
b74f652127 Улучшен trait HasInn
* Constraints::PATTERN_INN
* phpdoc
2020-06-07 20:12:23 +08:00
1061914a5f Улучшен trait HasEmail
* valid_strlen()
* Constraints::MAX_LENGTH_EMAIL
* phpdoc
2020-06-07 20:12:23 +08:00
5424726a97 Мелкофиксы в исключениях
* использование valid_strlen() в AtolInnWrongLengthException и AtolTooLongException
* фикс опечатки в AtolEmailTooLongException
2020-06-07 20:12:23 +08:00
2b3713db69 Новые исключения
+ AtolAuthFailedException
+ AtolCallbackUrlTooLongException
+ AtolInvalidCallbackUrlException
+ AtolKktPasswordTooLongException
2020-06-07 20:12:23 +08:00
a6b57115b6 Новый класс Constraints 2020-06-07 20:12:23 +08:00
12e0e49c9b Удалёно всё, что связано со схемами 2020-06-07 20:12:22 +08:00
3ffab562f8 Новый файл helpers 2020-06-07 20:12:22 +08:00
c05e013a5a Освежил документацию в части работы с объектом Kkt 2020-06-04 22:29:36 +08:00
e282de7e08 Вернул назад переопределение параметров компании при тестовом режиме: ИНН, адрес места расчётов
Также появился новый класс с константами тестовых параметров
2020-06-04 22:12:34 +08:00
c14b680be4 Проброс external_id на регистрацию документа 2020-06-01 04:12:23 +08:00
7558cb6638 Убрал схему Атола валидации количества НДС, оплат и предметов 2020-06-01 03:58:31 +08:00
92 changed files with 5796 additions and 2717 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
custom: ['https://money.yandex.ru/to/41001685237530'] custom: ['https://yoomoney.ru/to/41001685237530']

25
.github/workflows/dev.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Dev build
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
jobs:
Tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
uses: php-actions/composer@40-env
with:
version: 2
php_version: 8.0
only_args: --prefer-dist --no-progress
- name: Run phpunit tests
uses: php-actions/phpunit@v9
with:
configuration: ./phpunit.xml

25
.github/workflows/master.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Master build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
Tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
uses: php-actions/composer@40-env
with:
version: 2
php_version: 7.4
only_args: --prefer-dist --no-progress
- name: Run phpunit tests
uses: php-actions/phpunit@v8
with:
configuration: ./phpunit.xml

View File

@@ -1,26 +0,0 @@
name: Build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install composer dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run phpunit tests
uses: php-actions/phpunit@v1.0.0
with:
# Configuration file location
config: ./phpunit.xml

View File

@@ -1,6 +1,7 @@
# АТОЛ Онлайн # АТОЛ Онлайн
![Build](https://github.com/anthonyaxenov/atol-online/workflows/Build/badge.svg?branch=master) [![Master build](https://github.com/anthonyaxenov/atol-online/actions/workflows/master.yml/badge.svg)](https://github.com/anthonyaxenov/atol-online/actions/workflows/master.yml)
[![Dev build](https://github.com/anthonyaxenov/atol-online/actions/workflows/dev.yml/badge.svg)](https://github.com/anthonyaxenov/atol-online/actions/workflows/dev.yml)
Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/). Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/).
@@ -10,7 +11,7 @@
## Системные требования ## Системные требования
* PHP 7.2+ * PHP 7.4+
* composer * composer
* php-json * php-json
@@ -103,8 +104,6 @@
## Дополнительные ресурсы ## Дополнительные ресурсы
**Discord-сервер для обсуждения этой библиотеки: [discord.gg/mFYTQmp](https://discord.gg/mFYTQmp)**
Функционал, находящийся в разработке: [ROADMAP.md](ROADMAP.md) Функционал, находящийся в разработке: [ROADMAP.md](ROADMAP.md)
Официальные ресурсы АТОЛ Онлайн: Официальные ресурсы АТОЛ Онлайн:

View File

@@ -25,9 +25,8 @@
- [ ] Тесты для регистрации документа расхода - [ ] Тесты для регистрации документа расхода
- [ ] Тесты для регистрации документа возврата расхода - [ ] Тесты для регистрации документа возврата расхода
- [ ] Тесты для регистрации документа коррекции расхода - [ ] Тесты для регистрации документа коррекции расхода
- [ ] Вообще все расчёты вообще везде должны быть строго в копейках. Рубли (дроби) должны быть только в JSON-представлениях - [ ] Вообще все расчёты вообще везде должны быть строго в копейках.
- [ ] Валидатор схемы для документов прихода, возврата прихода, расхода, возврата расхода Рубли (дроби) должны быть только в JSON-представлениях
- [ ] Валидатор схемы для документов коррекции прихода, коррекции расхода
## Поддержка методов API (регистрация документов) ## Поддержка методов API (регистрация документов)

View File

@@ -5,16 +5,21 @@
"type": "library", "type": "library",
"keywords": [ "keywords": [
"54-fz", "54-fz",
"54-фз",
"kkt", "kkt",
"ккт",
"e-commerce", "e-commerce",
"cash", "cash",
"cash register", "cash register",
"payment", "payment",
"payment system", "payment system",
"atol", "atol",
"atol-online" "атол",
"atol-online",
"атол онлайн"
], ],
"homepage": "https://github.com/anthonyaxenov/atol-online", "homepage": "https://github.com/anthonyaxenov/atol-online",
"readme": "https://github.com/anthonyaxenov/atol-online/blob/master/README.md",
"authors": [ "authors": [
{ {
"name": "Anthony Axenov", "name": "Anthony Axenov",
@@ -25,27 +30,39 @@
"support": { "support": {
"source": "https://github.com/anthonyaxenov/atol-online", "source": "https://github.com/anthonyaxenov/atol-online",
"issues": "https://github.com/anthonyaxenov/atol-online/issues", "issues": "https://github.com/anthonyaxenov/atol-online/issues",
"chat": "https://discord.gg/mFYTQmp" "docs": "https://github.com/anthonyaxenov/atol-online/blob/master/docs/readme.md"
}, },
"funding": [
{
"type": "Yoomoney",
"url": "https://yoomoney.ru/to/41001685237530"
}
],
"require": { "require": {
"php": ">=7.2", "php": ">=8.0",
"ext-json": "*", "ext-json": "*",
"guzzlehttp/guzzle": "^6.5", "ext-mbstring": "*",
"psr/log": "^1.1", "guzzlehttp/guzzle": "^7.4",
"ramsey/uuid": "^3.9" "psr/log": "^3",
"ramsey/uuid": "^4.2",
"myclabs/php-enum": "^1.8",
"illuminate/collections": "^8.70"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8.5" "phpunit/phpunit": "^9.5"
}, },
"autoload": { "autoload": {
"classmap": [ "psr-4": {
"src/AtolOnline/Api/", "AtolOnline\\": "src/"
"src/AtolOnline/Api/Schemas/", }
"src/AtolOnline/Exceptions/", },
"src/AtolOnline/Entities/", "autoload-dev": {
"src/AtolOnline/Traits/", "psr-4": {
"src/AtolOnline/Constants/", "AtolOnlineTests\\": "tests/"
"tests/" }
] },
"scripts": {
"test": "vendor/bin/phpunit --colors=always",
"test-cov": "vendor/bin/phpunit --coverage-html coverage"
} }
} }

2366
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,10 +46,10 @@ $customer = (new AtolOnline\Entities\Client())
* `AtolEmailValidateException` (если email невалиден). * `AtolEmailValidateException` (если email невалиден).
Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр). Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр).
Выбрасывает исключение `AtolInnWrongLengthException` (если длина строка ИНН некорректна). Выбрасывает исключение `AtolInnWrongLengthException` (если длина ИНН некорректна).
Метод `setName()` проверяет входную строку на длину (до 256 символов). Метод `setName()` проверяет входную строку на длину (до 256 символов).
Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное наименование). Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное имя).
Метод `setPhone()` чистит входную строку от всех символов, кроме цифр и знака `+`, и проверяет длину (до 64 символов). Метод `setPhone()` чистит входную строку от всех символов, кроме цифр и знака `+`, и проверяет длину (до 64 символов).
Выбрасывает исключение `AtolPhoneTooLongException` (если слишком длинный номер телефона). Выбрасывает исключение `AtolPhoneTooLongException` (если слишком длинный номер телефона).

View File

@@ -46,7 +46,7 @@ $company = (new AtolOnline\Entities\Company())
* `AtolEmailValidateException` (если email невалиден). * `AtolEmailValidateException` (если email невалиден).
Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр). Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр).
Выбрасывает исключение `AtolInnWrongLengthException` (если длина строка ИНН некорректна). Выбрасывает исключение `AtolInnWrongLengthException` (если длина ИНН некорректна).
Метод `setPaymentAddress()` проверяет длину (до 256 символов). Метод `setPaymentAddress()` проверяет длину (до 256 символов).
Выбрасывает исключение `AtolPaymentAddressTooLongException` (если слишком длинный адрес места расчётов). Выбрасывает исключение `AtolPaymentAddressTooLongException` (если слишком длинный адрес места расчётов).

View File

@@ -164,4 +164,4 @@ $json_array = $doc->jsonSerialize();
--- ---
[Вернуться к содержанию](readme.md) [Вернуться к содержанию](readme.md)

View File

@@ -49,12 +49,18 @@ $kkt->getGroup();
Эти параметры нужно задать [объекту компании](/docs/company.md), который будет передаваться в документах через эту ККТ. Эти параметры нужно задать [объекту компании](/docs/company.md), который будет передаваться в документах через эту ККТ.
<a name='testmode'></a>
## Тестовый режим ## Тестовый режим
На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе. На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе.
АТОЛ предоставляет нам отдельную тестовую ККТ. АТОЛ предоставляет нам отдельную тестовую среду (ККТ).
[Её настройки](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 +76,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,29 +142,64 @@ $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);
``` ```
Любой из перечисленных выше шести методов может выбросить исключение `AtolAuthFailedException` при ошибке
аутентификации или авторизации.
### Собственный идентификатор документа
Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки.
По умолчанию это 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');
$kkt->getCallbackUrl(); $kkt->getCallbackUrl();
``` ```
Метод `setCallbackUrl()` проверяет входную строку на длину (до 256 символов) и валидность формата url по
регулярному выражению:
```
^http(s?)\:\/\/[0-9a-zA-Zа-яА-Я]([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-\.\?\,\'\/\\\+&=%$#_]*)?$
```
Выбрасывает исключения:
* `AtolCallbackUrlTooLongException` (если слишком длинный url);
* `AtolInvalidCallbackUrlException` (если url невалиден).
## Обработка результата регистрации ## Обработка результата регистрации
Методы `sell()`, `sellRefund()`, `sellCorrection()`, `buy()`, `buyRefund()` и `buyCorrection()` возвращают объект `AtolOnline\Api\KktResponse`. Методы `sell()`, `sellRefund()`, `sellCorrection()`, `buy()`, `buyRefund()` и `buyCorrection()` возвращают объект `AtolOnline\Api\KktResponse`.
@@ -138,7 +209,7 @@ $kkt->getCallbackUrl();
Этот объект содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа. Этот объект содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа.
```php ```php
$result = $kkt->getLastResponse(); $result = $kkt->getLastResponse(); // вернёт последний ответ от API
$headers = $result->getHeaders(); // вернёт заголовки $headers = $result->getHeaders(); // вернёт заголовки
$code = $result->getCode(); // вернёт код ответа $code = $result->getCode(); // вернёт код ответа
$body = $result->getContent(); // вернёт JSON-декодированное тело ответа $body = $result->getContent(); // вернёт JSON-декодированное тело ответа
@@ -158,7 +229,7 @@ $err_text = $result->error->text;
Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`: Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`:
```php ```php
$kkt->isValid(); // вернёт true, если ошибок нет $kkt->getLastResponse()->isValid(); // вернёт true, если ошибок нет
``` ```
## Проверка статуса документа ## Проверка статуса документа

View File

@@ -1,4 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
~
~ This code is licensed under MIT.
~ Этот код распространяется по лицензии MIT.
~ https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
-->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false" backupGlobals="false"
@@ -11,15 +19,14 @@
processIsolation="false" processIsolation="false"
stopOnFailure="false"> stopOnFailure="false">
<testsuites> <testsuites>
<testsuite name="Unit"> <testsuite name="All">
<file>ClientTest.php</file> <directory suffix="Test.php">./tests</directory>
<file>CompanyTest.php</file>
<file>VatTest.php</file>
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<file>ItemTest.php</file>
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
</phpunit> </phpunit>

301
src/Api/AtolClient.php Normal file
View File

@@ -0,0 +1,301 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Api;
use AtolOnline\{
Constants\Constraints,
Exceptions\AuthFailedException,
Exceptions\EmptyLoginException,
Exceptions\EmptyPasswordException,
Exceptions\TooLongLoginException,
Exceptions\TooLongPasswordException};
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
/**
* Класс для подключения АТОЛ Онлайн API
*/
abstract class AtolClient
{
/**
* @var bool Флаг тестового режима
*/
protected bool $test_mode;
/**
* @var Client HTTP-клиент для работы с API
*/
protected Client $http;
/**
* @var string|null Логин доступа к API
*/
private ?string $login = null;
/**
* @var string|null Пароль доступа к API (readonly)
*/
private ?string $password = null;
/**
* @var string|null Токен авторизации
*/
private ?string $token = null;
/**
* @var KktResponse|null Последний ответ сервера АТОЛ
*/
private ?KktResponse $response;
/**
* Конструктор
*
* @param bool $test_mode
* @param string|null $login
* @param string|null $password
* @param array $config
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html Допустимые параметры для $config
*/
public function __construct(
bool $test_mode = true,
?string $login = null,
?string $password = null,
array $config = []
) {
$this->http = new Client(array_merge($config, [
'http_errors' => $config['http_errors'] ?? false,
]));
$this->setTestMode($test_mode);
!is_null($login) && $this->setLogin($login);
!is_null($password) && $this->setPassword($password);
}
/**
* Возвращает установленный флаг тестового режима
*
* @return bool
*/
public function isTestMode(): bool
{
return $this->test_mode;
}
/**
* Устанавливает флаг тестового режима
*
* @param bool $test_mode
* @return $this
*/
public function setTestMode(bool $test_mode = true): self
{
$this->test_mode = $test_mode;
return $this;
}
/**
* Возвращает текущий токен авторизации
*
* @return string|null
*/
public function getToken(): ?string
{
return $this->token;
}
/**
* Устанавливает токен авторизации
*
* @param string|null $token
* @return $this
*/
public function setToken(?string $token): AtolClient
{
$this->token = $token;
return $this;
}
/**
* Возвращает последний ответ сервера
*
* @return KktResponse|null
*/
public function getResponse(): ?KktResponse
{
return $this->response;
}
/**
* Возвращает логин доступа к API
*
* @return string|null
*/
public function getLogin(): ?string
{
return $this->login;
}
/**
* Устанавливает логин доступа к API
*
* @param string $login
* @return $this
* @throws EmptyLoginException
* @throws TooLongLoginException
*/
public function setLogin(string $login): self
{
$login = trim($login);
if (empty($login)) {
throw new EmptyLoginException();
} elseif (mb_strlen($login) > Constraints::MAX_LENGTH_LOGIN) {
throw new TooLongLoginException($login, Constraints::MAX_LENGTH_LOGIN);
}
$this->login = $login;
return $this;
}
/**
* Возвращает пароль доступа к API
*
* @return string|null
*/
public function getPassword(): ?string
{
return $this->password;
}
/**
* Устанавливает пароль доступа к API
*
* @param string $password
* @return $this
* @throws EmptyPasswordException Пароль ККТ не может быть пустым
* @throws TooLongPasswordException Слишком длинный пароль ККТ
*/
public function setPassword(string $password): self
{
if (empty($password)) {
throw new EmptyPasswordException();
} elseif (mb_strlen($password) > Constraints::MAX_LENGTH_PASSWORD) {
throw new TooLongPasswordException($password, Constraints::MAX_LENGTH_PASSWORD);
}
$this->password = $password;
return $this;
}
/**
* Возвращает набор заголовков для HTTP-запроса
*
* @return array
*/
private function getHeaders(): array
{
$headers['Content-type'] = 'application/json; charset=utf-8';
if ($this->getToken()) {
$headers['Token'] = $this->getToken();
}
return $headers;
}
/**
* Возвращает полный URL для запроса
*
* @param string $method
* @return string
*/
protected function getUrlToMethod(string $method): string
{
return $this->getMainEndpoint() . '/' . trim($method);
}
/**
* Отправляет авторизационный запрос на сервер АТОЛ и возвращает авторизационный токен
*
* @return string|null
* @throws AuthFailedException
* @throws EmptyPasswordException
* @throws EmptyLoginException
* @throws GuzzleException
*/
protected function doAuth(): ?string
{
$result = $this->sendRequest('POST', $this->getAuthEndpoint(), [
'login' => $this->getLogin() ?? throw new EmptyLoginException(),
'pass' => $this->getPassword() ?? throw new EmptyPasswordException(),
]);
if (!$result->isValid() || !$result->getContent()->token) {
throw new AuthFailedException($result);
}
return $result->getContent()?->token;
}
/**
* Отправляет запрос и возвращает декодированный ответ
*
* @param string $http_method Метод HTTP
* @param string $url URL
* @param array|null $data Данные для передачи
* @param array|null $options Параметры Guzzle
* @return KktResponse
* @throws GuzzleException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/
protected function sendRequest(
string $http_method,
string $url,
?array $data = null,
?array $options = null
): KktResponse {
$http_method = strtoupper(trim($http_method));
$options['headers'] = array_merge($this->getHeaders(), $options['headers'] ?? []);
if ($http_method != 'GET') {
$options['json'] = $data;
}
$response = $this->http->request($http_method, $url, $options);
return $this->response = new KktResponse($response);
}
/**
* Выполняет авторизацию на сервере АТОЛ
*
* Авторизация выолнится только если неизвестен токен
*
* @param string|null $login
* @param string|null $password
* @return bool
* @throws AuthFailedException
* @throws TooLongLoginException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongPasswordException
* @throws GuzzleException
*/
abstract public function auth(?string $login = null, ?string $password = null): bool;
/**
* Возвращает URL для запроса авторизации
*
* @return string
*/
abstract protected function getAuthEndpoint(): string;
/**
* Возвращает URL для запросов
*
* @return string
*/
abstract protected function getMainEndpoint(): string;
}

577
src/Api/Kkt.php Normal file
View File

@@ -0,0 +1,577 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Api;
use AtolOnline\{
Constants\Constraints,
Entities\Company,
Entities\Document,
Exceptions\AuthFailedException,
Exceptions\EmptyCorrectionInfoException,
Exceptions\EmptyLoginException,
Exceptions\EmptyPasswordException,
Exceptions\InvalidCallbackUrlException,
Exceptions\InvalidDocumentTypeException,
Exceptions\InvalidInnLengthException,
Exceptions\InvalidUuidException,
Exceptions\TooLongCallbackUrlException,
Exceptions\TooLongLoginException,
Exceptions\TooLongPasswordException,
Exceptions\TooLongPaymentAddressException,
Exceptions\TooManyItemsException,
Exceptions\TooManyVatsException,
TestEnvParams};
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Ramsey\Uuid\Uuid;
/**
* Класс для отправки запросов на ККТ
*
* @package AtolOnline\Api
*/
class Kkt extends Client
{
/**
* @var bool Флаг тестового режима работы
*/
protected bool $is_test_mode = false;
/**
* @var array Настройки доступа к ККТ
*/
protected array $kkt_config = [];
/**
* @var KktResponse|null Последний ответ сервера АТОЛ
*/
protected ?KktResponse $last_response;
/**
* @var string|null Токен авторизации
*/
private ?string $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
* @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): Kkt
{
$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 EmptyLoginException Логин ККТ не может быть пустым
* @throws TooLongLoginException Слишком длинный логин ККТ
*/
public function setLogin(string $login): Kkt
{
if (empty($login)) {
throw new EmptyLoginException();
} elseif (mb_strlen($login) > Constraints::MAX_LENGTH_LOGIN) {
throw new TooLongLoginException($login, Constraints::MAX_LENGTH_LOGIN);
}
$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 EmptyPasswordException Пароль ККТ не может быть пустым
* @throws TooLongPasswordException Слишком длинный пароль ККТ
*/
public function setPassword(string $password): Kkt
{
if (empty($password)) {
throw new EmptyPasswordException();
} elseif (mb_strlen($password) > Constraints::MAX_LENGTH_PASSWORD) {
throw new TooLongPasswordException($password, Constraints::MAX_LENGTH_PASSWORD);
}
$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
* @throws TooLongCallbackUrlException Слишком длинный Callback URL
* @throws InvalidCallbackUrlException Невалидный Callback URL
*/
public function setCallbackUrl(string $url): Kkt
{
if (mb_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
throw new TooLongCallbackUrlException($url, Constraints::MAX_LENGTH_CALLBACK_URL);
} elseif (!preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
throw new InvalidCallbackUrlException('Callback URL not matches with pattern');
}
$this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'] = $url;
return $this;
}
/**
* Возвращает URL для приёма колбеков
*
* @return string
*/
public function getCallbackUrl(): string
{
return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'];
}
/**
* Возвращает флаг тестового режима
*
* @return bool
*/
public function isTestMode(): bool
{
return $this->is_test_mode;
}
/**
* Устанавливает флаг тестового режима
*
* @param bool $test_mode
* @return $this
*/
public function setTestMode(bool $test_mode = true): Kkt
{
$this->is_test_mode = $test_mode;
return $this;
}
/**
* Регистрирует документ прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе есть данные коррекции
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function sell(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('sell', 'receipt', $document, $external_id);
}
/**
* Регистрирует документ возврата прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе есть данные коррекции
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws TooManyVatsException Слишком много ставок НДС
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function sellRefund(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('sell_refund', 'receipt', $document->clearVats(), $external_id);
}
/**
* Регистрирует документ коррекции прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе отсутствуют данные коррекции
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws TooManyItemsException Слишком много предметов расчёта
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function sellCorrection(Document $document, ?string $external_id = null): KktResponse
{
if (!$document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('sell_correction', 'correction', $document, $external_id);
}
/**
* Регистрирует документ расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе есть данные коррекции
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function buy(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('buy', 'receipt', $document, $external_id);
}
/**
* Регистрирует документ возврата расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе есть данные коррекции
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws TooManyVatsException Слишком много ставок НДС
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function buyRefund(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('buy_refund', 'receipt', $document->clearVats(), $external_id);
}
/**
* Регистрирует документ коррекции расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе отсутствуют данные коррекции
* @throws InvalidInnLengthException Некорректная длтина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws TooManyItemsException Слишком много предметов расчёта
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function buyCorrection(Document $document, ?string $external_id = null): KktResponse
{
if (!$document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('buy_correction', 'correction', $document, $external_id);
}
/**
* Проверяет статус чека на ККТ один раз
*
* @param string $uuid UUID регистрации
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws InvalidUuidException Некорректный UUID документа
* @throws GuzzleException
*/
public function getDocumentStatus(string $uuid): KktResponse
{
$uuid = trim($uuid);
if (!Uuid::isValid($uuid)) {
throw new InvalidUuidException($uuid);
}
$this->auth();
return $this->sendAtolRequest('GET', 'report/' . $uuid);
}
/**
* Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
* Вернёт результат как только при очередной проверке сменится статус регистрации документа.
*
* @param string $uuid UUID регистрации
* @param int $retry_count Количество попыток
* @param int $timeout Таймаут в секундах между попытками
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws InvalidUuidException Некорректный UUID документа
* @throws GuzzleException
*/
public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1): KktResponse
{
$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;
}
/**
* Возвращает текущий токен авторизации
*
* @return string
*/
public function getAuthToken(): ?string
{
return $this->auth_token;
}
/**
* Устанавливает заранее известный токен авторизации
*
* @param string|null $auth_token
* @return $this
*/
public function setAuthToken(?string $auth_token): Kkt
{
$this->auth_token = $auth_token;
return $this;
}
/**
* Сбрасывает настройки ККТ по умолчанию
*/
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'] = '';
$this->kkt_config['test']['group'] = TestEnvParams::FFD105()['group'];
$this->kkt_config['test']['login'] = TestEnvParams::FFD105()['login'];
$this->kkt_config['test']['pass'] = TestEnvParams::FFD105()['password'];
$this->kkt_config['test']['url'] = 'https://testonline.atol.ru/possystem/v4';
$this->kkt_config['test']['callback_url'] = '';
}
/**
* Возвращает набор заголовков для HTTP-запроса
*
* @return array
*/
protected function getHeaders(): array
{
$headers['Content-type'] = 'application/json; charset=utf-8';
if ($this->getAuthToken()) {
$headers['Token'] = $this->getAuthToken();
}
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): string
{
$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 KktResponse
* @throws GuzzleException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/
protected function sendAtolRequest(
string $http_method,
string $api_method,
$data = null,
array $options = null
): KktResponse {
$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
* @throws AuthFailedException Ошибка авторизации
* @throws GuzzleException
*/
protected function auth(): bool
{
if (!$this->getAuthToken()) {
$result = $this->sendAtolRequest('GET', 'getToken', [
'login' => $this->getLogin(),
'pass' => $this->getPassword(),
]);
if (!$result->isValid() || !$result->getContent()->token) {
throw new AuthFailedException($result);
}
$this->auth_token = $result->getContent()->token;
}
return true;
}
/**
* Отправляет документ на регистрацию
*
* @param string $api_method Метод API
* @param string $type Тип документа: receipt, correction
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws GuzzleException
* @throws Exception
*/
protected function registerDocument(
string $api_method,
string $type,
Document $document,
?string $external_id = null
): KktResponse {
$type = trim($type);
if (!in_array($type, ['receipt', 'correction'])) {
throw new InvalidDocumentTypeException($type);
}
$this->auth();
if ($this->isTestMode()) {
$document->setCompany(($document->getCompany() ?: new Company())
->setInn(TestEnvParams::FFD105()['inn'])
->setSno(TestEnvParams::FFD105()['sno'])
->setPaymentAddress(TestEnvParams::FFD105()['payment_address']));
}
$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()];
}
return $this->sendAtolRequest('POST', trim($api_method), $data);
}
}

125
src/Api/KktMonitor.php Normal file
View File

@@ -0,0 +1,125 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Api;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Collection;
use stdClass;
/**
* Класс для мониторинга ККТ
*
* @see https://online.atol.ru/files/API_service_information.pdf Документация
*/
class KktMonitor extends AtolClient
{
/**
* @inheritDoc
*/
protected function getAuthEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/api/auth/v1/gettoken'
: 'https://online.atol.ru/api/auth/v1/gettoken';
}
/**
* @inheritDoc
*/
protected function getMainEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/api/kkt/v1'
: 'https://online.atol.ru/api/kkt/v1';
}
/**
* @inheritDoc
*/
public function auth(?string $login = null, ?string $password = null): bool
{
if (empty($this->getToken())) {
$login && $this->setLogin($login);
$password && $this->setPassword($password);
if ($token = $this->doAuth()) {
$this->setToken($token);
}
}
return !empty($this->getToken());
}
/**
* Получает от API информацию обо всех ККТ и ФН в рамках группы
*
* @param int|null $limit
* @param int|null $offset
* @return KktResponse
* @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
*/
protected function fetchAll(?int $limit = null, ?int $offset = null): KktResponse
{
$params = [];
$limit && $params['limit'] = $limit;
$offset && $params['offset'] = $offset;
return $this->sendRequest('GET', self::getUrlToMethod('cash-registers'), $params);
}
/**
* Возвращает информацию обо всех ККТ и ФН в рамках группы
*
* @param int|null $limit
* @param int|null $offset
* @return Collection
* @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
*/
public function getAll(?int $limit = null, ?int $offset = null): Collection
{
return collect($this->fetchAll($limit, $offset)->getContent());
}
/**
* Получает от API информацию о конкретной ККТ по её серийному номеру
*
* @param string $serial_number
* @return KktResponse
* @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
*/
protected function fetchOne(string $serial_number): KktResponse
{
return $this->sendRequest(
'GET',
self::getUrlToMethod('cash-registers') . '/' . trim($serial_number),
options: [
'headers' => [
'Accept' => 'application/hal+json',
],
]
);
}
/**
* Возвращает информацию о конкретной ККТ по её серийному номеру
*
* @todo кастовать к отдельному классу со своими геттерами
* @param string $serial_number
* @return stdClass
* @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
*/
public function getOne(string $serial_number): stdClass
{
return $this->fetchOne($serial_number)->getContent()->data;
}
}

View File

@@ -1,50 +1,54 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Api; namespace AtolOnline\Api;
use JsonSerializable; use JsonSerializable;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use stdClass; use stdClass;
use Stringable;
/** /**
* Класс AtolResponse, описывающий ответ от ККТ * Класс AtolResponse, описывающий ответ от ККТ
* *
* @property mixed $error
* @package AtolOnline\Api * @package AtolOnline\Api
*/ */
class KktResponse implements JsonSerializable class KktResponse implements JsonSerializable, Stringable
{ {
/** /**
* @var int Код ответа сервера * @var int Код ответа сервера
*/ */
protected $code; protected int $code;
/** /**
* @var \stdClass Содержимое ответа сервера * @var stdClass|array|null Содержимое ответа сервера
*/ */
protected $content; protected stdClass|array|null $content;
/** /**
* @var array Заголовки ответа * @var array Заголовки ответа
*/ */
protected $headers; protected array $headers;
/** /**
* AtolResponse constructor. * AtolResponse constructor.
* *
* @param \Psr\Http\Message\ResponseInterface $response * @param ResponseInterface $response
*/ */
public function __construct(ResponseInterface $response) public function __construct(ResponseInterface $response)
{ {
$this->code = $response->getStatusCode(); $this->code = $response->getStatusCode();
$this->headers = $response->getHeaders(); $this->headers = $response->getHeaders();
$this->content = json_decode($response->getBody()); $this->content = json_decode((string)$response->getBody());
} }
/** /**
@@ -63,9 +67,9 @@ class KktResponse implements JsonSerializable
* @param $name * @param $name
* @return mixed * @return mixed
*/ */
public function __get($name) public function __get($name): mixed
{ {
return $this->getContent()->$name; return $this->getContent()?->$name;
} }
/** /**
@@ -81,9 +85,9 @@ class KktResponse implements JsonSerializable
/** /**
* Возвращает объект результата запроса * Возвращает объект результата запроса
* *
* @return stdClass|null * @return mixed
*/ */
public function getContent(): ?stdClass public function getContent(): mixed
{ {
return $this->content; return $this->content;
} }
@@ -93,18 +97,18 @@ class KktResponse implements JsonSerializable
* *
* @return bool * @return bool
*/ */
public function isValid() public function isValid(): bool
{ {
return !empty($this->getCode()) return !empty($this->getCode())
&& !empty($this->getContent()) && !empty($this->getContent())
&& empty($this->getContent()->error) && empty($this->getContent()->error)
&& (int)$this->getCode() < 400; && $this->getCode() < 400;
} }
/** /**
* Возвращает текстовое представление * Возвращает текстовое представление
*/ */
public function __toString() public function __toString(): string
{ {
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE); return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
} }
@@ -112,7 +116,7 @@ class KktResponse implements JsonSerializable
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function jsonSerialize() public function jsonSerialize(): array
{ {
return [ return [
'code' => $this->code, 'code' => $this->code,

View File

@@ -1,518 +0,0 @@
<?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;
use AtolOnline\{Entities\Document,
Exceptions\AtolCorrectionInfoException,
Exceptions\AtolInvalidUuidException,
Exceptions\AtolKktLoginEmptyException,
Exceptions\AtolKktLoginTooLongException,
Exceptions\AtolKktPasswordEmptyException,
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 Пароль ККТ не может быть пустым
* @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)
{
if (!$this->isTestMode()) {
if (empty($login)) {
throw new AtolKktLoginEmptyException();
} elseif ((function_exists('mb_strlen') ? mb_strlen($login) : strlen($login)) > 100) {
throw new AtolKktLoginTooLongException($login, 100);
}
}
$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 Пароль ККТ не может быть пустым
*/
public function setPassword(string $password)
{
if (!$this->isTestMode()) {
if (empty($password)) {
throw new AtolKktPasswordEmptyException();
}
}
$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
*/
public function setCallbackUrl(string $url)
{
$this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'] = $url;
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;
}
/**
* Регистрирует документ прихода
*
* @param \AtolOnline\Entities\Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
*/
public function sell(Document $document)
{
if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('sell', 'receipt', $document);
}
/**
* Регистрирует документ возврата прихода
*
* @param \AtolOnline\Entities\Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
*/
public function sellRefund(Document $document)
{
if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('sell_refund', 'receipt', $document->clearVats());
}
/**
* Регистрирует документ коррекции прихода
*
* @param \AtolOnline\Entities\Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
*/
public function sellCorrection(Document $document)
{
if (!$document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('sell_correction', 'correction', $document);
}
/**
* Регистрирует документ расхода
*
* @param \AtolOnline\Entities\Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
*/
public function buy(Document $document)
{
if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('buy', 'receipt', $document);
}
/**
* Регистрирует документ возврата расхода
*
* @param \AtolOnline\Entities\Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
*/
public function buyRefund(Document $document)
{
if ($document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('buy_refund', 'receipt', $document->clearVats());
}
/**
* Регистрирует документ коррекции расхода
*
* @param Document $document
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
* @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
*/
public function buyCorrection(Document $document)
{
if (!$document->getCorrectionInfo()) {
throw new AtolCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('buy_correction', 'correction', $document);
}
/**
* Проверяет статус чека на ККТ один раз
*
* @param string $uuid UUID регистрации
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolInvalidUuidException Некорректный UUID документа
*/
public function getDocumentStatus(string $uuid)
{
$uuid = trim($uuid);
if (!Uuid::isValid($uuid)) {
throw new AtolInvalidUuidException($uuid);
}
$this->auth();
return $this->sendAtolRequest('GET', 'report/'.$uuid);
}
/**
* Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
* Вернёт результат как только при очередной проверке сменится статус регистрации документа.
*
* @param string $uuid UUID регистрации
* @param int $retry_count Количество попыток
* @param int $timeout Таймаут в секундах между попытками
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolException Некорректный UUID документа
*/
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;
}
/**
* Возвращает текущий токен авторизации
*
* @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;
}
/**
* Сбрасывает настройки ККТ по умолчанию
*/
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'] = '';
$this->kkt_config['test']['group'] = 'v4-online-atol-ru_4179';
$this->kkt_config['test']['login'] = 'v4-online-atol-ru';
$this->kkt_config['test']['pass'] = 'iGFFuihss';
$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()) {
$headers['Token'] = $this->getAuthToken();
}
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
* @throws \GuzzleHttp\Exception\GuzzleException
* @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
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function auth()
{
if (!$this->getAuthToken()) {
$result = $this->sendAtolRequest('GET', 'getToken', [
'login' => $this->getLogin(),
'pass' => $this->getPassword(),
]);
if (!$result->isValid() || !$result->getContent()->token) {
return false;
}
$this->auth_token = $result->getContent()->token;
}
return true;
}
/**
* Отправляет документ на регистрацию
*
* @param string $api_method Метод API
* @param string $type Тип документа: receipt, correction
* @param \AtolOnline\Entities\Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return \AtolOnline\Api\KktResponse
* @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function registerDocument(string $api_method, string $type, Document $document, ?string $external_id = null)
{
$type = trim($type);
if (!in_array($type, ['receipt', 'correction'])) {
throw new AtolWrongDocumentTypeException($type);
}
$this->auth();
$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()];
}
return $this->sendAtolRequest('POST', trim($api_method), $data);
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace AtolOnline\Api;
abstract class AtolSchema
{
/**
* @return mixed
*/
public static function get()
{
return static::$json
?? static::$json = json_decode(file_get_contents(static::$URL));
}
/**
* @return false|string
*/
public static function json()
{
return json_encode(static::get());
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace AtolOnline\Api;
class CorrectionSchema extends AtolSchema
{
/**
* @var
*/
protected static $json;
/**
* Адрес схемы
*/
protected static $URL = 'https://online.atol.ru/possystem/v4/schema/correction';
}

View File

@@ -1,17 +0,0 @@
<?php
namespace AtolOnline\Api;
class SellSchema extends AtolSchema
{
/**
* @var
*/
protected static $json;
/**
* Адрес схемы
*/
protected static $URL = 'https://online.atol.ru/possystem/v4/schema/sell';
}

View File

@@ -1,53 +0,0 @@
<?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;
/**
* Константы, определяющие виды оплат. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
*
* @package AtolOnline\Constants
*/
class PaymentTypes
{
/**
* Расчёт наличными. Тег ФФД - 1031.
*/
const CASH = 0;
/**
* Расчёт безналичными. Тег ФФД - 1081.
*/
const ELECTRON = 1;
/**
* Предварительная оплата (зачет аванса). Тег ФФД - 1215.
*/
const PRE_PAID = 2;
/**
* Предварительная оплата (кредит). Тег ФФД - 1216.
*/
const CREDIT = 3;
/**
* Иная форма оплаты (встречное предоставление). Тег ФФД - 1217.
*/
const OTHER = 4;
/**
* Расширенный типы оплаты
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_5 = 5;
const ADD_6 = 6;
const ADD_7 = 7;
const ADD_8 = 8;
const ADD_9 = 9;
}

View File

@@ -1,149 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{Exceptions\AtolNameTooLongException, Exceptions\AtolPhoneTooLongException, Traits\HasEmail, Traits\HasInn};
/**
* Класс Client, описывающий сущность покупателя
*
* @package AtolOnline\Entities
*/
class Client extends Entity
{
use
/**
* Покупатель может иметь почту. Тег ФФД - 1008.
*/
HasEmail,
/**
* Покупатель может иметь ИНН. Тег ФФД - 1228.
*/
HasInn;
/**
* @var string Телефон покупателя. Тег ФФД - 1008.
*/
protected $phone;
/**
* @var string Имя покупателя. Тег ФФД - 1227.
*/
protected $name;
/**
* Client constructor.
*
* @param string|null $name Наименование
* @param string|null $phone Телефон
* @param string|null $email Email
* @param string|null $inn ИНН
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
* @throws \AtolOnline\Exceptions\AtolNameTooLongException
* @throws \AtolOnline\Exceptions\AtolPhoneTooLongException
*/
public function __construct(?string $name = null, ?string $phone = null, ?string $email = null, ?string $inn = null)
{
if ($name) {
$this->setName($name);
}
if ($email) {
$this->setEmail($email);
}
if ($phone) {
$this->setPhone($phone);
}
if ($inn) {
$this->setInn($inn);
}
}
/**
* Возвращает имя покупателя. Тег ФФД - 1227.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Устанавливает имя покупателя
* Тег ФФД - 1227.
*
* @param string $name
* @return $this
* @throws AtolNameTooLongException
*/
public function setName(string $name)
{
$name = trim($name);
if ((function_exists('mb_strlen') ? mb_strlen($name) : strlen($name)) > 256) {
throw new AtolNameTooLongException($name, 256);
}
$this->name = $name;
return $this;
}
/**
* Возвращает телефон покупателя.
* Тег ФФД - 1008.
*
* @return string
*/
public function getPhone()
{
return $this->phone ?? '';
}
/**
* Устанавливает телефон покупателя.
* Тег ФФД - 1008.
* Входная строка лишается всех знаков, кроме цифр и знака '+'.
*
* @param string $phone
* @return $this
* @throws AtolPhoneTooLongException
*/
public function setPhone(string $phone)
{
$phone = preg_replace("/[^0-9+]/", '', $phone);
if ((function_exists('mb_strlen') ? mb_strlen($phone) : strlen($phone)) > 64) {
throw new AtolPhoneTooLongException($phone, 64);
}
$this->phone = $phone;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$json = [];
if ($this->getName()) {
$json['name'] = $this->getName() ?? '';
}
if ($this->getEmail()) {
$json['email'] = $this->getEmail() ?? '';
}
if ($this->getPhone()) {
$json['phone'] = $this->getPhone() ?? '';
}
if ($this->getInn()) {
$json['inn'] = $this->getInn() ?? '';
}
return $json;
}
}

View File

@@ -1,137 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{Exceptions\AtolEmailTooLongException,
Exceptions\AtolEmailValidateException,
Exceptions\AtolInnWrongLengthException,
Exceptions\AtolPaymentAddressTooLongException,
Traits\HasEmail,
Traits\HasInn
};
/**
* Класс, описывающий сущность компании-продавца
*
* @package AtolOnline\Entities
*/
class Company extends Entity
{
use
/**
* Продавец должен иметь почту. Тег ФФД - 1117.
*/
HasEmail,
/**
* Продавец должен иметь ИНН. Тег ФФД - 1018.
*/
HasInn;
/**
* @var string Система налогообложения продавца. Тег ФФД - 1055.
*/
protected $sno;
/**
* @var string Место расчётов (адрес интернет-магазина). Тег ФФД - 1187.
*/
protected $payment_address;
/**
* Company constructor.
*
* @param string|null $sno
* @param string|null $inn
* @param string|null $paymentAddress
* @param string|null $email
* @throws AtolEmailTooLongException
* @throws AtolEmailValidateException
* @throws AtolInnWrongLengthException
* @throws AtolPaymentAddressTooLongException
*/
public function __construct(string $sno = null, string $inn = null, string $paymentAddress = null, string $email = null)
{
if ($sno) {
$this->setSno($sno);
}
if ($inn) {
$this->setInn($inn);
}
if ($paymentAddress) {
$this->setPaymentAddress($paymentAddress);
}
if ($email) {
$this->setEmail($email);
}
}
/**
* Возвращает установленный тип налогообложения. Тег ФФД - 1055.
*
* @return string
*/
public function getSno()
{
return $this->sno;
}
/**
* Устанавливает тип налогообложения. Тег ФФД - 1055.
*
* @param string $sno
* @return $this
*/
public function setSno(string $sno)
{
$this->sno = trim($sno);
return $this;
}
/**
* Возвращает установленный адрес места расчётов. Тег ФФД - 1187.
*
* @return string
*/
public function getPaymentAddress()
{
return $this->payment_address;
}
/**
* Устанавливает адрес места расчётов. Тег ФФД - 1187.
*
* @param string $payment_address
* @return $this
* @throws AtolPaymentAddressTooLongException
*/
public function setPaymentAddress(string $payment_address)
{
$payment_address = trim($payment_address);
if ((function_exists('mb_strlen') ? mb_strlen($payment_address) : strlen($payment_address)) > 256) {
throw new AtolPaymentAddressTooLongException($payment_address, 256);
}
$this->payment_address = $payment_address;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
return [
'email' => $this->getEmail(),
'sno' => $this->getSno(),
'inn' => $this->getInn(),
'payment_address' => $this->getPaymentAddress(),
];
}
}

View File

@@ -1,41 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при ошибке валидации email
*
* @package AtolOnline\Exceptions
*/
class AtolEmailValidateException extends AtolException
{
/**
* @inheritDoc
*/
protected $ffd_tags = [
1008,
1117,
];
/**
* AtolEmailValidateException constructor.
*
* @param $email
* @param string $message
* @param int $code
* @param \Throwable|null $previous
*/
public function __construct($email, $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message ?: 'Invalid email: '.$email, $code, $previous);
}
}

View File

@@ -1,44 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при попытке указать ИНН некорректной длины
*
* @package AtolOnline\Exceptions
*/
class AtolInnWrongLengthException extends AtolException
{
/**
* @inheritDoc
*/
protected $ffd_tags = [
1016,
1018,
1226,
1228,
];
/**
* AtolInnWrongLengthException constructor.
*
* @param $inn
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($inn, $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message ?: 'INN length must be 10 or 12 digits only, but actual is '.
(function_exists('mb_strlen') ? mb_strlen($inn) : strlen($inn)).')', $code, $previous);
}
}

View File

@@ -1,33 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при попытке указать некорректный тип документа
*
* @package AtolOnline\Exceptions
*/
class AtolWrongDocumentTypeException extends AtolException
{
/**
* AtolWrongDocumentTypeException constructor.
*
* @param $type
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($type, $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message ?: "Wrong document type: 'receipt' or 'correction' expected, but '$type' provided", $code, $previous);
}
}

View File

@@ -1,56 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Traits;
use AtolOnline\{Exceptions\AtolEmailTooLongException, Exceptions\AtolEmailValidateException};
/**
* Добавляет объекту функционал для работы с email
*
* @package AtolOnline\Traits
*/
trait HasEmail
{
/**
* @var string Почта
*/
protected $email;
/**
* Возвращает установленную почту. Тег ФФД: 1008, 1117.
*
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Устанавливает почту. Тег ФФД: 1008, 1117.
*
* @param string $email
* @return $this
* @throws AtolEmailTooLongException
* @throws AtolEmailValidateException
*/
public function setEmail(string $email)
{
$email = trim($email);
if ((function_exists('mb_strlen') ? mb_strlen($email) : strlen($email)) > 64) {
throw new AtolEmailTooLongException($email, 64);
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new AtolEmailValidateException($email);
}
$this->email = $email;
return $this;
}
}

View File

@@ -1,53 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Traits;
use AtolOnline\Exceptions\AtolInnWrongLengthException;
/**
* Добавляет объекту функционал для работы с ИНН
*
* @package AtolOnline\Traits
*/
trait HasInn
{
/**
* @var string ИНН
*/
protected $inn;
/**
* Возвращает установленный ИНН. Тег ФФД: 1228, 1018.
*
* @return string
*/
public function getInn()
{
return $this->inn ?? '';
}
/**
* Устанавливает ИНН. Тег ФФД: 1228, 1018.
* Входная строка лишается всех знаков, кроме цифр.
*
* @param string $inn
* @return $this
* @throws AtolInnWrongLengthException
*/
public function setInn(string $inn)
{
$inn = preg_replace("/[^0-9]/", '', $inn);
if (preg_match_all("/(^[0-9]{10}$)|(^[0-9]{12}$)/", $inn) == 0) {
throw new AtolInnWrongLengthException($inn);
}
$this->inn = $inn;
return $this;
}
}

View File

@@ -1,40 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Traits;
/**
* Свойство класса, позволяющее конвертировать рубли <-> копейки
*
* @package AtolOnline\Traits
*/
trait RublesKopeksConverter
{
/**
* Конвертирует рубли в копейки, учитывая только 2 знака после запятой
*
* @param float|null $rubles Рубли
* @return int Копейки
*/
protected static function toKop(?float $rubles = null)
{
return $rubles === null ? null : (int)round($rubles * 100, 2);
}
/**
* Конвертирует копейки в рубли, оставляя только 2 знака после запятой
*
* @param int|null $kopeks Копейки
* @return float Рубли
*/
protected static function toRub(?int $kopeks = null)
{
return $kopeks === null ? null : round($kopeks / 100, 2);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Constants;
/**
* Класс с константами ограничений
*/
final class Constraints
{
/**
* Максимальная длина Callback URL
*/
const MAX_LENGTH_CALLBACK_URL = 256;
/**
* Максимальная длина email
*/
const MAX_LENGTH_EMAIL = 64;
/**
* Максимальная длина логина ККТ
*/
const MAX_LENGTH_LOGIN = 100;
/**
* Максимальная длина пароля ККТ
*/
const MAX_LENGTH_PASSWORD = 100;
/**
* Максимальная длина имени покупателя
*/
const MAX_LENGTH_CLIENT_NAME = 256;
/**
* Максимальная длина телефона покупателя
*/
const MAX_LENGTH_CLIENT_PHONE = 64;
/**
* Максимальная длина адреса места расчётов
*/
const MAX_LENGTH_PAYMENT_ADDRESS = 256;
/**
* Максимальная длина имени кассира
*/
const MAX_LENGTH_CASHIER_NAME = 64;
/**
* Максимальная длина наименования предмета расчётов
*/
const MAX_LENGTH_ITEM_NAME = 128;
/**
* Максимальная длина единицы измерения предмета расчётов
*/
const MAX_LENGTH_MEASUREMENT_UNIT = 16;
/**
* Максимальная длина пользовательских данных для предмета расчётов
*/
const MAX_LENGTH_USER_DATA = 64;
/**
* Регулярное выражание для валидации строки ИНН
*/
const PATTERN_INN = "/(^[0-9]{10}$)|(^[0-9]{12}$)/";
/**
* Регулярное выражание для валидации строки Callback URL
*/
const PATTERN_CALLBACK_URL = "/^http(s?)\:\/\/[0-9a-zA-Zа-яА-Я]([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-\.\?\,\'\/\\\+&=%\$#_]*)?$/";
}

View File

@@ -1,28 +1,34 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие типы документов коррекции * Константы, определяющие типы документов коррекции
* *
* @package AtolOnline\Constants * Тег ФФД - 1173
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35 (correction_info)
*/ */
class CorrectionTypes final class CorrectionTypes extends Enum
{ {
/** /**
* Самостоятельно * Самостоятельно
*/ */
const SELF = 'self'; const SELF = 'self';
/** /**
* По предписанию * По предписанию
*/ */
const INSTRUCTION = 'instruction'; const INSTRUCTION = 'instruction';
} }

View File

@@ -0,0 +1,30 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/**
* Константы, определяющие типы документов
*/
final class DocumentTypes extends Enum
{
/**
* Чек прихода, возврата прихода, расхода, возврата расхода
*/
const RECEIPT = 'receipt';
/**
* Чек коррекции
*/
const CORRECTION = 'correction';
}

View File

@@ -1,54 +1,59 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие признаки способов расчёта. Тег ФФД - 1214. * Константы, определяющие признаки способов расчёта
* *
* @package AtolOnline\Constants * Тег ФФД - 1214
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 22 (payment_method)
*/ */
class PaymentMethods final class PaymentMethods extends Enum
{ {
/** /**
* Предоплата 100% до передачи предмета расчёта * Предоплата 100% до передачи предмета расчёта
*/ */
const FULL_PREPAYMENT = 'full_prepayment'; const FULL_PREPAYMENT = 'full_prepayment';
/** /**
* Частичная предоплата до передачи предмета расчёта * Частичная предоплата до передачи предмета расчёта
*/ */
const PREPAYMENT = 'prepayment'; const PREPAYMENT = 'prepayment';
/** /**
* Аванс * Аванс
*/ */
const ADVANCE = 'advance'; const ADVANCE = 'advance';
/** /**
* Полная оплата с учётом аванса/предоплаты в момент передачи предмета расчёта * Полная оплата с учётом аванса/предоплаты в момент передачи предмета расчёта
*/ */
const FULL_PAYMENT = 'full_payment'; const FULL_PAYMENT = 'full_payment';
/** /**
* Частичный расчёт в момент передачи предмета расчёта (дальнейшая оплата в кредит) * Частичный расчёт в момент передачи предмета расчёта (дальнейшая оплата в кредит)
*/ */
const PARTIAL_PAYMENT = 'partial_payment'; const PARTIAL_PAYMENT = 'partial_payment';
/** /**
* Передача предмета расчёта в кредит * Передача предмета расчёта в кредит
*/ */
const CREDIT = 'credit'; const CREDIT = 'credit';
/** /**
* Оплата кредита * Оплата кредита
*/ */
const CREDIT_PAYMENT = 'credit_payment'; const CREDIT_PAYMENT = 'credit_payment';
} }

View File

@@ -1,108 +1,161 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие признаки предметов расчёта. Тег ФФД - 1212. * Константы, определяющие признаки предметов расчёта
* *
* @package AtolOnline\Constants * Тег ФФД - 1212
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 23 (payment_object)
*/ */
class PaymentObjects final class PaymentObjects extends Enum
{ {
/** /**
* Товар, кроме подакцизного * Товар, кроме подакцизного
*/ */
const COMMODITY = 'commodity'; const COMMODITY = 'commodity';
/** /**
* Товар подакцизный * Товар подакцизный
*/ */
const EXCISE = 'excise'; const EXCISE = 'excise';
/** /**
* Работа * Работа
*/ */
const JOB = 'job'; const JOB = 'job';
/** /**
* Услуга * Услуга
*/ */
const SERVICE = 'service'; const SERVICE = 'service';
/** /**
* Ставка азартной игры * Ставка азартной игры
*/ */
const GAMBLING_BET = 'gambling_bet'; const GAMBLING_BET = 'gambling_bet';
/** /**
* Выигрыш азартной игры * Выигрыш азартной игры
*/ */
const GAMBLING_PRIZE = 'gambling_prize'; const GAMBLING_PRIZE = 'gambling_prize';
/** /**
* Лотерея * Лотерея
*/ */
const LOTTERY = 'lottery'; const LOTTERY = 'lottery';
/** /**
* Выигрыш лотереи * Выигрыш лотереи
*/ */
const LOTTERY_PRIZE = 'lottery_prize'; const LOTTERY_PRIZE = 'lottery_prize';
/** /**
* Предоставление результатов интеллектуальной деятельности * Предоставление результатов интеллектуальной деятельности
*/ */
const INTELLECTUAL_ACTIVITY = 'intellectual_activity'; const INTELLECTUAL_ACTIVITY = 'intellectual_activity';
/** /**
* Платёж (задаток, кредит, аванс, предоплата, пеня, штраф, бонус и пр.) * Платёж (задаток, кредит, аванс, предоплата, пеня, штраф, бонус и пр.)
*/ */
const PAYMENT = 'payment'; const PAYMENT = 'payment';
/** /**
* Агентское вознаграждение * Агентское вознаграждение
*/ */
const AGENT_COMMISSION = 'agent_commission'; const AGENT_COMMISSION = 'agent_commission';
/** /**
* Составной предмет расчёта * Составной предмет расчёта
* @deprecated Более не используется согласно ФФД 1.05
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 25 (payment_object)
*/ */
const COMPOSITE = 'composite'; const COMPOSITE = 'composite';
/** /**
* Другой предмет расчёта * Другой предмет расчёта
*/ */
const ANOTHER = 'another'; const ANOTHER = 'another';
/** /**
* Имущественное право * Имущественное право
*/ */
const PROPERTY_RIGHT = 'property_right'; const PROPERTY_RIGHT = 'property_right';
/** /**
* Внереализационный доход * Внереализационный доход
*/ */
const NON_OPERATING_GAIN = 'non-operating_gain'; const NON_OPERATING_GAIN = 'non-operating_gain';
/** /**
* Страховые взносы * Страховые взносы
*/ */
const INSURANCE_PREMIUM = 'insurance_premium'; const INSURANCE_PREMIUM = 'insurance_premium';
/** /**
* Торговый сбор * Торговый сбор
*/ */
const SALES_TAX = 'sales_tax'; const SALES_TAX = 'sales_tax';
/** /**
* Курортный сбор * Курортный сбор
*/ */
const RESORT_FEE = 'resort_fee'; const RESORT_FEE = 'resort_fee';
/**
* Взнос в счёт оплаты пени, штрафе, вознаграждении, бонусе и ином аналогичном предмете расчета
*/
const AWARD = 'award';
/**
* Залог
*/
const DEPOSIT = 'deposit';
/**
* Расход, уменьшающий доход (в соответствии со статьей 346.16 НК РФ)
*/
const EXPENSE = 'expense';
/**
* Взнос на ОПС ИП
*/
const PEN_INSURANCE_IP = 'pension_insurance_ip';
/**
* Взнос на ОПС
*/
const PEN_INSURANCE = 'pension_insurance';
/**
* Взнос на ОМС ИП
*/
const MED_INSURANCE_IP = 'medical_insurance_ip';
/**
* Взнос на ОМС
*/
const MED_INSURANCE = 'medical_insurance';
/**
* Взнос на ОСС
*/
const SOC_INSURANCE = 'social_insurance';
/**
* Платёж казино
*/
const CASINO_PAYMENT = 'casino_payment';
} }

View File

@@ -0,0 +1,77 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/**
* Константы, определяющие виды оплат
*
* Теги ФФД: 1031, 1081, 1215, 1216, 1217
*/
final class PaymentTypes extends Enum
{
/**
* Расчёт наличными. Тег ФФД - 1031.
*/
const CASH = 0;
/**
* Расчёт безналичными. Тег ФФД - 1081.
*/
const ELECTRON = 1;
/**
* Предварительная оплата (зачёт аванса). Тег ФФД - 1215.
*/
const PRE_PAID = 2;
/**
* Предварительная оплата (кредит). Тег ФФД - 1216.
*/
const CREDIT = 3;
/**
* Иная форма оплаты (встречное предоставление). Тег ФФД - 1217.
*/
const OTHER = 4;
/**
* Расширенный типы оплаты (5)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_5 = 5;
/**
* Расширенный типы оплаты (6)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_6 = 6;
/**
* Расширенный типы оплаты (7)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_7 = 7;
/**
* Расширенный типы оплаты (8)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_8 = 8;
/**
* Расширенный типы оплаты (9)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
const ADD_9 = 9;
}

View File

@@ -1,48 +1,50 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие типы операций (чеков) * Константы, определяющие типы операций (чеков)
*
* @package AtolOnline\Constants
*/ */
class ReceiptOperationTypes final class ReceiptOperationTypes extends Enum
{ {
/** /**
* Приход (мы продали) * Приход (мы продали)
*/ */
const SELL = 'sell'; const SELL = 'sell';
/** /**
* Возврат прихода (нам вернули предмет расчёта, мы вернули деньги) * Возврат прихода (нам вернули предмет расчёта, мы вернули средства)
*/ */
const SELL_REFUND = 'sell_refund'; const SELL_REFUND = 'sell_refund';
/** /**
* Коррекция прихода * Коррекция прихода
*/ */
const SELL_CORRECTION = 'sell_correction'; const SELL_CORRECTION = 'sell_correction';
/** /**
* Расход (мы купили) * Расход (мы купили)
*/ */
const BUY = 'buy'; const BUY = 'buy';
/** /**
* Возврат расхода (мы вернули предмет расчёта, нам вернули деньги) * Возврат расхода (мы вернули предмет расчёта, нам вернули средства)
*/ */
const BUY_REFUND = 'buy_refund'; const BUY_REFUND = 'buy_refund';
/** /**
* Коррекция прихода * Коррекция прихода (догоняем неучтённые средства)
*/ */
const BUY_CORRECTION = 'buy_correction'; const BUY_CORRECTION = 'buy_correction';
} }

View File

@@ -1,46 +1,50 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие типы налогообложения * Константы, определяющие типы налогообложения
* *
* @package AtolOnline\Constants * Тег ФДД - 1055
*/ */
class SnoTypes final class SnoTypes extends Enum
{ {
/** /**
* Общая СН * Общая СН
*/ */
const OSN = 'osn'; const OSN = 'osn';
/** /**
* Упрощенная СН (доходы) * Упрощенная СН (доходы)
*/ */
const USN_INCOME = 'usn_income'; const USN_INCOME = 'usn_income';
/** /**
* Упрощенная СН (доходы минус расходы) * Упрощенная СН (доходы минус расходы)
*/ */
const USN_INCOME_OUTCOME = 'usn_income_outcome'; const USN_INCOME_OUTCOME = 'usn_income_outcome';
/** /**
* Единый налог на вмененный доход * Единый налог на вмененный доход
*/ */
const ENDV = 'envd'; const ENDV = 'envd';
/** /**
* Единый сельскохозяйственный налог * Единый сельскохозяйственный налог
*/ */
const ESN = 'esn'; const ESN = 'esn';
/** /**
* Патентная СН * Патентная СН
*/ */

View File

@@ -1,56 +1,60 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Constants; namespace AtolOnline\Constants;
use MyCLabs\Enum\Enum;
/** /**
* Константы, определяющие типы ставок НДС * Константы, определяющие типы ставок НДС
* *
* @package AtolOnline\Constants * Теги ФФД: 1199, 1105, 1104, 1103, 1102, 1107, 1106
*/ */
class VatTypes final class VatTypes extends Enum
{ {
/** /**
* Без НДС * Без НДС
*/ */
const NONE = 'none'; const NONE = 'none';
/** /**
* НДС 0% * НДС 0%
*/ */
const VAT0 = 'vat0'; const VAT0 = 'vat0';
/** /**
* НДС 10% * НДС 10%
*/ */
const VAT10 = 'vat10'; const VAT10 = 'vat10';
/** /**
* НДС 18% * НДС 18%
*/ */
const VAT18 = 'vat18'; const VAT18 = 'vat18';
/** /**
* НДС 20% * НДС 20%
*/ */
const VAT20 = 'vat20'; const VAT20 = 'vat20';
/** /**
* НДС 10/110% * НДС 10/110%
*/ */
const VAT110 = 'vat110'; const VAT110 = 'vat110';
/** /**
* НДС 18/118% * НДС 18/118%
*/ */
const VAT118 = 'vat118'; const VAT118 = 'vat118';
/** /**
* НДС 20/120% * НДС 20/120%
*/ */

213
src/Entities/Client.php Normal file
View File

@@ -0,0 +1,213 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\{
Constants\Constraints,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\TooLongEmailException,
Exceptions\TooLongNameException,
Exceptions\TooLongPhoneException};
/**
* Класс Client, описывающий сущность покупателя
*
* @package AtolOnline\Entities
*/
class Client extends Entity
{
/**
* @var string|null Наименование. Тег ФФД - 1227.
*/
protected ?string $name = null;
/**
* @var string|null Email. Тег ФФД - 1008.
*/
protected ?string $email = null;
/**
* @var string|null Телефон покупателя. Тег ФФД - 1008.
*/
protected ?string $phone = null;
/**
* @var string|null ИНН. Тег ФФД - 1228.
*/
protected ?string $inn = null;
/**
* Конструктор объекта покупателя
*
* @param string|null $name Наименование. Тег ФФД - 1227.
* @param string|null $phone Email. Тег ФФД - 1008.
* @param string|null $email Телефон покупателя. Тег ФФД - 1008.
* @param string|null $inn ИНН. Тег ФФД - 1228.
* @throws TooLongNameException Слишком длинное имя
* @throws TooLongPhoneException Слишком длинный телефон
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
* @throws InvalidInnLengthException Некорректная длина ИНН
*/
public function __construct(
?string $name = null,
?string $email = null,
?string $phone = null,
?string $inn = null
) {
$name && $this->setName($name);
$email && $this->setEmail($email);
$phone && $this->setPhone($phone);
$inn && $this->setInn($inn);
}
/**
* Возвращает наименование покупателя
*
* Тег ФФД - 1227
*
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Устанавливает наименование покупателя
*
* Тег ФФД - 1227
*
* @param string|null $name
* @return $this
* @throws TooLongNameException
*/
public function setName(?string $name): Client
{
if (is_string($name)) {
$name = preg_replace('/[\n\r\t]/', '', trim($name));
if (mb_strlen($name) > Constraints::MAX_LENGTH_CLIENT_NAME) {
throw new TooLongNameException($name, Constraints::MAX_LENGTH_CLIENT_NAME);
}
}
$this->name = empty($name) ? null : $name;
return $this;
}
/**
* Возвращает установленный email
*
* @return string|null
*/
public function getEmail(): ?string
{
return $this->email;
}
/**
* Устанавливает email
*
* @param string|null $email
* @return $this
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
*/
public function setEmail(?string $email): self
{
if (is_string($email)) {
$email = preg_replace('/[\n\r\t]/', '', trim($email));
if (mb_strlen($email) > Constraints::MAX_LENGTH_EMAIL) {
throw new TooLongEmailException($email, Constraints::MAX_LENGTH_EMAIL);
} elseif (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidEmailException($email);
}
}
$this->email = empty($email) ? null : $email;
return $this;
}
/**
* Возвращает установленный телефон
*
* Тег ФФД - 1008
*
* @return string|null
*/
public function getPhone(): ?string
{
return $this->phone;
}
/**
* Устанавливает телефон
*
* Тег ФФД - 1008
*
* @param string|null $phone Номер телефона
* @return $this
* @throws TooLongPhoneException
*/
public function setPhone(?string $phone): Client
{
if (is_string($phone)) {
$phone = preg_replace('/[^\d]/', '', trim($phone));
if (mb_strlen($phone) > Constraints::MAX_LENGTH_CLIENT_PHONE) {
throw new TooLongPhoneException($phone, Constraints::MAX_LENGTH_CLIENT_PHONE);
}
}
$this->phone = empty($phone) ? null : "+$phone";
return $this;
}
/**
* Возвращает установленный ИНН
*
* @return string|null
*/
public function getInn(): ?string
{
return $this->inn;
}
/**
* Устанавливает ИНН
*
* @param string|null $inn
* @return $this
* @throws InvalidInnLengthException Некорректная длина ИНН
*/
public function setInn(?string $inn): self
{
if (is_string($inn)) {
$inn = preg_replace('/[^\d]/', '', trim($inn));
if (preg_match_all(Constraints::PATTERN_INN, $inn) === 0) {
throw new InvalidInnLengthException($inn);
}
}
$this->inn = empty($inn) ? null : $inn;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): object
{
$json = [];
$this->getName() && $json['name'] = $this->getName();
$this->getEmail() && $json['email'] = $this->getEmail();
$this->getPhone() && $json['phone'] = $this->getPhone();
$this->getInn() && $json['inn'] = $this->getInn();
return (object)$json;
}
}

227
src/Entities/Company.php Normal file
View File

@@ -0,0 +1,227 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\{
Constants\Constraints,
Constants\SnoTypes,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\InvalidPaymentAddressException,
Exceptions\InvalidSnoException,
Exceptions\TooLongEmailException,
Exceptions\TooLongPaymentAddressException};
/**
* Класс, описывающий сущность компании-продавца
*
* @package AtolOnline\Entities
*/
class Company extends Entity
{
/**
* @var string|null Почта. Тег ФФД - 1117.
*/
protected ?string $email;
/**
* @var string|null Система налогообложения продавца. Тег ФФД - 1055.
*/
protected ?string $sno;
/**
* @var string|null ИНН. Тег ФФД - 1018.
*/
protected ?string $inn;
/**
* @var string|null Место расчётов (адрес интернет-магазина). Тег ФФД - 1187.
*/
protected ?string $payment_address;
/**
* Company constructor.
*
* @param string $sno
* @param string $inn
* @param string $payment_address
* @param string $email
* @throws InvalidEmailException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws InvalidSnoException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/
public function __construct(
string $email,
string $sno,
string $inn,
string $payment_address,
) {
$this->setEmail($email);
$this->setSno($sno);
$this->setInn($inn);
$this->setPaymentAddress($payment_address);
}
/**
* Возвращает установленный email
*
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* Устанавливает email
*
* @param string $email
* @return $this
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
*/
public function setEmail(string $email): self
{
$email = preg_replace('/[\n\r\t]/', '', trim($email));
if (mb_strlen($email) > Constraints::MAX_LENGTH_EMAIL) {
throw new TooLongEmailException($email, Constraints::MAX_LENGTH_EMAIL);
} elseif (empty($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidEmailException($email);
}
$this->email = $email;
return $this;
}
/**
* Возвращает установленный тип налогообложения
*
* Тег ФФД - 1055
*
* @return string
*/
public function getSno(): string
{
return $this->sno;
}
/**
* Устанавливает тип налогообложения
*
* Тег ФФД - 1055
*
* @param string $sno
* @return $this
* @throws InvalidSnoException
*/
public function setSno(string $sno): Company
{
$sno = trim($sno);
if (empty($sno) || !in_array($sno, SnoTypes::toArray())) {
throw new InvalidSnoException($sno);
}
$this->sno = $sno;
return $this;
}
/**
* Возвращает установленный ИНН
*
* Тег ФФД - 1018
*
* @return string
*/
public function getInn(): string
{
return $this->inn;
}
/**
* Устанавливает ИНН
*
* Тег ФФД - 1018
*
* @param string $inn
* @return $this
* @throws InvalidInnLengthException
*/
public function setInn(string $inn): self
{
$inn = preg_replace('/[^\d]/', '', trim($inn));
if (empty($inn) || preg_match_all(Constraints::PATTERN_INN, $inn) === 0) {
throw new InvalidInnLengthException($inn);
}
$this->inn = $inn;
return $this;
}
/**
* Возвращает установленный адрес места расчётов
*
* Тег ФФД - 1187
*
* @return string
*/
public function getPaymentAddress(): string
{
return $this->payment_address;
}
/**
* Устанавливает адрес места расчётов
*
* Тег ФФД - 1187
*
* @param string $payment_address
* @return $this
* @throws TooLongPaymentAddressException
* @throws InvalidPaymentAddressException
*/
public function setPaymentAddress(string $payment_address): Company
{
$payment_address = trim($payment_address);
if (empty($payment_address)) {
throw new InvalidPaymentAddressException();
} elseif (mb_strlen($payment_address) > Constraints::MAX_LENGTH_PAYMENT_ADDRESS) {
throw new TooLongPaymentAddressException($payment_address, Constraints::MAX_LENGTH_PAYMENT_ADDRESS);
}
$this->payment_address = $payment_address;
return $this;
}
/**
* @inheritDoc
* @throws InvalidEmailException
* @throws InvalidSnoException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
*/
public function jsonSerialize(): object
{
return (object)[
'email' => $this->email
? $this->getEmail()
: throw new InvalidEmailException(),
'sno' => $this->sno
? $this->getSno()
: throw new InvalidSnoException(),
'inn' => $this->inn
? $this->getInn()
: throw new InvalidInnLengthException(),
'payment_address' => $this->payment_address
? $this->getPaymentAddress()
: throw new InvalidPaymentAddressException(),
];
}
}

View File

@@ -1,40 +1,40 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
/** /**
* Класс CorrectionInfo, описывающий данные коррекции * Класс CorrectionInfo, описывающий данные чек коррекции
*
* @package AtolOnline\Entities
*/ */
class CorrectionInfo extends Entity class CorrectionInfo extends Entity
{ {
/** /**
* @var int Тип коррекции. Тег ФФД - 1173. * @var string Тип коррекции. Тег ФФД - 1173.
*/ */
protected $type; protected string $type;
/** /**
* @var string Дата документа основания для коррекции. Тег ФФД - 1178. * @var string Дата документа основания для коррекции. Тег ФФД - 1178.
*/ */
protected $base_date; protected string $base_date;
/** /**
* @var string Номер документа основания для коррекции. Тег ФФД - 1179. * @var string Номер документа основания для коррекции. Тег ФФД - 1179.
*/ */
protected $base_number; protected string $base_number;
/** /**
* @var string Описание коррекции. Тег ФФД - 1177. * @var string Описание коррекции. Тег ФФД - 1177.
*/ */
protected $base_name; protected string $base_name;
/** /**
* CorrectionInfo constructor. * CorrectionInfo constructor.
@@ -68,7 +68,7 @@ class CorrectionInfo extends Entity
*/ */
public function getNumber(): ?string public function getNumber(): ?string
{ {
return $this->base_name; return $this->base_number;
} }
/** /**
@@ -78,7 +78,7 @@ class CorrectionInfo extends Entity
* @param string $number * @param string $number
* @return $this * @return $this
*/ */
public function setNumber(string $number) public function setNumber(string $number): CorrectionInfo
{ {
$this->base_number = trim($number); $this->base_number = trim($number);
return $this; return $this;
@@ -102,7 +102,7 @@ class CorrectionInfo extends Entity
* @param string $name * @param string $name
* @return $this * @return $this
*/ */
public function setName(string $name) public function setName(string $name): CorrectionInfo
{ {
$this->base_name = trim($name); $this->base_name = trim($name);
return $this; return $this;
@@ -126,7 +126,7 @@ class CorrectionInfo extends Entity
* @param string $date Строка в формате d.m.Y * @param string $date Строка в формате d.m.Y
* @return $this * @return $this
*/ */
public function setDate(string $date) public function setDate(string $date): CorrectionInfo
{ {
$this->base_date = $date; $this->base_date = $date;
return $this; return $this;
@@ -150,7 +150,7 @@ class CorrectionInfo extends Entity
* @param string $type * @param string $type
* @return $this * @return $this
*/ */
public function setType(string $type) public function setType(string $type): CorrectionInfo
{ {
$this->type = $type; $this->type = $type;
return $this; return $this;
@@ -165,7 +165,7 @@ class CorrectionInfo extends Entity
'type' => $this->getType() ?? '', // обязателен 'type' => $this->getType() ?? '', // обязателен
'base_date' => $this->getDate() ?? '', // обязателен 'base_date' => $this->getDate() ?? '', // обязателен
'base_number' => $this->getNumber() ?? '', // обязателен 'base_number' => $this->getNumber() ?? '', // обязателен
'base_name' => $this->getName() ?? '' // обязателен 'base_name' => $this->getName() ?? '' // не обязателен
]; ];
} }
} }

View File

@@ -1,17 +1,34 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolCashierTooLongException; use AtolOnline\Constants\Constraints;
use AtolOnline\Exceptions\AtolException; use AtolOnline\Exceptions\AtolException;
use AtolOnline\Exceptions\AtolInvalidJsonException; use AtolOnline\Exceptions\BasicTooManyException;
use AtolOnline\Exceptions\InvalidEmailException;
use AtolOnline\Exceptions\InvalidInnLengthException;
use AtolOnline\Exceptions\InvalidJsonException;
use AtolOnline\Exceptions\TooHighPriceException;
use AtolOnline\Exceptions\TooLongCashierException;
use AtolOnline\Exceptions\TooLongEmailException;
use AtolOnline\Exceptions\TooLongNameException;
use AtolOnline\Exceptions\TooLongPaymentAddressException;
use AtolOnline\Exceptions\TooLongPhoneException;
use AtolOnline\Exceptions\TooLongUnitException;
use AtolOnline\Exceptions\TooLongUserdataException;
use AtolOnline\Exceptions\TooManyItemsException;
use AtolOnline\Exceptions\TooManyPaymentsException;
use AtolOnline\Exceptions\TooManyVatsException;
use Exception;
/** /**
* Класс, описывающий документ * Класс, описывающий документ
@@ -21,44 +38,44 @@ use AtolOnline\Exceptions\AtolInvalidJsonException;
class Document extends Entity class Document extends Entity
{ {
/** /**
* @var \AtolOnline\Entities\ItemArray Массив предметов расчёта * @var ItemArray Массив предметов расчёта
*/ */
protected $items; protected ItemArray $items;
/** /**
* @var \AtolOnline\Entities\VatArray Массив ставок НДС * @var VatArray Массив ставок НДС
*/ */
protected $vats; protected VatArray $vats;
/** /**
* @var \AtolOnline\Entities\PaymentArray Массив оплат * @var PaymentArray Массив оплат
*/ */
protected $payments; protected PaymentArray $payments;
/** /**
* @var \AtolOnline\Entities\Company Объект компании (продавца) * @var Company Объект компании (продавца)
*/ */
protected $company; protected Company $company;
/** /**
* @var \AtolOnline\Entities\Client Объект клиента (покупателя) * @var Client Объект клиента (покупателя)
*/ */
protected $client; protected Client $client;
/** /**
* @var int Итоговая сумма чека. Тег ФФД - 1020. * @var float Итоговая сумма чека. Тег ФФД - 1020.
*/ */
protected $total = 0; protected float $total = 0;
/** /**
* @var string ФИО кассира. Тег ФФД - 1021. * @var string ФИО кассира. Тег ФФД - 1021.
*/ */
protected $cashier; protected string $cashier;
/** /**
* @var \AtolOnline\Entities\CorrectionInfo Данные коррекции * @var CorrectionInfo Данные коррекции
*/ */
protected $correction_info; protected CorrectionInfo $correction_info;
/** /**
* Document constructor. * Document constructor.
@@ -69,66 +86,65 @@ class Document extends Entity
$this->payments = new PaymentArray(); $this->payments = new PaymentArray();
$this->items = new ItemArray(); $this->items = new ItemArray();
} }
/** /**
* Удаляет все налоги из документа и предметов расчёта * Удаляет все налоги из документа и предметов расчёта
* *
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма * @throws TooManyVatsException Слишком много ставок НДС
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
*/ */
public function clearVats() public function clearVats(): Document
{ {
$this->setVats([]); $this->setVats([]);
return $this; return $this;
} }
/** /**
* Добавляет новую ставку НДС в массив ставок НДС * Добавляет новую ставку НДС в массив ставок НДС
* *
* @param \AtolOnline\Entities\Vat $vat Объект ставки НДС * @param Vat $vat Объект ставки НДС
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
*/ */
public function addVat(Vat $vat) public function addVat(Vat $vat): Document
{ {
$this->vats->add($vat); $this->vats->add($vat);
return $this; return $this;
} }
/** /**
* Возвращает массив ставок НДС * Возвращает массив ставок НДС
* *
* @return \AtolOnline\Entities\Vat[] * @return Vat[]
*/ */
public function getVats(): array public function getVats(): array
{ {
return $this->vats->get(); return $this->vats->get();
} }
/** /**
* Устанавливает массив ставок НДС * Устанавливает массив ставок НДС
* *
* @param \AtolOnline\Entities\Vat[] $vats Массив ставок НДС * @param Vat[] $vats Массив ставок НДС
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
* @throws \Exception * @throws Exception
*/ */
public function setVats(array $vats) public function setVats(array $vats): Document
{ {
$this->vats->set($vats); $this->vats->set($vats);
return $this; return $this;
} }
/** /**
* Добавляет новую оплату в массив оплат * Добавляет новую оплату в массив оплат
* *
* @param \AtolOnline\Entities\Payment $payment Объект оплаты * @param Payment $payment Объект оплаты
* @return $this * @return $this
* @throws \Exception * @throws Exception
* @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
public function addPayment(Payment $payment) public function addPayment(Payment $payment): Document
{ {
if (count($this->getPayments()) == 0 && !$payment->getSum()) { if (count($this->getPayments()) == 0 && !$payment->getSum()) {
$payment->setSum($this->calcTotal()); $payment->setSum($this->calcTotal());
@@ -136,61 +152,61 @@ class Document extends Entity
$this->payments->add($payment); $this->payments->add($payment);
return $this; return $this;
} }
/** /**
* Возвращает массив оплат * Возвращает массив оплат
* *
* @return \AtolOnline\Entities\Payment[] * @return Payment[]
*/ */
public function getPayments(): array public function getPayments(): array
{ {
return $this->payments->get(); return $this->payments->get();
} }
/** /**
* Устанавливает массив оплат * Устанавливает массив оплат
* *
* @param \AtolOnline\Entities\Payment[] $payments Массив оплат * @param Payment[] $payments Массив оплат
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
public function setPayments(array $payments) public function setPayments(array $payments): Document
{ {
$this->payments->set($payments); $this->payments->set($payments);
return $this; return $this;
} }
/** /**
* Добавляет новый предмет расчёта в массив предметов расчёта * Добавляет новый предмет расчёта в массив предметов расчёта
* *
* @param \AtolOnline\Entities\Item $item Объект предмета расчёта * @param Item $item Объект предмета расчёта
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
public function addItem(Item $item) public function addItem(Item $item): Document
{ {
$this->items->add($item); $this->items->add($item);
return $this; return $this;
} }
/** /**
* Возвращает массив предметов расчёта * Возвращает массив предметов расчёта
* *
* @return \AtolOnline\Entities\Item[] * @return Item[]
*/ */
public function getItems(): array public function getItems(): array
{ {
return $this->items->get(); return $this->items->get();
} }
/** /**
* Устанавливает массив предметов расчёта * Устанавливает массив предметов расчёта
* *
* @param \AtolOnline\Entities\Item[] $items Массив предметов расчёта * @param Item[] $items Массив предметов расчёта
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
public function setItems(array $items) public function setItems(array $items): Document
{ {
$this->items->set($items); $this->items->set($items);
return $this; return $this;
@@ -212,7 +228,7 @@ class Document extends Entity
* @param Client|null $client * @param Client|null $client
* @return $this * @return $this
*/ */
public function setClient(?Client $client) public function setClient(?Client $client): Document
{ {
$this->client = $client; $this->client = $client;
return $this; return $this;
@@ -234,7 +250,7 @@ class Document extends Entity
* @param Company|null $company * @param Company|null $company
* @return $this * @return $this
*/ */
public function setCompany(?Company $company) public function setCompany(?Company $company): Document
{ {
$this->company = $company; $this->company = $company;
return $this; return $this;
@@ -249,53 +265,55 @@ class Document extends Entity
{ {
return $this->cashier; return $this->cashier;
} }
/** /**
* Устанавливает ФИО кассира. Тег ФФД - 1021. * Устанавливает ФИО кассира. Тег ФФД - 1021.
* *
* @param string|null $cashier * @param string|null $cashier
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolCashierTooLongException * @throws TooLongCashierException
*/ */
public function setCashier(?string $cashier) public function setCashier(?string $cashier): Document
{ {
$cashier = trim($cashier); if ($cashier !== null) {
if ((function_exists('mb_strlen') ? mb_strlen($cashier) : strlen($cashier)) > 64) { $cashier = trim($cashier);
throw new AtolCashierTooLongException($cashier, 64); if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
throw new TooLongCashierException($cashier, Constraints::MAX_LENGTH_CASHIER_NAME);
}
} }
$this->cashier = $cashier; $this->cashier = $cashier;
return $this; return $this;
} }
/** /**
* Возвращает данные коррекции * Возвращает данные коррекции
* *
* @return \AtolOnline\Entities\CorrectionInfo|null * @return CorrectionInfo|null
*/ */
public function getCorrectionInfo(): ?CorrectionInfo public function getCorrectionInfo(): ?CorrectionInfo
{ {
return $this->correction_info; return $this->correction_info;
} }
/** /**
* Устанавливает данные коррекции * Устанавливает данные коррекции
* *
* @param \AtolOnline\Entities\CorrectionInfo|null $correction_info * @param CorrectionInfo|null $correction_info
* @return $this * @return $this
*/ */
public function setCorrectionInfo(?CorrectionInfo $correction_info) public function setCorrectionInfo(?CorrectionInfo $correction_info): Document
{ {
$this->correction_info = $correction_info; $this->correction_info = $correction_info;
return $this; return $this;
} }
/** /**
* Пересчитывает, сохраняет и возвращает итоговую сумму чека по всем позициям (включая НДС). Тег ФФД - 1020. * Пересчитывает, сохраняет и возвращает итоговую сумму чека по всем позициям (включая НДС). Тег ФФД - 1020.
* *
* @return float * @return float
* @throws \Exception * @throws Exception
*/ */
public function calcTotal() public function calcTotal(): float
{ {
$sum = 0; $sum = 0;
$this->clearVats(); $this->clearVats();
@@ -315,32 +333,33 @@ class Document extends Entity
{ {
return $this->total; return $this->total;
} }
/** /**
* Собирает объект документа из сырой json-строки * Собирает объект документа из сырой json-строки
* *
* @param string $json * @param string $json
* @return \AtolOnline\Entities\Document * @return Document
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException * @throws TooLongEmailException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException * @throws InvalidEmailException
* @throws \AtolOnline\Exceptions\AtolException * @throws AtolException
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException * @throws InvalidInnLengthException
* @throws \AtolOnline\Exceptions\AtolInvalidJsonException * @throws InvalidJsonException
* @throws \AtolOnline\Exceptions\AtolNameTooLongException * @throws TooLongNameException
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException * @throws TooLongPaymentAddressException
* @throws \AtolOnline\Exceptions\AtolPhoneTooLongException * @throws TooLongPhoneException
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException * @throws TooHighPriceException
* @throws \AtolOnline\Exceptions\AtolTooManyException * @throws BasicTooManyException
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException * @throws TooManyItemsException
* @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException * @throws TooManyPaymentsException
* @throws \AtolOnline\Exceptions\AtolUnitTooLongException * @throws TooLongUnitException
* @throws \AtolOnline\Exceptions\AtolUserdataTooLongException * @throws TooLongUserdataException
* @throws Exception
*/ */
public static function fromRaw(string $json) public static function fromRaw(string $json): Document
{ {
$array = json_decode($json, true); $array = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) { if (json_last_error() !== JSON_ERROR_NONE) {
throw new AtolInvalidJsonException(); throw new InvalidJsonException();
} }
$doc = new self(); $doc = new self();
if (isset($array['company'])) { if (isset($array['company'])) {
@@ -359,6 +378,14 @@ class Document extends Entity
$array['client']['inn'] ?? null $array['client']['inn'] ?? null
)); ));
} }
if (isset($array['correction_info'])) {
$doc->setCorrectionInfo(new CorrectionInfo(
$array['correction_info']['type'] ?? null,
$array['correction_info']['base_date'] ?? null,
$array['correction_info']['base_number'] ?? null,
$array['correction_info']['base_name'] ?? null,
));
}
if (isset($array['items'])) { if (isset($array['items'])) {
foreach ($array['items'] as $ar_item) { foreach ($array['items'] as $ar_item) {
$item = new Item( $item = new Item(
@@ -388,18 +415,30 @@ class Document extends Entity
$doc->payments->add($payment); $doc->payments->add($payment);
} }
} }
if (isset($array['vats'])) {
foreach ($array['vats'] as $vat_payment) {
$vat = new Vat();
if (isset($vat_payment['type'])) {
$vat->setType($vat_payment['type']);
}
if (isset($vat_payment['sum'])) {
$vat->setSum($vat_payment['sum']);
}
$doc->vats->add($vat);
}
}
if (isset($array['total']) && $array['total'] != $doc->calcTotal()) { if (isset($array['total']) && $array['total'] != $doc->calcTotal()) {
throw new AtolException('Real total sum not equals to provided in JSON one'); throw new AtolException('Real total sum not equals to provided in JSON one');
} }
return $doc; return $doc;
} }
/** /**
* Возвращает массив для кодирования в json * Возвращает массив для кодирования в json
* *
* @throws \Exception * @throws Exception
*/ */
public function jsonSerialize() public function jsonSerialize(): array
{ {
if ($this->getCompany()) { if ($this->getCompany()) {
$json['company'] = $this->getCompany()->jsonSerialize(); // обязательно $json['company'] = $this->getCompany()->jsonSerialize(); // обязательно

View File

@@ -1,25 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use JsonSerializable; use JsonSerializable;
use Stringable;
/** /**
* Абстрактное описание любой сущности, представляемой как JSON * Абстрактное описание любой сущности, представляемой как json
*
* @package AtolOnline\Entities
*/ */
abstract class Entity implements JsonSerializable abstract class Entity implements JsonSerializable, Stringable
{ {
/** /**
* @inheritDoc * Возвращает строковое представление json-структуры объекта
*
* @return false|string
*/ */
public function __toString() public function __toString()
{ {

View File

@@ -1,20 +1,23 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\{Exceptions\AtolNameTooLongException, use AtolOnline\{
Exceptions\AtolPriceTooHighException, Constants\Constraints,
Exceptions\AtolTooManyException, Exceptions\BasicTooManyException,
Exceptions\AtolUnitTooLongException, Exceptions\TooHighPriceException,
Exceptions\AtolUserdataTooLongException, Exceptions\TooLongNameException,
Traits\RublesKopeksConverter}; Exceptions\TooLongUnitException,
Exceptions\TooLongUserdataException};
/** /**
* Предмет расчёта (товар, услуга) * Предмет расчёта (товар, услуга)
@@ -23,74 +26,72 @@ use AtolOnline\{Exceptions\AtolNameTooLongException,
*/ */
class Item extends Entity class Item extends Entity
{ {
use RublesKopeksConverter;
/** /**
* @var string Наименование. Тег ФФД - 1030. * @var string Наименование. Тег ФФД - 1030.
*/ */
protected $name; protected string $name;
/** /**
* @var int Цена в копейках (с учётом скидок и наценок). Тег ФФД - 1079. * @var int Цена в копейках (с учётом скидок и наценок). Тег ФФД - 1079.
*/ */
protected $price = 0; protected int $price = 0;
/** /**
* @var float Количество, вес. Тег ФФД - 1023. * @var float Количество, вес. Тег ФФД - 1023.
*/ */
protected $quantity = 0.0; protected float $quantity = 0.0;
/** /**
* @var float Сумма в копейках. Тег ФФД - 1043. * @var float Сумма в копейках. Тег ФФД - 1043.
*/ */
protected $sum = 0; protected float $sum = 0;
/** /**
* @var string Единица измерения количества. Тег ФФД - 1197. * @var string Единица измерения количества. Тег ФФД - 1197.
*/ */
protected $measurement_unit; protected string $measurement_unit;
/** /**
* @var Vat Ставка НДС * @var Vat|null Ставка НДС
*/ */
protected $vat; protected ?Vat $vat;
/** /**
* @var string Признак способа расчёта. Тег ФФД - 1214. * @var string Признак способа расчёта. Тег ФФД - 1214.
*/ */
protected $payment_method; protected string $payment_method;
/** /**
* @var string Признак объекта расчёта. Тег ФФД - 1212. * @var string Признак объекта расчёта. Тег ФФД - 1212.
*/ */
protected $payment_object; protected string $payment_object;
/** /**
* @var string Дополнительный реквизит. Тег ФФД - 1191. * @var string Дополнительный реквизит. Тег ФФД - 1191.
*/ */
protected $user_data; protected string $user_data;
/** /**
* Item constructor. * Item constructor.
* *
* @param string|null $name Наименование * @param string|null $name Наименование
* @param float|null $price Цена за одну единицу * @param float|null $price Цена за одну единицу
* @param float|null $quantity Количество * @param float|null $quantity Количество
* @param string|null $measurement_unit Единица измерения * @param string|null $measurement_unit Единица измерения
* @param string|null $vat_type Ставка НДС * @param string|null $vat_type Ставка НДС
* @param string|null $payment_object Признак * @param string|null $payment_object Признак
* @param string|null $payment_method Способ расчёта * @param string|null $payment_method Способ расчёта
* @throws AtolNameTooLongException Слишком длинное наименование * @throws TooLongNameException Слишком длинное наименование
* @throws AtolPriceTooHighException Слишком высокая цена за одну единицу * @throws TooHighPriceException Слишком высокая цена за одну единицу
* @throws AtolTooManyException Слишком большое количество * @throws BasicTooManyException Слишком большое количество
* @throws AtolUnitTooLongException Слишком длинное название единицы измерения * @throws TooLongUnitException Слишком длинное название единицы измерения
*/ */
public function __construct( public function __construct(
?string $name = null, ?string $name = null,
?float $price = null, ?float $price = null,
?float $quantity = null, ?float $quantity = null,
?string $measurement_unit = null, ?string $measurement_unit = null,
$vat_type = null, ?string $vat_type = null,
?string $payment_object = null, ?string $payment_object = null,
?string $payment_method = null ?string $payment_method = null
) { ) {
@@ -122,23 +123,23 @@ class Item extends Entity
* *
* @return string * @return string
*/ */
public function getName() public function getName(): string
{ {
return $this->name; return $this->name;
} }
/** /**
* Устаналивает наименование. Тег ФФД - 1030. * Устаналивает наименование. Тег ФФД - 1030.
* *
* @param string $name Наименование * @param string $name Наименование
* @return $this * @return $this
* @throws AtolNameTooLongException Слишком длинное имя/наименование * @throws TooLongNameException Слишком длинное имя/наименование
*/ */
public function setName(string $name) public function setName(string $name): self
{ {
$name = trim($name); $name = trim($name);
if ((function_exists('mb_strlen') ? mb_strlen($name) : strlen($name)) > 128) { if (mb_strlen($name) > Constraints::MAX_LENGTH_ITEM_NAME) {
throw new AtolNameTooLongException($name, 128); throw new TooLongNameException($name, Constraints::MAX_LENGTH_ITEM_NAME);
} }
$this->name = $name; $this->name = $name;
return $this; return $this;
@@ -149,24 +150,24 @@ class Item extends Entity
* *
* @return float * @return float
*/ */
public function getPrice() public function getPrice(): float
{ {
return self::toRub($this->price); return rubles($this->price);
} }
/** /**
* Устанавливает цену в рублях. Тег ФФД - 1079. * Устанавливает цену в рублях. Тег ФФД - 1079.
* *
* @param float $rubles Цена за одну единицу в рублях * @param float $rubles Цена за одну единицу в рублях
* @return $this * @return $this
* @throws AtolPriceTooHighException Слишком высокая цена за одну единицу * @throws TooHighPriceException Слишком высокая цена за одну единицу
*/ */
public function setPrice(float $rubles) public function setPrice(float $rubles): Item
{ {
if ($rubles > 42949672.95) { if ($rubles > 42949672.95) {
throw new AtolPriceTooHighException($rubles, 42949672.95); throw new TooHighPriceException($rubles, 42949672.95);
} }
$this->price = self::toKop($rubles); $this->price = kopeks($rubles);
$this->calcSum(); $this->calcSum();
return $this; return $this;
} }
@@ -180,22 +181,22 @@ class Item extends Entity
{ {
return $this->quantity; return $this->quantity;
} }
/** /**
* Устанавливает количество. Тег ФФД - 1023. * Устанавливает количество. Тег ФФД - 1023.
* *
* @param float $quantity Количество * @param float $quantity Количество
* @param string|null $measurement_unit Единица измерения количества * @param string|null $measurement_unit Единица измерения количества
* @return $this * @return $this
* @throws AtolTooManyException Слишком большое количество * @throws BasicTooManyException Слишком большое количество
* @throws AtolPriceTooHighException Слишком высокая общая стоимость * @throws TooHighPriceException Слишком высокая общая стоимость
* @throws AtolUnitTooLongException Слишком длинное название единицы измерения * @throws TooLongUnitException Слишком длинное название единицы измерения
*/ */
public function setQuantity(float $quantity, string $measurement_unit = null) public function setQuantity(float $quantity, string $measurement_unit = null): Item
{ {
$quantity = round($quantity, 3); $quantity = round($quantity, 3);
if ($quantity > 99999.999) { if ($quantity > 99999.999) {
throw new AtolTooManyException($quantity, 99999.999); throw new BasicTooManyException($quantity, 99999.999);
} }
$this->quantity = $quantity; $this->quantity = $quantity;
$this->calcSum(); $this->calcSum();
@@ -214,19 +215,19 @@ class Item extends Entity
{ {
return $this->measurement_unit; return $this->measurement_unit;
} }
/** /**
* Устанавливает единицу измерения количества. Тег ФФД - 1197. * Устанавливает единицу измерения количества. Тег ФФД - 1197.
* *
* @param string $measurement_unit Единица измерения количества * @param string $measurement_unit Единица измерения количества
* @return $this * @return $this
* @throws AtolUnitTooLongException Слишком длинное название единицы измерения * @throws TooLongUnitException Слишком длинное название единицы измерения
*/ */
public function setMeasurementUnit(string $measurement_unit) public function setMeasurementUnit(string $measurement_unit): Item
{ {
$measurement_unit = trim($measurement_unit); $measurement_unit = trim($measurement_unit);
if ((function_exists('mb_strlen') ? mb_strlen($measurement_unit) : strlen($measurement_unit)) > 16) { if (mb_strlen($measurement_unit) > Constraints::MAX_LENGTH_MEASUREMENT_UNIT) {
throw new AtolUnitTooLongException($measurement_unit, 16); throw new TooLongUnitException($measurement_unit, Constraints::MAX_LENGTH_MEASUREMENT_UNIT);
} }
$this->measurement_unit = $measurement_unit; $this->measurement_unit = $measurement_unit;
return $this; return $this;
@@ -249,7 +250,7 @@ class Item extends Entity
* @return $this * @return $this
* @todo Проверка допустимых значений * @todo Проверка допустимых значений
*/ */
public function setPaymentMethod(string $payment_method) public function setPaymentMethod(string $payment_method): Item
{ {
$this->payment_method = trim($payment_method); $this->payment_method = trim($payment_method);
return $this; return $this;
@@ -272,30 +273,30 @@ class Item extends Entity
* @return $this * @return $this
* @todo Проверка допустимых значений * @todo Проверка допустимых значений
*/ */
public function setPaymentObject(string $payment_object) public function setPaymentObject(string $payment_object): Item
{ {
$this->payment_object = trim($payment_object); $this->payment_object = trim($payment_object);
return $this; return $this;
} }
/** /**
* Возвращает ставку НДС * Возвращает ставку НДС
* *
* @return \AtolOnline\Entities\Vat|null * @return Vat|null
*/ */
public function getVat(): ?Vat public function getVat(): ?Vat
{ {
return $this->vat; return $this->vat;
} }
/** /**
* Устанавливает ставку НДС * Устанавливает ставку НДС
* *
* @param string|null $vat_type Тип ставки НДС. Передать null, чтобы удалить ставку. * @param string|null $vat_type Тип ставки НДС. Передать null, чтобы удалить ставку.
* @return $this * @return $this
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException * @throws TooHighPriceException
*/ */
public function setVatType(?string $vat_type) public function setVatType(?string $vat_type): Item
{ {
if ($vat_type) { if ($vat_type) {
$this->vat $this->vat
@@ -317,19 +318,19 @@ class Item extends Entity
{ {
return $this->user_data; return $this->user_data;
} }
/** /**
* Устанавливает дополнительный реквизит. Тег ФФД - 1191. * Устанавливает дополнительный реквизит. Тег ФФД - 1191.
* *
* @param string $user_data Дополнительный реквизит. Тег ФФД - 1191. * @param string $user_data Дополнительный реквизит. Тег ФФД - 1191.
* @return $this * @return $this
* @throws AtolUserdataTooLongException Слишком длинный дополнительный реквизит * @throws TooLongUserdataException Слишком длинный дополнительный реквизит
*/ */
public function setUserData(string $user_data) public function setUserData(string $user_data): self
{ {
$user_data = trim($user_data); $user_data = trim($user_data);
if ((function_exists('mb_strlen') ? mb_strlen($user_data) : strlen($user_data)) > 64) { if (mb_strlen($user_data) > Constraints::MAX_LENGTH_USER_DATA) {
throw new AtolUserdataTooLongException($user_data, 64); throw new TooLongUserdataException($user_data, Constraints::MAX_LENGTH_USER_DATA);
} }
$this->user_data = $user_data; $this->user_data = $user_data;
return $this; return $this;
@@ -342,24 +343,24 @@ class Item extends Entity
*/ */
public function getSum(): float public function getSum(): float
{ {
return self::toRub($this->sum); return rubles($this->sum);
} }
/** /**
* Расчитывает стоимость и размер НДС на неё * Расчитывает стоимость и размер НДС на неё
* *
* @return float * @return float
* @throws AtolPriceTooHighException Слишком большая сумма * @throws TooHighPriceException Слишком большая сумма
*/ */
public function calcSum() public function calcSum(): float
{ {
$sum = $this->quantity * $this->price; $sum = $this->quantity * $this->price;
if (self::toRub($sum) > 42949672.95) { if (rubles($sum) > 42949672.95) {
throw new AtolPriceTooHighException($sum, 42949672.95); throw new TooHighPriceException($sum, 42949672.95);
} }
$this->sum = $sum; $this->sum = $sum;
if ($this->vat) { if ($this->vat) {
$this->vat->setSum(self::toRub($sum)); $this->vat->setSum(rubles($sum));
} }
return $this->getSum(); return $this->getSum();
} }

View File

@@ -1,15 +1,17 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolTooManyItemsException; use AtolOnline\Exceptions\TooManyItemsException;
/** /**
* Класс, описывающий массив предметов расчёта * Класс, описывающий массив предметов расчёта
@@ -28,13 +30,13 @@ class ItemArray extends Entity
/** /**
* @var Item[] Массив предметов расчёта * @var Item[] Массив предметов расчёта
*/ */
private $items = []; private array $items = [];
/** /**
* ItemArray constructor. * ItemArray constructor.
* *
* @param Item[]|null $items Массив предметов расчёта * @param Item[]|null $items Массив предметов расчёта
* @throws AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
public function __construct(?array $items = null) public function __construct(?array $items = null)
{ {
@@ -42,30 +44,30 @@ class ItemArray extends Entity
$this->set($items); $this->set($items);
} }
} }
/** /**
* Устанавливает массив предметов расчёта * Устанавливает массив предметов расчёта
* *
* @param Item[] $items Массив предметов расчёта * @param Item[] $items Массив предметов расчёта
* @return $this * @return $this
* @throws AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
public function set(array $items) public function set(array $items): ItemArray
{ {
if ($this->validateCount($items)) { if ($this->validateCount($items)) {
$this->items = $items; $this->items = $items;
} }
return $this; return $this;
} }
/** /**
* Добавляет предмет расчёта в массив * Добавляет предмет расчёта в массив
* *
* @param Item $item Объект предмета расчёта * @param Item $item Объект предмета расчёта
* @return $this * @return $this
* @throws AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
public function add(Item $item) public function add(Item $item): ItemArray
{ {
if ($this->validateCount()) { if ($this->validateCount()) {
$this->items[] = $item; $this->items[] = $item;
@@ -78,7 +80,7 @@ class ItemArray extends Entity
* *
* @return Item[] * @return Item[]
*/ */
public function get() public function get(): array
{ {
return $this->items; return $this->items;
} }
@@ -94,19 +96,19 @@ class ItemArray extends Entity
} }
return $result; return $result;
} }
/** /**
* Проверяет количество предметов расчёта * Проверяет количество предметов расчёта
* *
* @param Item[]|null $items Если передать массив, то проверит количество его элементов. * @param Item[]|null $items Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов. * Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение * @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyItemsException Слишком много предметов расчёта * @throws TooManyItemsException Слишком много предметов расчёта
*/ */
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 TooManyItemsException(count($items), self::MAX_COUNT);
} }
return true; return true;
} }

View File

@@ -1,12 +1,14 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Constants\PaymentTypes; use AtolOnline\Constants\PaymentTypes;
@@ -21,12 +23,12 @@ class Payment extends Entity
/** /**
* @var int Тип оплаты * @var int Тип оплаты
*/ */
protected $type; protected int $type;
/** /**
* @var float Сумма оплаты * @var float Сумма оплаты
*/ */
protected $sum; protected float $sum;
/** /**
* Payment constructor. * Payment constructor.
@@ -56,7 +58,7 @@ class Payment extends Entity
* @param int $type * @param int $type
* @return $this * @return $this
*/ */
public function setType(int $type) public function setType(int $type): Payment
{ {
$this->type = $type; $this->type = $type;
return $this; return $this;
@@ -78,7 +80,7 @@ class Payment extends Entity
* @param float $sum * @param float $sum
* @return $this * @return $this
*/ */
public function setSum(float $sum) public function setSum(float $sum): Payment
{ {
$this->sum = $sum; $this->sum = $sum;
return $this; return $this;

View File

@@ -1,16 +1,17 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Api\SellSchema; use AtolOnline\Exceptions\TooManyPaymentsException;
use AtolOnline\Exceptions\AtolTooManyPaymentsException;
/** /**
* Класс, описывающий массив оплат * Класс, описывающий массив оплат
@@ -19,16 +20,21 @@ use AtolOnline\Exceptions\AtolTooManyPaymentsException;
*/ */
class PaymentArray extends Entity class PaymentArray extends Entity
{ {
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 10;
/** /**
* @var Payment[] Массив оплат * @var Payment[] Массив оплат
*/ */
private $payments = []; private array $payments = [];
/** /**
* ItemArray constructor. * ItemArray constructor.
* *
* @param Payment[]|null $payments Массив оплат * @param Payment[]|null $payments Массив оплат
* @throws AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
public function __construct(?array $payments = null) public function __construct(?array $payments = null)
{ {
@@ -36,30 +42,30 @@ class PaymentArray extends Entity
$this->set($payments); $this->set($payments);
} }
} }
/** /**
* Устанавливает массив оплат * Устанавливает массив оплат
* *
* @param Payment[] $payments * @param Payment[] $payments
* @return $this * @return $this
* @throws AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
public function set(array $payments) public function set(array $payments): PaymentArray
{ {
if ($this->validateCount($payments)) { if ($this->validateCount($payments)) {
$this->payments = $payments; $this->payments = $payments;
} }
return $this; return $this;
} }
/** /**
* Добавляет новую оплату к заданным * Добавляет новую оплату к заданным
* *
* @param Payment $payment Объект оплаты * @param Payment $payment Объект оплаты
* @return $this * @return $this
* @throws AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
public function add(Payment $payment) public function add(Payment $payment): PaymentArray
{ {
if ($this->validateCount()) { if ($this->validateCount()) {
$this->payments[] = $payment; $this->payments[] = $payment;
@@ -72,7 +78,7 @@ class PaymentArray extends Entity
* *
* @return Payment[] * @return Payment[]
*/ */
public function get() public function get(): array
{ {
return $this->payments; return $this->payments;
} }
@@ -88,20 +94,19 @@ class PaymentArray extends Entity
} }
return $result; return $result;
} }
/** /**
* Проверяет количество налоговых ставок * Проверяет количество налоговых ставок
* *
* @param Payment[]|null $payments Если передать массив, то проверит количество его элементов. * @param Payment[]|null $payments Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов. * Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение * @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyPaymentsException Слишком много оплат * @throws TooManyPaymentsException Слишком много оплат
*/ */
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 TooManyPaymentsException(count($payments), self::MAX_COUNT);
throw new AtolTooManyPaymentsException($max_items);
} }
return true; return true;
} }

View File

@@ -1,15 +1,17 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\{Constants\VatTypes, Traits\RublesKopeksConverter}; use AtolOnline\Constants\VatTypes;
/** /**
* Класс, описывающий ставку НДС * Класс, описывающий ставку НДС
@@ -18,22 +20,20 @@ use AtolOnline\{Constants\VatTypes, Traits\RublesKopeksConverter};
*/ */
class Vat extends Entity class Vat extends Entity
{ {
use RublesKopeksConverter;
/** /**
* @var string Выбранный тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106. * @var string Выбранный тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
*/ */
private $type; private string $type;
/** /**
* @var int Сумма в копейках, от которой пересчитывается размер НДС * @var int Сумма в копейках, от которой пересчитывается размер НДС
*/ */
private $sum_original = 0; private int $sum_original = 0;
/** /**
* @var int Сумма НДС в копейках * @var int Сумма НДС в копейках
*/ */
private $sum_final = 0; private int $sum_final = 0;
/** /**
* Vat constructor. * Vat constructor.
@@ -50,7 +50,8 @@ class Vat extends Entity
} }
/** /**
* Устанавливает размер НДС от суммы в копейках * Устанавливает:
* размер НДС от суммы в копейках
* *
* @param string $type Тип ставки НДС * @param string $type Тип ставки НДС
* @param int $kopeks Копейки * @param int $kopeks Копейки
@@ -98,7 +99,7 @@ class Vat extends Entity
* @param string $type Тип ставки НДС * @param string $type Тип ставки НДС
* @return $this * @return $this
*/ */
public function setType(string $type) public function setType(string $type): Vat
{ {
$this->type = $type; $this->type = $type;
$this->setFinal(); $this->setFinal();
@@ -110,9 +111,9 @@ class Vat extends Entity
* *
* @return float * @return float
*/ */
public function getFinalSum() public function getFinalSum(): float
{ {
return self::toRub($this->sum_final); return rubles($this->sum_final);
} }
/** /**
@@ -122,9 +123,9 @@ class Vat extends Entity
* @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС * @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС
* @return $this * @return $this
*/ */
public function setSum(float $rubles) public function setSum(float $rubles): Vat
{ {
$this->sum_original = self::toKop($rubles); $this->sum_original = kopeks($rubles);
$this->setFinal(); $this->setFinal();
return $this; return $this;
} }
@@ -136,7 +137,7 @@ class Vat extends Entity
*/ */
public function getSum(): float public function getSum(): float
{ {
return self::toRub($this->sum_original); return rubles($this->sum_original);
} }
/** /**
@@ -146,9 +147,9 @@ class Vat extends Entity
* @param float $rubles * @param float $rubles
* @return $this * @return $this
*/ */
public function addSum(float $rubles) public function addSum(float $rubles): Vat
{ {
$this->sum_original += self::toKop($rubles); $this->sum_original += kopeks($rubles);
$this->setFinal(); $this->setFinal();
return $this; return $this;
} }
@@ -162,7 +163,7 @@ class Vat extends Entity
*/ */
public function calc(float $rubles): float public function calc(float $rubles): float
{ {
return self::toRub(self::calculator($this->type, self::toKop($rubles))); return rubles(self::calculator($this->type, kopeks($rubles)));
} }
/** /**
@@ -179,7 +180,7 @@ class Vat extends Entity
/** /**
* Расчитывает и устанавливает итоговый размер ставки от исходной суммы в копейках * Расчитывает и устанавливает итоговый размер ставки от исходной суммы в копейках
*/ */
protected function setFinal() protected function setFinal(): Vat
{ {
$this->sum_final = self::calculator($this->type, $this->sum_original); $this->sum_final = self::calculator($this->type, $this->sum_original);
return $this; return $this;

View File

@@ -1,16 +1,17 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Api\SellSchema; use AtolOnline\Exceptions\TooManyVatsException;
use AtolOnline\Exceptions\AtolTooManyVatsException;
/** /**
* Класс, описывающий массив ставок НДС * Класс, описывающий массив ставок НДС
@@ -19,16 +20,21 @@ use AtolOnline\Exceptions\AtolTooManyVatsException;
*/ */
class VatArray extends Entity class VatArray extends Entity
{ {
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 6;
/** /**
* @var Vat[] Массив ставок НДС * @var Vat[] Массив ставок НДС
*/ */
private $vats = []; private array $vats = [];
/** /**
* VatArray constructor. * VatArray constructor.
* *
* @param Vat[]|null $vats Массив ставок НДС * @param Vat[]|null $vats Массив ставок НДС
* @throws AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
*/ */
public function __construct(?array $vats = null) public function __construct(?array $vats = null)
{ {
@@ -36,30 +42,30 @@ class VatArray extends Entity
$this->set($vats); $this->set($vats);
} }
} }
/** /**
* Устанавливает массив ставок НДС * Устанавливает массив ставок НДС
* *
* @param Vat[] $vats Массив ставок НДС * @param Vat[] $vats Массив ставок НДС
* @return $this * @return $this
* @throws AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
*/ */
public function set(array $vats) public function set(array $vats): VatArray
{ {
if ($this->validateCount($vats)) { if ($this->validateCount($vats)) {
$this->vats = $vats; $this->vats = $vats;
} }
return $this; return $this;
} }
/** /**
* Добавляет новую ставку НДС в массив * Добавляет новую ставку НДС в массив
* *
* @param Vat $vat Объект ставки НДС * @param Vat $vat Объект ставки НДС
* @return $this * @return $this
* @throws AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
*/ */
public function add(Vat $vat) public function add(Vat $vat): VatArray
{ {
if ($this->validateCount()) { if ($this->validateCount()) {
if (isset($this->vats[$vat->getType()])) { if (isset($this->vats[$vat->getType()])) {
@@ -76,7 +82,7 @@ class VatArray extends Entity
* *
* @return Vat[] * @return Vat[]
*/ */
public function get() public function get(): array
{ {
return $this->vats; return $this->vats;
} }
@@ -92,20 +98,19 @@ class VatArray extends Entity
} }
return $result; return $result;
} }
/** /**
* Проверяет количество налоговых ставок * Проверяет количество налоговых ставок
* *
* @param Vat[]|null $vats Если передать массив, то проверит количество его элементов. * @param Vat[]|null $vats Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов. * Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение * @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyVatsException Слишком много ставок НДС * @throws TooManyVatsException Слишком много ставок НДС
*/ */
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 TooManyVatsException(count($vats), self::MAX_COUNT);
throw new AtolTooManyVatsException(count($vats), $max_items);
} }
return true; return true;
} }

View File

@@ -1,12 +1,14 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Exception; use Exception;
@@ -14,37 +16,36 @@ use Throwable;
/** /**
* Исключение, возникающее при работе с АТОЛ Онлайн * Исключение, возникающее при работе с АТОЛ Онлайн
*
* @package AtolOnline\Exceptions
*/ */
class AtolException extends Exception class AtolException extends Exception
{ {
/** /**
* @var int[] Теги ФФД * @var int[] Теги ФФД
*/ */
protected $ffd_tags = null; protected array $ffd_tags = [];
/** /**
* AtolException constructor. * AtolException constructor.
* *
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param \Throwable|null $previous * @param Throwable|null $previous
*/ */
public function __construct($message = "", $code = 0, Throwable $previous = null) public function __construct($message = "", $code = 0, Throwable $previous = null)
{ {
$message = $message ?: $this->message;
if ($this->getFfdTags()) { if ($this->getFfdTags()) {
$message .= ' [FFD tags: '.implode(', ', $this->getFfdTags()).']'; $message .= ' [FFD tags: '.implode(', ', $this->getFfdTags()).']';
} }
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
/** /**
* Возвращает теги ФФД, с которыми связано исключение * Возвращает теги ФФД, с которыми связано исключение
* *
* @return array|null * @return array
*/ */
protected function getFfdTags(): ?array protected function getFfdTags(): array
{ {
return $this->ffd_tags; return $this->ffd_tags;
} }

View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Api\KktResponse;
use Exception;
use Throwable;
/**
* Исключение, возникающее при неудачной авторизации
*/
class AuthFailedException extends Exception
{
/**
* Конструктор
*
* @param KktResponse $response
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(KktResponse $response, $message = "", $code = 0, Throwable $previous = null)
{
$message = $response->isValid()
? $message
: '[' . $response->error->code . '] ' . $response->error->text .
'. ERROR_ID: ' . $response->error->error_id .
'. TYPE: ' . $response->error->type;
$code = $response->isValid() ? $code : $response->error->code;
parent::__construct($message, $code, $previous);
}
}

View File

@@ -1,40 +1,40 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Throwable; use Throwable;
/** /**
* Исключение, возникающее при попытке указать слишком длинное что-либо * Исключение, возникающее при попытке указать слишком длинное что-либо
*
* @package AtolOnline\Exceptions
*/ */
class AtolTooLongException extends AtolException class BasicTooLongException extends AtolException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */
protected $message = 'Parameter is too long'; protected $message = 'Parameter is too long';
/** /**
* AtolTooLongException constructor. * AtolTooLongException constructor.
* *
* @param $string * @param $string
* @param $max * @param $max
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param Throwable|null $previous * @param Throwable|null $previous
*/ */
public function __construct($string, $max, $message = "", $code = 0, Throwable $previous = null) public function __construct($string, $max, $message = "", int $code = 0, Throwable $previous = null)
{ {
parent::__construct($message ?: $this->message.' (max length - '.$max.', actual length - '. parent::__construct($message ?: $this->message . ' (max length - ' . $max . ', actual length - ' .
(function_exists('mb_strlen') ? mb_strlen($string) : strlen($string)).')', $code, $previous); mb_strlen($string), $code, $previous);
} }
} }

View File

@@ -1,40 +1,43 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Throwable; use Throwable;
/** /**
* Исключение, возникающее при попытке указать слишком большое количество чего-либо * Исключение, возникающее при попытке указать слишком большое количество чего-либо
*
* @package AtolOnline\Exceptions
*/ */
class AtolTooManyException extends AtolException class BasicTooManyException extends AtolException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */
protected $message = 'Quantity is too high'; protected $message = 'Quantity is too high';
/** /**
* AtolTooManyException constructor. * AtolTooManyException constructor.
* *
* @param $quantity * @param float $quantity
* @param $max * @param float $max
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param Throwable|null $previous * @param Throwable|null $previous
*/ */
public function __construct($quantity, $max, $message = "", $code = 0, Throwable $previous = null) public function __construct(float $quantity, float $max, $message = "", int $code = 0, Throwable $previous = null)
{ {
$message = $message ?: $this->message.' (max - '.$max.', actual - '.$quantity.')'; parent::__construct(
parent::__construct($message, $code, $previous); $message ?: $this->message . ' (max - ' . $max . ', actual - ' . $quantity . ')',
$code,
$previous
);
} }
} }

View File

@@ -1,20 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке зарегистрировать документ без данных коррекции * Исключение, возникающее при попытке зарегистрировать документ без данных коррекции
*
* @package AtolOnline\Exceptions
*/ */
class AtolCorrectionInfoException extends AtolException class EmptyCorrectionInfoException extends AtolException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке

View File

@@ -1,29 +1,29 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать пустой email * Исключение, возникающее при попытке указать пустой email
*
* @package AtolOnline\Exceptions
*/ */
class AtolEmailEmptyException extends AtolException class EmptyEmailException extends AtolException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1008, 1008,
1117, 1117,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -1,20 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать пустой логин ККТ * Исключение, возникающее при попытке указать пустой логин ККТ
*
* @package AtolOnline\Exceptions
*/ */
class AtolKktLoginEmptyException extends AtolException class EmptyLoginException extends AtolException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке

View File

@@ -1,20 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать пустой пароль ККТ * Исключение, возникающее при попытке указать пустой пароль ККТ
*
* @package AtolOnline\Exceptions
*/ */
class AtolKktPasswordEmptyException extends AtolException class EmptyPasswordException extends AtolException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке

View File

@@ -0,0 +1,23 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать невалидный callback_url
*/
class InvalidCallbackUrlException extends AtolException
{
/**
* @var string Сообщение об ошибке
*/
protected $message = 'Invalid callback URL';
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\DocumentTypes;
use Throwable;
/**
* Исключение, возникающее при попытке указать некорректный тип документа
*/
class InvalidDocumentTypeException extends AtolException
{
/**
* Конструктор
*
* @param string $type
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($type = '', $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct(
$message ?: "Wrong document type: '$type'. Valid ones: " . implode(', ', DocumentTypes::toArray()),
$code,
$previous
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при ошибке валидации email
*/
class InvalidEmailException extends AtolException
{
/**
* @inheritDoc
*/
protected array $ffd_tags = [
1008,
1117,
];
/**
* AtolEmailValidateException constructor.
*
* @param string $email
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $email = '', $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message ?: "Invalid email: '$email'", $code, $previous);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при попытке указать ИНН некорректной длины
*/
class InvalidInnLengthException extends AtolException
{
/**
* @inheritDoc
*/
protected array $ffd_tags = [
1016,
1018,
1226,
1228,
];
/**
* AtolInnWrongLengthException constructor.
*
* @param string $inn
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($inn = '', $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct(
$message ?: 'INN length must be 10 or 12 digits only, actual is ' . strlen($inn),
$code,
$previous
);
}
}

View File

@@ -1,32 +1,36 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Throwable; use Throwable;
/** /**
* Исключение, возникающее при работе с невалидным JSON * Исключение, возникающее при работе с невалидным JSON
*
* @package AtolOnline\Exceptions
*/ */
class AtolInvalidJsonException extends AtolException class InvalidJsonException extends AtolException
{ {
/** /**
* AtolInnWrongLengthException constructor. * AtolInnWrongLengthException constructor.
* *
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param Throwable|null $previous * @param Throwable|null $previous
*/ */
public function __construct($message = "", $code = 0, Throwable $previous = null) public function __construct($message = "", $code = 0, Throwable $previous = null)
{ {
parent::__construct($message ?: 'Invalid JSON: ['.json_last_error().'] '.json_last_error_msg(), $code, $previous); parent::__construct(
$message ?: 'Invalid JSON: [' . json_last_error() . '] ' . json_last_error_msg(),
$code,
$previous
);
} }
} }

View File

@@ -0,0 +1,44 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use Throwable;
/**
* Исключение, возникающее при попытке указать некорректный платёжный адрес
*/
class InvalidPaymentAddressException extends AtolException
{
/**
* @inheritDoc
*/
protected array $ffd_tags = [
1187,
];
/**
* Конструктор
*
* @param string $address
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($address = '', $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct(
$message ?: "Wrong payment address: '$address'",
$code,
$previous
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\SnoTypes;
use Throwable;
/**
* Исключение, возникающее при попытке указать некорректную СНО
*/
class InvalidSnoException extends AtolException
{
/**
* @inheritDoc
*/
protected array $ffd_tags = [
1055,
];
/**
* Конструктор
*
* @param string $sno
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($sno = '', $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct(
$message ?: "Wrong SNO: '$sno'. Valid ones: " . implode(', ', SnoTypes::toArray()),
$code,
$previous
);
}
}

View File

@@ -1,30 +1,30 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Throwable; use Throwable;
/** /**
* Исключение, возникающее при ошибке валидации UUID * Исключение, возникающее при ошибке валидации UUID
*
* @package AtolOnline\Exceptions
*/ */
class AtolInvalidUuidException extends AtolException class InvalidUuidException extends AtolException
{ {
/** /**
* AtolInvalidUuidException constructor. * AtolInvalidUuidException constructor.
* *
* @param $uuid * @param $uuid
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param \Throwable|null $previous * @param Throwable|null $previous
*/ */
public function __construct($uuid, $message = "", $code = 0, Throwable $previous = null) public function __construct($uuid, $message = "", $code = 0, Throwable $previous = null)
{ {

View File

@@ -1,28 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком высокую цену (сумму) * Исключение, возникающее при попытке указать слишком высокую цену (сумму)
*
* @package AtolOnline\Exceptions
*/ */
class AtolPriceTooHighException extends AtolTooManyException class TooHighPriceException extends BasicTooManyException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1079, 1079,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -0,0 +1,23 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать слишком длинный callback_url
*/
class TooLongCallbackUrlException extends BasicTooLongException
{
/**
* @var string Сообщение об ошибке
*/
protected $message = 'Callback URL is too long';
}

View File

@@ -1,28 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинное имя кассира * Исключение, возникающее при попытке указать слишком длинное имя кассира
*
* @package AtolOnline\Exceptions
*/ */
class AtolCashierTooLongException extends AtolTooLongException class TooLongCashierException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1021, 1021,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -1,31 +1,31 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный email * Исключение, возникающее при попытке указать слишком длинный email
*
* @package AtolOnline\Exceptions
*/ */
class AtolEmailTooLongException extends AtolTooLongException class TooLongEmailException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1008, 1008,
1117, 1117,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */
protected $message = 'Email is is too long'; protected $message = 'Email is too long';
} }

View File

@@ -1,20 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный логин ККТ * Исключение, возникающее при попытке указать слишком длинный логин ККТ
*
* @package AtolOnline\Exceptions
*/ */
class AtolKktLoginTooLongException extends AtolTooLongException class TooLongLoginException extends BasicTooLongException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке

View File

@@ -1,25 +1,25 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинное имя * Исключение, возникающее при попытке указать слишком длинное имя
*
* @package AtolOnline\Exceptions
*/ */
class AtolNameTooLongException extends AtolTooLongException class TooLongNameException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1026, 1026,
1030, 1030,
1085, 1085,

View File

@@ -0,0 +1,23 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать слишком длинный пароль ККТ
*/
class TooLongPasswordException extends BasicTooLongException
{
/**
* @var string Сообщение об ошибке
*/
protected $message = 'KKT password is too long';
}

View File

@@ -1,28 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный платёжный адрес * Исключение, возникающее при попытке указать слишком длинный платёжный адрес
*
* @package AtolOnline\Exceptions
*/ */
class AtolPaymentAddressTooLongException extends AtolException class TooLongPaymentAddressException extends AtolException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1187, 1187,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -1,25 +1,25 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный телефон * Исключение, возникающее при попытке указать слишком длинный телефон
*
* @package AtolOnline\Exceptions
*/ */
class AtolPhoneTooLongException extends AtolTooLongException class TooLongPhoneException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1008, 1008,
1073, 1073,
1074, 1074,

View File

@@ -1,28 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный телефон * Исключение, возникающее при попытке указать слишком длинный телефон
*
* @package AtolOnline\Exceptions
*/ */
class AtolUnitTooLongException extends AtolTooLongException class TooLongUnitException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1197, 1197,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -1,28 +1,28 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать слишком длинный дополнительный реквизит * Исключение, возникающее при попытке указать слишком длинный дополнительный реквизит
*
* @package AtolOnline\Exceptions
*/ */
class AtolUserdataTooLongException extends AtolTooLongException class TooLongUserdataException extends BasicTooLongException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1191, 1191,
]; ];
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке
*/ */

View File

@@ -1,20 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке добавить слишком много предметов расчёта в массив * Исключение, возникающее при попытке добавить слишком много предметов расчёта в массив
*
* @package AtolOnline\Exceptions
*/ */
class AtolTooManyItemsException extends AtolTooManyException class TooManyItemsException extends BasicTooManyException
{ {
/** /**
* @var string Сообщение об ошибке * @var string Сообщение об ошибке

View File

@@ -1,25 +1,25 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке добавить слишком много платежей в массив * Исключение, возникающее при попытке добавить слишком много платежей в массив
*
* @package AtolOnline\Exceptions
*/ */
class AtolTooManyPaymentsException extends AtolTooManyException class TooManyPaymentsException extends BasicTooManyException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1031, 1031,
1081, 1081,
1215, 1215,

View File

@@ -1,25 +1,25 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке добавить слишком много ставок НДС в массив * Исключение, возникающее при попытке добавить слишком много ставок НДС в массив
*
* @package AtolOnline\Exceptions
*/ */
class AtolTooManyVatsException extends AtolTooManyException class TooManyVatsException extends BasicTooManyException
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected $ffd_tags = [ protected array $ffd_tags = [
1102, 1102,
1103, 1103,
1104, 1104,

55
src/Helpers.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline;
/**
* Класс с функциями-хелперами
*/
final class Helpers
{
/**
* Конвертирует копейки в рубли, оставляя только 2 знака после запятой
*
* @param int|null $kopeks Копейки
* @return float Рубли
*/
public static function KopToRub(?int $kopeks): float
{
return round(abs((int)$kopeks) / 100, 2);
}
/**
* Конвертирует рубли в копейки, учитывая только 2 знака после запятой
*
* @param float|null $rubles Рубли
* @return int Копейки
*/
public static function RubToKop(?float $rubles): int
{
return (int)round(abs((float)$rubles) * 100, 2);
}
/**
* Генерирует случайную строку указанной длины
*
* @param int $length Длина, по умолчнанию 8
* @param bool $with_digits Включать ли цифры
* @return string
*/
public static function randomStr(int $length = 8, bool $with_digits = true): string
{
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' . ($with_digits ? '0123456789' : '');
$result = '';
for ($i = 0; $i < abs($length); $i++) {
$result .= $alphabet[mt_rand(0, strlen($alphabet) - 1)];
}
return $result;
}
}

58
src/TestEnvParams.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline;
/**
* Константы, определяющие параметры тестовой среды для ФФД 1.05
*
* @see https://online.atol.ru/files/ffd/test_sreda.txt Параметры настройки тестовых сред
*/
class TestEnvParams
{
/**
* Возвращает данные для работы с тестовой средой АТОЛ Онлайн ФФД 1.05
*
* @return string[]
*/
public static function FFD105(): array
{
return [
'endpoint' => 'https://testonline.atol.ru/possystem/v4/',
'company_name' => 'АТОЛ',
'inn' => '5544332219',
'payment_address' => 'https://v4.online.atol.ru',
'group' => 'v4-online-atol-ru_4179',
'login' => 'v4-online-atol-ru',
'password' => 'iGFFuihss',
'endpoint_ofd' => 'https://consumer.1-ofd-test.ru/v1',
];
}
/**
* Возвращает данные для работы с тестовой средой АТОЛ Онлайн ФФД 1.2
*
* @return string[]
*/
public static function FFD12(): array
{
return [
'endpoint' => 'https://testonline.atol.ru/possystem/v5/',
'company_name' => 'АТОЛ',
'inn' => '5544332219',
'payment_address' => 'https://v5.online.atol.ru',
'group' => 'v5-online-atol-ru_5179',
'login' => 'v5-online-atol-ru',
'password' => 'zUr0OxfI',
'endpoint_ofd' => '',
];
}
}

View File

@@ -1,13 +1,20 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
declare(strict_types = 1);
namespace AtolOnlineTests;
use AtolOnline\Entities\Entity; use AtolOnline\Entities\Entity;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Collection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@@ -16,48 +23,170 @@ use PHPUnit\Framework\TestCase;
class BasicTestCase extends TestCase class BasicTestCase extends TestCase
{ {
/** /**
* Проверяет наличие подключения к ресурсу по URL
* *
* @param string $url
* @param int $code
* @return bool
*/ */
public function setUp(): void protected function ping(string $url, int $code): bool
{ {
//parent::setUp(); try {
defined('ATOL_KKT_GROUP') ?: define('ATOL_KKT_GROUP', 'v4-online-atol-ru_4179'); $result = (new Client([
defined('ATOL_KKT_LOGIN') ?: define('ATOL_KKT_LOGIN', 'v4-online-atol-ru'); 'http_errors' => false,
defined('ATOL_KKT_PASS') ?: define('ATOL_KKT_PASS', 'iGFFuihss'); 'timeout' => 3,
defined('ATOL_CALLBACK_URL') ?: define('ATOL_CALLBACK_URL', 'http://example.com/callback'); ]))->request('GET', $url);
} } catch (GuzzleException) {
return false;
/**
* @param Entity $entity
* @return $this
*/
public function checkAtolEntity(Entity $entity)
{
$this->assertJson((string)$entity);
return $this;
}
/**
*
*/
public function tearDown(): void
{
//parent::tearDown();
}
/**
* Возвращает случайную строку указанной длины
*
* @param int $length
* @return string
*/
protected static function randomString($length = 8)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= $characters[mt_rand(0, strlen($characters) - 1)];
} }
return $string; return $result->getStatusCode() === $code;
}
/**
* Проверяет доступность API мониторинга
*
* @return bool
* @throws GuzzleException
*/
protected function isMonitoringOnline(): bool
{
return $this->ping('https://testonline.atol.ru/api/auth/v1/gettoken', 400);
}
/**
* Пропускает текущий тест если API мониторинга недоступно
*
* @throws GuzzleException
*/
protected function skipIfMonitoringIsOffline(): void
{
if (!$this->isMonitoringOnline()) {
$this->markTestSkipped($this->getName() . ': Monitoring API is inaccessible. Skipping test.');
}
}
/**
* Тестирует является ли объект приводимым к json-строке согласно схеме АТОЛ Онлайн
*
* @param Entity $entity
* @param array $json_structure
* @covers \AtolOnline\Entities\Entity::jsonSerialize
* @covers \AtolOnline\Entities\Entity::__toString
*/
public function assertAtolable(Entity $entity, array $json_structure = []): void
{
$this->assertIsObject($entity);
$this->assertIsObject($entity->jsonSerialize());
$this->assertIsString((string)$entity);
$this->assertJson((string)$entity);
if ($json_structure) {
$this->assertEquals(json_encode($json_structure), (string)$entity);
}
}
/**
* Тестирует идентичность двух классов
*
* @param object|string $expected
* @param object|string $actual
*/
public function assertIsSameClass(object|string $expected, object|string $actual)
{
$this->assertEquals(
is_object($expected) ? $expected::class : $expected,
is_object($actual) ? $actual::class : $actual
);
}
/**
* Тестирует наследование класса (объекта) от указанных классов
*
* @param string[] $parents
* @param object|string $actual
*/
public function assertExtendsClasses(array $parents, object|string $actual)
{
$this->checkClassesIntersection($parents, $actual, 'class_parents');
}
/**
* Тестирует имплементацию классом (объектом) указанных интерфейсов
*
* @param string[] $parents
* @param object|string $actual
*/
public function assertImplementsInterfaces(array $parents, object|string $actual)
{
$this->checkClassesIntersection($parents, $actual, 'class_implements');
}
/**
* Тестирует использование классом (объектом) указанных трейтов
*
* @param string[] $parents
* @param object|string $actual
*/
public function assertUsesTraits(array $parents, object|string $actual)
{
$this->checkClassesIntersection($parents, $actual, 'class_uses');
}
/**
* Проверяет пересечение классов указанной функцией SPL
*
* @param object|string $class Класс для проверки на вхождение, или объект, класс коего нужно проверить
* @param array $classes Массив классов, вхождение в который нужно проверить
* @param string $function class_parents|class_implements|class_uses
*/
protected function checkClassesIntersection(array $classes, object|string $class, string $function): void
{
$actual_classes = is_object($class) ? $function($class) : [$class::class];
$this->assertIsArray($actual_classes);
$this->assertNotEmpty(array_intersect($classes, $actual_classes));
}
/**
* Тестирует, является ли объект коллекцией
*
* @param mixed $expected
*/
public function assertIsCollection(mixed $expected): void
{
$this->assertIsObject($expected);
$this->assertIsIterable($expected);
$this->assertIsSameClass($expected, Collection::class);
}
/**
* Провайдер валидных телефонов
*
* @return array<array<string, string>>
*/
public function providerValidPhones(): array
{
return [
['+79991234567', '+79991234567'],
['79991234567', '+79991234567'],
['89991234567', '+89991234567'],
['+7 999 123 45 67', '+79991234567'],
['+7 (999) 123-45-67', '+79991234567'],
["+7 %(?9:9\"9')abc\r123\n45\t67\0", '+79991234567'],
];
}
/**
* Провайдер валидных email-ов
*
* @return array<array<string>>
*/
public function providerValidEmails(): array
{
return [
['abc@mail.com'],
['abc-d@mail.com'],
['abc.def@mail.com'],
['abc.def@mail.org'],
['abc.def@mail-archive.com'],
];
} }
} }

318
tests/ClientTest.php Normal file
View File

@@ -0,0 +1,318 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnlineTests;
use AtolOnline\{
Entities\Client,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\TooLongEmailException,
Exceptions\TooLongNameException,
Exceptions\TooLongPhoneException,
Helpers
};
/**
* Набор тестов для проверки работы класс покупателя
*/
class ClientTest extends BasicTestCase
{
/**
* Провайдер строк, которые приводятся к null
*
* @return array<array<string|null>>
*/
public function providerNullableStrings(): array
{
return [
[''],
[' '],
[null],
["\n\r\t"],
];
}
/**
* Провайдер телефонов, которые приводятся к null
*
* @return array<array<string>>
*/
public function providerNullablePhones(): array
{
return array_merge(
$this->providerNullableStrings(),
[
[Helpers::randomStr(10, false)],
["asdfgvs \n\rtt\t*/(*&%^*$%"],
]
);
}
/**
* Провайдер невалидных email-ов
*
* @return array<array<string>>
*/
public function providerInvalidEmails(): array
{
return [
['@example'],
[Helpers::randomStr(15)],
['@example.com'],
['abc.def@mail'],
['.abc@mail.com'],
['example@example'],
['abc..def@mail.com'],
['abc.def@mail..com'],
['abc.def@mail#archive.com'],
];
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует приведение покупателя к json
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::jsonSerialize
*/
public function testAtolable(): void
{
$this->assertAtolable(new Client());
}
/**
* Тестирует конструктор покупателя без передачи значений
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::jsonSerialize
*/
public function testConstructorWithoutArgs(): void
{
$this->assertEquals('{}', (string)(new Client()));
}
/**
* Тестирует конструктор с передачей значений (внутри работают сеттеры)
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::jsonSerialize
* @covers \AtolOnline\Entities\Client::setName
* @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Entities\Client::setEmail
* @covers \AtolOnline\Entities\Client::setInn
*/
public function testConstructorWithArgs(): void
{
$customer = new Client(
'John Doe',
'john@example.com',
'+1/22/99*73s dsdas654 5s6', // +122997365456
'+fasd3\qe3fs_=nac99013928czc' // 3399013928
);
$this->assertAtolable($customer, [
'name' => 'John Doe',
'email' => 'john@example.com',
'phone' => '+122997365456',
'inn' => '3399013928',
]);
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует установку имён покупателя, которые приводятся к null
*
* @param mixed $name
* @dataProvider providerNullableStrings
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setName
* @covers \AtolOnline\Entities\Client::getName
* @throws TooLongNameException
*/
public function testNullableNames(mixed $name): void
{
$customer = (new Client())->setName($name);
$this->assertNull($customer->getName());
}
/**
* Тестирует установку валидного имени покупателя
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setName
* @covers \AtolOnline\Entities\Client::getName
* @throws TooLongNameException
*/
public function testValidName(): void
{
$name = Helpers::randomStr();
$customer = (new Client())->setName($name);
$this->assertEquals($name, $customer->getName());
}
/**
* Тестирует установку невалидного имени покупателя
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setName
* @covers \AtolOnline\Exceptions\TooLongNameException
* @throws TooLongNameException
*/
public function testInvalidName(): void
{
$this->expectException(TooLongNameException::class);
(new Client())->setName(Helpers::randomStr(400));
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует установку телефонов покупателя, которые приводятся к null
*
* @param mixed $phone
* @dataProvider providerNullablePhones
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Entities\Client::getPhone
* @throws TooLongPhoneException
*/
public function testNullablePhones(mixed $phone): void
{
$customer = (new Client())->setPhone($phone);
$this->assertNull($customer->getPhone());
}
/**
* Тестирует установку валидного телефона покупателя
*
* @dataProvider providerValidPhones
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Entities\Client::getPhone
* @throws TooLongPhoneException
*/
public function testValidPhone(string $input, string $output): void
{
$customer = (new Client())->setPhone($input);
$this->assertEquals($output, $customer->getPhone());
}
/**
* Тестирует установку невалидного телефона покупателя
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Exceptions\TooLongPhoneException
* @throws TooLongPhoneException
*/
public function testInvalidPhone(): void
{
$this->expectException(TooLongPhoneException::class);
(new Client())->setPhone('99999999999999999999999999999999999999999999999999999999999999999999999999');
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует установку валидных email-ов покупателя
*
* @param mixed $email
* @dataProvider providerValidEmails
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setEmail
* @covers \AtolOnline\Entities\Client::getEmail
* @throws TooLongEmailException
* @throws InvalidEmailException
*/
public function testValidEmails(mixed $email): void
{
$customer = (new Client())->setEmail($email);
$this->assertEquals($email, $customer->getEmail());
}
/**
* Тестирует установку слишком длинного email покупателя
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setEmail
* @covers \AtolOnline\Exceptions\TooLongEmailException
* @throws TooLongEmailException
* @throws InvalidEmailException
*/
public function testTooLongEmail(): void
{
$this->expectException(TooLongEmailException::class);
(new Client())->setEmail(Helpers::randomStr(65));
}
/**
* Тестирует установку невалидного email покупателя
*
* @param mixed $email
* @dataProvider providerInvalidEmails
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setEmail
* @covers \AtolOnline\Exceptions\InvalidEmailException
* @throws TooLongEmailException
* @throws InvalidEmailException
*/
public function testInvalidEmail(mixed $email): void
{
$this->expectException(InvalidEmailException::class);
(new Client())->setEmail($email);
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует исключение о корректной длине ИНН
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setInn
* @covers \AtolOnline\Entities\Client::getInn
* @throws InvalidInnLengthException
*/
public function testValidInn(): void
{
$customer = (new Client())->setInn('1234567890');
$this->assertEquals('1234567890', $customer->getInn());
$customer = $customer->setInn('123456789012');
$this->assertEquals('123456789012', $customer->getInn());
}
/**
* Тестирует исключение о некорректной длине ИНН (10 цифр)
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setInn
* @covers \AtolOnline\Exceptions\InvalidInnLengthException
* @throws InvalidInnLengthException
*/
public function testInvalidInn10(): void
{
$this->expectException(InvalidInnLengthException::class);
(new Client())->setInn('12345678901');
}
/**
* Тестирует исключение о некорректной длине ИНН (12 цифр)
*
* @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setInn
* @covers \AtolOnline\Exceptions\InvalidInnLengthException
* @throws InvalidInnLengthException
*/
public function testInvalidInn12(): void
{
$this->expectException(InvalidInnLengthException::class);
(new Client())->setInn('1234567890123');
}
}

134
tests/CompanyTest.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnlineTests;
use AtolOnline\{
Constants\SnoTypes,
Entities\Company,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\InvalidPaymentAddressException,
Exceptions\InvalidSnoException,
Exceptions\TooLongEmailException,
Exceptions\TooLongPaymentAddressException,
Helpers};
/**
* Набор тестов для проверки работы класс продавца
*/
class CompanyTest extends BasicTestCase
{
/**
* Тестирует конструктор с сеттерами и приведение к json с геттерами
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::jsonSerialize
* @covers \AtolOnline\Entities\Company::setEmail
* @covers \AtolOnline\Entities\Company::setSno
* @covers \AtolOnline\Entities\Company::setInn
* @covers \AtolOnline\Entities\Company::setPaymentAddress
* @covers \AtolOnline\Entities\Company::getEmail
* @covers \AtolOnline\Entities\Company::getSno
* @covers \AtolOnline\Entities\Company::getInn
* @covers \AtolOnline\Entities\Company::getPaymentAddress
*/
public function testConstructor()
{
$this->assertAtolable(new Company(
$email = 'company@example.com',
$sno = SnoTypes::OSN,
$inn = '1234567890',
$payment_address = 'https://example.com',
), [
'email' => $email,
'sno' => $sno,
'inn' => $inn,
'payment_address' => $payment_address,
]);
}
/**
* Тестирует исключение о слишком длинном email
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setEmail
* @covers \AtolOnline\Exceptions\TooLongEmailException
*/
public function testEmailTooLongException()
{
$this->expectException(TooLongEmailException::class);
new Company(Helpers::randomStr(65), SnoTypes::OSN, '1234567890', 'https://example.com');
}
/**
* Тестирует исключение о невалидном email
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setEmail
* @covers \AtolOnline\Exceptions\InvalidEmailException
*/
public function testInvalidEmailException()
{
$this->expectException(InvalidEmailException::class);
new Company('company@examas%^*.com', SnoTypes::OSN, '1234567890', 'https://example.com');
}
/**
* Тестирует исключение о слишком длинном платёжном адресе
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setSno
* @covers \AtolOnline\Exceptions\InvalidSnoException
*/
public function testInvalidSnoException()
{
$this->expectException(InvalidSnoException::class);
new Company('company@example.com', 'test', '1234567890', 'https://example.com');
}
/**
* Тестирует исключение о слишком длинном платёжном адресе
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setInn
* @covers \AtolOnline\Exceptions\InvalidInnLengthException
*/
public function testInvalidInnLengthException()
{
$this->expectException(InvalidInnLengthException::class);
new Company('company@example.com', SnoTypes::OSN, Helpers::randomStr(13), 'https://example.com');
}
/**
* Тестирует исключение о слишком длинном платёжном адресе
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setPaymentAddress
* @covers \AtolOnline\Exceptions\TooLongPaymentAddressException
*/
public function testTooLongPaymentAddressException()
{
$this->expectException(TooLongPaymentAddressException::class);
new Company('company@example.com', SnoTypes::OSN, '1234567890', Helpers::randomStr(257));
}
/**
* Тестирует исключение о невалидном платёжном адресе
*
* @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setPaymentAddress
* @covers \AtolOnline\Exceptions\InvalidPaymentAddressException
*/
public function testInvalidPaymentAddressException()
{
$this->expectException(InvalidPaymentAddressException::class);
new Company('company@example.com', SnoTypes::OSN, '1234567890', '');
}
}

123
tests/HelpersTest.php Normal file
View File

@@ -0,0 +1,123 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnlineTests;
use AtolOnline\Helpers;
/**
* Набор тестов для проверки работы функций-хелперов
*/
class HelpersTest extends BasicTestCase
{
/**
* Провайдер копеек для перевода в рубли
*
* @return array<array<int|null, float>>
*/
public function providerKopeksToRubles(): array
{
return [
[null, 0],
[0, 0],
[1, 0.01],
[12, 0.12],
[123, 1.23],
[1234, 12.34],
[12345, 123.45],
[-1, 0.01],
[-12, 0.12],
[-123, 1.23],
[-1234, 12.34],
[-12345, 123.45],
];
}
/**
* Провайдер рублей для перевода в копейки
*
* @return array<array<float|null, int>>
*/
public function providerRublesToKopeks(): array
{
return [
[null, 0],
[0, 0],
[0.01, 1],
[0.12, 12],
[1.23, 123],
[12.34, 1234],
[123.45, 12345],
[-0.01, 1],
[-0.12, 12],
[-1.23, 123],
[-12.34, 1234],
[-123.45, 12345],
];
}
/**
* Провайдер для тестирования генерации рандомной строки
*
* @return array<array<int, int>>
*/
public function providerRandomStr(): array
{
return [
[0, 0],
[1, 1],
[5, 5],
[-1, 1],
[-5, 5],
];
}
//------------------------------------------------------------------------------------------------------------------
/**
* Тестирует перевод копеек в рубли
*
* @dataProvider providerKopeksToRubles
* @covers \AtolOnline\Helpers::KopToRub
*/
public function testKopeksToRubles(?int $kopeks, float $rubles): void
{
$result = Helpers::KopToRub($kopeks);
$this->assertIsFloat($result);
$this->assertEquals($result, $rubles);
}
/**
* Тестирует перевод копеек в рубли
*
* @dataProvider providerRublesToKopeks
* @covers \AtolOnline\Helpers::RubToKop
*/
public function testRublesToKopeks(?float $rubles, int $kopeks): void
{
$result = Helpers::RubToKop($rubles);
$this->assertIsInt($result);
$this->assertEquals($result, $kopeks);
}
/**
* Тестирует длину рандомной строки
*
* @param int $input
* @param int $output
* @dataProvider providerRandomStr
*/
public function testRandomString(int $input, int $output): void
{
$result = Helpers::randomStr($input);
$this->assertIsString($result);
$this->assertEquals($output, strlen($result));
// тестировать на наличие цифр быссмысленно
}
}

View File

@@ -1,34 +1,37 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
use AtolOnline\{Constants\PaymentMethods, namespace AtolOnlineTests;
use AtolOnline\{
Constants\PaymentMethods,
Constants\PaymentObjects, Constants\PaymentObjects,
Constants\VatTypes, Constants\VatTypes,
Entities\Item, Entities\Item,
Exceptions\AtolNameTooLongException, Exceptions\BasicTooManyException,
Exceptions\AtolPriceTooHighException, Exceptions\TooHighPriceException,
Exceptions\AtolTooManyException, Exceptions\TooLongNameException,
Exceptions\AtolUnitTooLongException, Exceptions\TooLongUnitException,
Exceptions\AtolUserdataTooLongException}; Exceptions\TooLongUserdataException,};
/** /**
* Class ItemTest * Class ItemTest
*/ */
class ItemTest extends BasicTestCase class ItemTestTodo extends BasicTestCase
{ {
/** /**
* Тестирует установку параметров через конструктор * Тестирует установку параметров через конструктор
* *
* @throws AtolOnline\Exceptions\AtolNameTooLongException * @throws AtolOnline\Exceptions\TooLongNameException
* @throws AtolOnline\Exceptions\AtolPriceTooHighException * @throws AtolOnline\Exceptions\TooHighPriceException
* @throws AtolOnline\Exceptions\AtolTooManyException * @throws AtolOnline\Exceptions\BasicTooManyException
* @throws AtolOnline\Exceptions\AtolUnitTooLongException * @throws AtolOnline\Exceptions\TooLongUnitException
*/ */
public function testConstructor() public function testConstructor()
{ {
@@ -41,7 +44,7 @@ class ItemTest extends BasicTestCase
PaymentObjects::COMMODITY, PaymentObjects::COMMODITY,
PaymentMethods::FULL_PAYMENT PaymentMethods::FULL_PAYMENT
); );
$this->checkAtolEntity($item); $this->assertAtolable($item);
$this->assertEquals('Банан', $item->getName()); $this->assertEquals('Банан', $item->getName());
$this->assertEquals(65.99, $item->getPrice()); $this->assertEquals(65.99, $item->getPrice());
$this->assertEquals(2.74, $item->getQuantity()); $this->assertEquals(2.74, $item->getQuantity());
@@ -50,15 +53,15 @@ class ItemTest extends BasicTestCase
$this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject()); $this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject());
$this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod()); $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod());
} }
/** /**
* Тестирует установку параметров через сеттеры * Тестирует установку параметров через сеттеры
* *
* @throws AtolOnline\Exceptions\AtolNameTooLongException * @throws AtolOnline\Exceptions\TooLongNameException
* @throws AtolOnline\Exceptions\AtolPriceTooHighException * @throws AtolOnline\Exceptions\TooHighPriceException
* @throws AtolOnline\Exceptions\AtolTooManyException * @throws AtolOnline\Exceptions\BasicTooManyException
* @throws AtolOnline\Exceptions\AtolUnitTooLongException * @throws AtolOnline\Exceptions\TooLongUnitException
* @throws AtolOnline\Exceptions\AtolUserdataTooLongException * @throws AtolOnline\Exceptions\TooLongUserdataException
*/ */
public function testSetters() public function testSetters()
{ {
@@ -71,7 +74,7 @@ class ItemTest extends BasicTestCase
$item->setPaymentObject(PaymentObjects::COMMODITY); $item->setPaymentObject(PaymentObjects::COMMODITY);
$item->setPaymentMethod(PaymentMethods::FULL_PAYMENT); $item->setPaymentMethod(PaymentMethods::FULL_PAYMENT);
$item->setUserData('Some user data'); $item->setUserData('Some user data');
$this->checkAtolEntity($item); $this->assertAtolable($item);
$this->assertEquals('Банан', $item->getName()); $this->assertEquals('Банан', $item->getName());
$this->assertEquals(65.99, $item->getPrice()); $this->assertEquals(65.99, $item->getPrice());
$this->assertEquals(2.74, $item->getQuantity()); $this->assertEquals(2.74, $item->getQuantity());
@@ -81,11 +84,11 @@ class ItemTest extends BasicTestCase
$this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod()); $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod());
$this->assertEquals('Some user data', $item->getUserData()); $this->assertEquals('Some user data', $item->getUserData());
} }
/** /**
* Тестирует установку ставки НДС разными путями * Тестирует установку ставки НДС разными путями
* *
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException * @throws TooHighPriceException
*/ */
public function testSetVat() public function testSetVat()
{ {
@@ -95,66 +98,66 @@ class ItemTest extends BasicTestCase
$item->setVatType(VatTypes::VAT20); $item->setVatType(VatTypes::VAT20);
$this->assertEquals(VatTypes::VAT20, $item->getVat()->getType()); $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
} }
/** /**
* Тестирует исключение о слишком длинном наименовании * Тестирует исключение о слишком длинном наименовании
* *
* @throws \AtolOnline\Exceptions\AtolNameTooLongException * @throws TooLongNameException
*/ */
public function testAtolNameTooLongException() public function testAtolNameTooLongException()
{ {
$item = new Item(); $item = new Item();
$this->expectException(AtolNameTooLongException::class); $this->expectException(TooLongNameException::class);
$item->setName(self::randomString(130)); $item->setName(Helpers::randomStr(130));
} }
/** /**
* Тестирует исключение о слишком высоком количестве * Тестирует исключение о слишком высоком количестве
* *
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException * @throws TooHighPriceException
* @throws \AtolOnline\Exceptions\AtolTooManyException * @throws BasicTooManyException
* @throws \AtolOnline\Exceptions\AtolUnitTooLongException * @throws TooLongUnitException
*/ */
public function testAtolQuantityTooHighException() public function testAtolQuantityTooHighException()
{ {
$item = new Item(); $item = new Item();
$this->expectException(AtolTooManyException::class); $this->expectException(BasicTooManyException::class);
$item->setQuantity(100000.1); $item->setQuantity(100000.1);
} }
/** /**
* Тестирует исключение о слишком высокой цене * Тестирует исключение о слишком высокой цене
* *
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException * @throws TooHighPriceException
*/ */
public function testAtolPriceTooHighException() public function testAtolPriceTooHighException()
{ {
$item = new Item(); $item = new Item();
$this->expectException(AtolPriceTooHighException::class); $this->expectException(TooHighPriceException::class);
$item->setPrice(42949673.1); $item->setPrice(42949673.1);
} }
/** /**
* Тестирует исключение о слишком длинных польз. данных * Тестирует исключение о слишком длинных польз. данных
* *
* @throws \AtolOnline\Exceptions\AtolUserdataTooLongException * @throws TooLongUserdataException
*/ */
public function testAtolUserdataTooLongException() public function testAtolUserdataTooLongException()
{ {
$item = new Item(); $item = new Item();
$this->expectException(AtolUserdataTooLongException::class); $this->expectException(TooLongUserdataException::class);
$item->setUserData('User data User data User data User data User data User data User data'); $item->setUserData('User data User data User data User data User data User data User data');
} }
/** /**
* Тестирует исключение о слишком длинной единице измерения * Тестирует исключение о слишком длинной единице измерения
* *
* @throws \AtolOnline\Exceptions\AtolUnitTooLongException * @throws TooLongUnitException
*/ */
public function testAtolUnitTooLongException() public function testAtolUnitTooLongException()
{ {
$item = new Item(); $item = new Item();
$this->expectException(AtolUnitTooLongException::class); $this->expectException(TooLongUnitException::class);
$item->setMeasurementUnit('кг кг кг кг кг кг кг кг кг '); $item->setMeasurementUnit('кг кг кг кг кг кг кг кг кг ');
} }
} }

322
tests/KktMonitorTest.php Normal file
View File

@@ -0,0 +1,322 @@
<?php
namespace AtolOnlineTests;
use AtolOnline\Api\AtolClient;
use AtolOnline\Api\KktMonitor;
use AtolOnline\Api\KktResponse;
use AtolOnline\Exceptions\AuthFailedException;
use AtolOnline\Exceptions\EmptyLoginException;
use AtolOnline\Exceptions\EmptyPasswordException;
use AtolOnline\Exceptions\TooLongLoginException;
use AtolOnline\Exceptions\TooLongPasswordException;
use AtolOnline\Helpers;
use AtolOnline\TestEnvParams;
use GuzzleHttp\Exception\GuzzleException;
/**
* Набор тестов для проверки работы API-клиента на примере монитора ККТ
*/
class KktMonitorTest extends BasicTestCase
{
/**
* Тестирует успешное создание объекта монитора без аргументов конструктора
*
* @covers \AtolOnline\Api\KktMonitor::__construct
*/
public function testConstructorWithoutArgs()
{
$client = new KktMonitor();
$this->assertIsObject($client);
$this->assertIsSameClass(KktMonitor::class, $client);
$this->assertExtendsClasses([AtolClient::class], $client);
}
/**
* Тестирует успешное создание объекта монитора с аргументами конструктора
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin
* @covers \AtolOnline\Api\KktMonitor::getLogin
* @covers \AtolOnline\Api\KktMonitor::setPassword
* @covers \AtolOnline\Api\KktMonitor::getPassword
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testConstructorWithArgs()
{
$client = new KktMonitor(false, 'login', 'password', []);
$this->assertIsObject($client);
$this->assertIsSameClass(KktMonitor::class, $client);
$this->assertExtendsClasses([AtolClient::class], $client);
}
/**
* Тестирует установку и возврат логина
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::getLogin
* @covers \AtolOnline\Api\KktMonitor::setLogin
* @throws EmptyLoginException
* @throws TooLongLoginException
*/
public function testLogin()
{
$client = new KktMonitor(login: 'login');
$this->assertEquals('login', $client->getLogin());
$client = new KktMonitor();
$this->assertNull($client->getLogin());
$client->setLogin('login');
$this->assertEquals('login', $client->getLogin());
}
/**
* Тестирует исключение при попытке передать пустой логин в конструктор
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin
* @covers \AtolOnline\Exceptions\EmptyLoginException
*/
public function testEmptyLoginException()
{
$this->expectException(EmptyLoginException::class);
new KktMonitor(login: '');
}
/**
* Тестирует исключение при попытке передать слишком длинный логин в конструктор
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin
* @covers \AtolOnline\Exceptions\TooLongLoginException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testTooLongLoginException()
{
$this->expectException(TooLongLoginException::class);
new KktMonitor(login: Helpers::randomStr(101));
}
/**
* Тестирует установку и возврат пароля
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::getPassword
* @covers \AtolOnline\Api\KktMonitor::setPassword
* @throws EmptyPasswordException
* @throws TooLongPasswordException
*/
public function testPassword()
{
$client = new KktMonitor(password: 'password');
$this->assertEquals('password', $client->getPassword());
$client = new KktMonitor();
$this->assertNull($client->getPassword());
$client->setPassword('password');
$this->assertEquals('password', $client->getPassword());
}
/**
* Тестирует исключение при попытке передать пустой пароль в конструктор
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setPassword
* @covers \AtolOnline\Exceptions\EmptyPasswordException
*/
public function testEmptyPasswordException()
{
$this->expectException(EmptyPasswordException::class);
new KktMonitor(password: '');
}
/**
* Тестирует исключение при попытке передать слишком длинный пароль в конструктор
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setPassword
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testConstructorWithLongPassword()
{
$this->expectException(TooLongPasswordException::class);
new KktMonitor(password: Helpers::randomStr(101));
}
/**
* Тестирует установку тестового режима
*
* @covers \AtolOnline\Api\KktMonitor::__construct
* @covers \AtolOnline\Api\KktMonitor::isTestMode
* @covers \AtolOnline\Api\KktMonitor::setTestMode
*/
public function testTestMode()
{
$client = new KktMonitor();
$this->assertTrue($client->isTestMode());
$client = new KktMonitor(true);
$this->assertTrue($client->isTestMode());
$client = new KktMonitor(false);
$this->assertFalse($client->isTestMode());
$client = (new KktMonitor())->setTestMode();
$this->assertTrue($client->isTestMode());
$client = (new KktMonitor())->setTestMode(true);
$this->assertTrue($client->isTestMode());
$client = (new KktMonitor())->setTestMode(false);
$this->assertFalse($client->isTestMode());
}
/**
* Возвращает объект клиента для тестирования с тестовым API АТОЛ
*
* @return AtolClient
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
private function newTestClient(): KktMonitor
{
$credentials = TestEnvParams::FFD105();
return (new KktMonitor(true))
->setLogin($credentials['login'])
->setPassword($credentials['password']);
}
/**
* Тестирует авторизацию
*
* @covers \AtolOnline\Api\AtolClient::getHeaders
* @covers \AtolOnline\Api\KktMonitor::sendRequest
* @covers \AtolOnline\Api\KktMonitor::getAuthEndpoint
* @covers \AtolOnline\Api\KktMonitor::doAuth
* @covers \AtolOnline\Api\KktMonitor::auth
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
* @throws AuthFailedException
* @throws GuzzleException
*/
public function testAuth()
{
$this->skipIfMonitoringIsOffline();
$result = $this->newTestClient()->auth();
$this->assertTrue($result);
}
/**
* Тестирует возврат токена после авторизации
*
* @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::setToken
* @covers \AtolOnline\Api\KktMonitor::getToken
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testGetToken()
{
$client = new KktMonitor();
$this->assertNull($client->getToken());
$this->skipIfMonitoringIsOffline();
$client = $this->newTestClient();
$client->auth();
$this->assertIsString($client->getToken());
}
/**
* Тестирует возврат объекта последнего ответа от API
*
* @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getResponse
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testGetResponse()
{
$this->skipIfMonitoringIsOffline();
$client = $this->newTestClient();
$client->auth();
$this->assertIsSameClass(KktResponse::class, $client->getResponse());
}
/**
* [Мониторинг] Тестирует получение данных о всех ККТ
*
* @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getMainEndpoint
* @covers \AtolOnline\Api\AtolClient::getUrlToMethod
* @covers \AtolOnline\Api\KktMonitor::fetchAll
* @covers \AtolOnline\Api\KktMonitor::getAll
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testMonitorGetAll()
{
$this->skipIfMonitoringIsOffline();
$client = $this->newTestClient();
$client->auth();
$kkts = $client->getAll();
$this->assertNotEmpty($client->getResponse()->getContent());
$this->assertIsCollection($kkts);
$this->assertTrue($kkts->count() > 0);
}
/**
* [Мониторинг] Тестирует получение данных о конкретной ККТ
*
* @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getMainEndpoint
* @covers \AtolOnline\Api\AtolClient::getUrlToMethod
* @covers \AtolOnline\Api\KktMonitor::fetchOne
* @covers \AtolOnline\Api\KktMonitor::getOne
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function testGetOne()
{
$this->skipIfMonitoringIsOffline();
$client = $this->newTestClient();
$client->auth();
$kkts = $client->getAll();
$serial_number = $kkts->first()->serialNumber;
$client->getOne($serial_number);
$this->assertIsSameClass(KktResponse::class, $client->getResponse());
$this->assertNotEmpty($client->getResponse());
$this->assertNotNull($client->getResponse()->data->serialNumber);
$this->assertEquals($serial_number, $client->getResponse()->data->serialNumber);
}
}

View File

@@ -1,103 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
use AtolOnline\{Entities\Client,
Exceptions\AtolEmailTooLongException,
Exceptions\AtolEmailValidateException,
Exceptions\AtolInnWrongLengthException,
Exceptions\AtolNameTooLongException,
Exceptions\AtolPhoneTooLongException
};
/**
* Class ClientTest
*/
class ClientTest extends BasicTestCase
{
/**
* Тестирует установку параметров
*/
public function testConstructor()
{
$customer = new Client(
'John Doe',
'+1/22/99*73s dsdas654 5s6', // +122997365456
'john@example.com',
'+fasd3\qe3fs_=nac99013928czc' // 3399013928
);
$this->checkAtolEntity($customer);
$this->assertEquals('John Doe', $customer->getName());
$this->assertEquals('+122997365456', $customer->getPhone());
$this->assertEquals('john@example.com', $customer->getEmail());
$this->assertEquals('3399013928', $customer->getInn());
}
/**
* Тестирует исключение о слишком длинном имени
*
* @throws \AtolOnline\Exceptions\AtolNameTooLongException
*/
public function testAtolNameTooLongException()
{
$customer = new Client();
$this->expectException(AtolNameTooLongException::class);
$customer->setName(self::randomString(257));
}
/**
* Тестирует исключение о слишком длинном телефоне
*
* @throws \AtolOnline\Exceptions\AtolPhoneTooLongException
*/
public function testAtolPhoneTooLongException()
{
$customer = new Client();
$this->expectException(AtolPhoneTooLongException::class);
$customer->setPhone('99999999999999999999999999999999999999999999999999999999999999999999999999');
}
/**
* Тестирует исключение о слишком длинной почте
*
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
*/
public function testAtolEmailTooLongException()
{
$customer = new Client();
$this->expectException(AtolEmailTooLongException::class);
$customer->setEmail(self::randomString(65));
}
/**
* Тестирует исключение о некорректной почте
*
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
*/
public function testAtolEmailValidateException()
{
$customer = new Client();
$this->expectException(AtolEmailValidateException::class);
$customer->setEmail(self::randomString(15));
}
/**
* Тестирует исключение о некорректной длине ИНН
*
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
*/
public function testAtolInnWrongLengthException()
{
$company = new Client();
$this->expectException(AtolInnWrongLengthException::class);
$company->setInn('123456789');
$company->setInn('1234567890123');
}
}

View File

@@ -1,91 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
use AtolOnline\{Constants\SnoTypes,
Entities\Company,
Exceptions\AtolEmailTooLongException,
Exceptions\AtolEmailValidateException,
Exceptions\AtolInnWrongLengthException,
Exceptions\AtolPaymentAddressTooLongException
};
/**
* Class CompanyTest
*/
class CompanyTest extends BasicTestCase
{
/**
* Тестирует установку параметров через конструктор
*/
public function testConstructor()
{
$company = new Company(
SnoTypes::OSN,
'5544332219',
'https://v4.online.atol.ru',
'company@example.com'
);
$this->checkAtolEntity($company);
$this->assertEquals(SnoTypes::OSN, $company->getSno());
$this->assertEquals('5544332219', $company->getInn());
$this->assertEquals('https://v4.online.atol.ru', $company->getPaymentAddress());
$this->assertEquals('company@example.com', $company->getEmail());
}
/**
* Тестирует исключение о некорректной длине ИНН
*
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
*/
public function testAtolInnWrongLengthException()
{
$company = new Company();
$this->expectException(AtolInnWrongLengthException::class);
$company->setInn('321');
$company->setInn('1234567890123');
}
/**
* Тестирует исключение о слишком длинном платёжном адресе
*
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException
*/
public function testAtolPaymentAddressTooLongException()
{
$company = new Company();
$this->expectException(AtolPaymentAddressTooLongException::class);
$company->setPaymentAddress(self::randomString(257));
}
/**
* Тестирует исключение о слишком длинной почте
*
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
*/
public function testAtolEmailTooLongException()
{
$company = new Company();
$this->expectException(AtolEmailTooLongException::class);
$company->setEmail(self::randomString(65));
}
/**
* Тестирует исключение о некорректной почте
*
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
*/
public function testAtolEmailValidateException()
{
$company = new Company();
$this->expectException(AtolEmailValidateException::class);
$company->setEmail(self::randomString(15));
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace Unit;
use AtolOnline\Api\CorrectionSchema;
use AtolOnline\Api\SellSchema;
use PHPUnit\Framework\TestCase;
class SchemaTest extends TestCase
{
/**
* Тестирует корректность работы объекта схемы документа
* прихода, возврата прихода, расхода, возврата расхода
*/
public function testSellSchema()
{
$this->assertIsObject(SellSchema::get());
$this->assertJson(SellSchema::json());
}
/**
* Тестирует корректность работы объекта схемы документа
* коррекции прихода, коррекции расхода
*/
public function testCorrectionSchema()
{
$this->assertIsObject(CorrectionSchema::get());
$this->assertJson(CorrectionSchema::json());
}
}

View File

@@ -1,39 +1,44 @@
<?php <?php
/** /*
* Copyright (c) Антон Аксенов (aka Anthony Axenov) * Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
* *
* This code is licensed under MIT. * This code is licensed under MIT.
* Этот код распространяется по лицензии MIT. * Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
use AtolOnline\{Constants\VatTypes, Entities\Vat}; namespace AtolOnlineTests;
use AtolOnline\{
Constants\VatTypes,
Entities\Vat
};
/** /**
* Class VatTest * Class VatTest
*/ */
class VatTest extends BasicTestCase class VatTestTodo extends BasicTestCase
{ {
/** /**
* Тестирует каждый тип ставки НДС * Тестирует каждый тип ставки НДС
* *
* @dataProvider vatProvider * @dataProvider vatProvider
* @param string $vat_type Тип НДС * @param string $vat_type Тип НДС
* @param float $sum Исходная сумма * @param float $sum Исходная сумма
* @param float $expected_set Ожидаемый результат после установки суммы * @param float $expected_set Ожидаемый результат после установки суммы
* @param float $expected_add Ожидаемый результат после прибавления 20р * @param float $expected_add Ожидаемый результат после прибавления 20р
*/ */
public function testVat(string $vat_type, float $sum, float $expected_set, float $expected_add) public function testVat(string $vat_type, float $sum, float $expected_set, float $expected_add)
{ {
$vat = new Vat($vat_type); $vat = new Vat($vat_type);
$this->assertEquals(0, $vat->getFinalSum(), 'Test '.$vat_type.' | 1 step'); $this->assertEquals(0, $vat->getFinalSum(), 'Test ' . $vat_type . ' | 1 step');
$vat->setSum($sum); $vat->setSum($sum);
$this->assertEquals($expected_set, $vat->getFinalSum(), 'Test '.$vat_type.' | 2 step'); $this->assertEquals($expected_set, $vat->getFinalSum(), 'Test ' . $vat_type . ' | 2 step');
$vat->addSum(20); $vat->addSum(20);
$this->assertEquals($expected_add, $vat->getFinalSum(), 'Test '.$vat_type.' | 3 step'); $this->assertEquals($expected_add, $vat->getFinalSum(), 'Test ' . $vat_type . ' | 3 step');
$vat->addSum(-20); $vat->addSum(-20);
} }
/** /**
* Провайдер данных для тестирования разных типов ставок НДС * Провайдер данных для тестирования разных типов ставок НДС
* *