From 1db2d5b49f632fc9adcb49bcb63df90bc4f0ae82 Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Sat, 11 Jan 2020 14:30:25 +0800 Subject: [PATCH] Initial commit, v0.1.0-b --- .github/workflows/tests.yml | 23 + .gitignore | 6 + LICENSE | 21 + README.md | 110 + ROADMAP.md | 67 + composer.json | 45 + composer.lock | 1996 +++++++++++++++++ docs/client.md | 75 + docs/company.md | 72 + docs/correction_info.md | 65 + docs/documents.md | 159 ++ docs/items.md | 194 ++ docs/kkt.md | 217 ++ docs/payments.md | 126 ++ docs/readme.md | 14 + docs/vats.md | 186 ++ phpunit.xml | 25 + src/AtolOnline/Api/Kkt.php | 500 +++++ src/AtolOnline/Api/KktResponse.php | 123 + src/AtolOnline/Constants/CorrectionTypes.php | 28 + src/AtolOnline/Constants/PaymentMethods.php | 54 + src/AtolOnline/Constants/PaymentObjects.php | 108 + src/AtolOnline/Constants/PaymentTypes.php | 53 + .../Constants/ReceiptOperationTypes.php | 48 + src/AtolOnline/Constants/SnoTypes.php | 48 + src/AtolOnline/Constants/VatTypes.php | 58 + src/AtolOnline/Entities/AtolEntity.php | 28 + src/AtolOnline/Entities/Client.php | 149 ++ src/AtolOnline/Entities/Company.php | 137 ++ src/AtolOnline/Entities/CorrectionInfo.php | 171 ++ src/AtolOnline/Entities/Document.php | 354 +++ src/AtolOnline/Entities/Item.php | 396 ++++ src/AtolOnline/Entities/ItemArray.php | 111 + src/AtolOnline/Entities/Payment.php | 97 + src/AtolOnline/Entities/PaymentArray.php | 111 + src/AtolOnline/Entities/Vat.php | 186 ++ src/AtolOnline/Entities/VatArray.php | 111 + .../AtolCashierTooLongException.php | 34 + .../AtolCorrectionInfoException.php | 33 + .../Exceptions/AtolEmailEmptyException.php | 33 + .../Exceptions/AtolEmailTooLongException.php | 35 + .../Exceptions/AtolEmailValidateException.php | 34 + src/AtolOnline/Exceptions/AtolException.php | 22 + .../AtolInnWrongLengthException.php | 34 + .../Exceptions/AtolKktLoginEmptyException.php | 33 + .../AtolKktLoginTooLongException.php | 35 + .../AtolKktPasswordEmptyException.php | 33 + .../Exceptions/AtolNameTooLongException.php | 35 + .../AtolPaymentAddressTooLongException.php | 35 + .../Exceptions/AtolPhoneTooLongException.php | 35 + .../Exceptions/AtolPriceTooHighException.php | 35 + .../AtolQuantityTooHighException.php | 35 + .../Exceptions/AtolTooManyItemsException.php | 34 + .../AtolTooManyPaymentsException.php | 34 + .../Exceptions/AtolTooManyVatsException.php | 34 + .../Exceptions/AtolUnitTooLongException.php | 35 + .../AtolUserdataTooLongException.php | 35 + .../Exceptions/AtolUuidValidateException.php | 34 + .../AtolWrongDocumentTypeException.php | 34 + src/AtolOnline/Traits/HasEmail.php | 56 + src/AtolOnline/Traits/HasInn.php | 53 + .../Traits/RublesKopeksConverter.php | 40 + tests/BasicTestCase.php | 48 + tests/Feature/ItemTest.php | 178 ++ tests/Unit/ClientTest.php | 131 ++ tests/Unit/CompanyTest.php | 110 + tests/Unit/VatTest.php | 51 + 67 files changed, 7680 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ROADMAP.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 docs/client.md create mode 100644 docs/company.md create mode 100644 docs/correction_info.md create mode 100644 docs/documents.md create mode 100644 docs/items.md create mode 100644 docs/kkt.md create mode 100644 docs/payments.md create mode 100644 docs/readme.md create mode 100644 docs/vats.md create mode 100644 phpunit.xml create mode 100644 src/AtolOnline/Api/Kkt.php create mode 100644 src/AtolOnline/Api/KktResponse.php create mode 100644 src/AtolOnline/Constants/CorrectionTypes.php create mode 100644 src/AtolOnline/Constants/PaymentMethods.php create mode 100644 src/AtolOnline/Constants/PaymentObjects.php create mode 100644 src/AtolOnline/Constants/PaymentTypes.php create mode 100644 src/AtolOnline/Constants/ReceiptOperationTypes.php create mode 100644 src/AtolOnline/Constants/SnoTypes.php create mode 100644 src/AtolOnline/Constants/VatTypes.php create mode 100644 src/AtolOnline/Entities/AtolEntity.php create mode 100644 src/AtolOnline/Entities/Client.php create mode 100644 src/AtolOnline/Entities/Company.php create mode 100644 src/AtolOnline/Entities/CorrectionInfo.php create mode 100644 src/AtolOnline/Entities/Document.php create mode 100644 src/AtolOnline/Entities/Item.php create mode 100644 src/AtolOnline/Entities/ItemArray.php create mode 100644 src/AtolOnline/Entities/Payment.php create mode 100644 src/AtolOnline/Entities/PaymentArray.php create mode 100644 src/AtolOnline/Entities/Vat.php create mode 100644 src/AtolOnline/Entities/VatArray.php create mode 100644 src/AtolOnline/Exceptions/AtolCashierTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolCorrectionInfoException.php create mode 100644 src/AtolOnline/Exceptions/AtolEmailEmptyException.php create mode 100644 src/AtolOnline/Exceptions/AtolEmailTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolEmailValidateException.php create mode 100644 src/AtolOnline/Exceptions/AtolException.php create mode 100644 src/AtolOnline/Exceptions/AtolInnWrongLengthException.php create mode 100644 src/AtolOnline/Exceptions/AtolKktLoginEmptyException.php create mode 100644 src/AtolOnline/Exceptions/AtolKktLoginTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolKktPasswordEmptyException.php create mode 100644 src/AtolOnline/Exceptions/AtolNameTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolPaymentAddressTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolPhoneTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolPriceTooHighException.php create mode 100644 src/AtolOnline/Exceptions/AtolQuantityTooHighException.php create mode 100644 src/AtolOnline/Exceptions/AtolTooManyItemsException.php create mode 100644 src/AtolOnline/Exceptions/AtolTooManyPaymentsException.php create mode 100644 src/AtolOnline/Exceptions/AtolTooManyVatsException.php create mode 100644 src/AtolOnline/Exceptions/AtolUnitTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolUserdataTooLongException.php create mode 100644 src/AtolOnline/Exceptions/AtolUuidValidateException.php create mode 100644 src/AtolOnline/Exceptions/AtolWrongDocumentTypeException.php create mode 100644 src/AtolOnline/Traits/HasEmail.php create mode 100644 src/AtolOnline/Traits/HasInn.php create mode 100644 src/AtolOnline/Traits/RublesKopeksConverter.php create mode 100644 tests/BasicTestCase.php create mode 100644 tests/Feature/ItemTest.php create mode 100644 tests/Unit/ClientTest.php create mode 100644 tests/Unit/CompanyTest.php create mode 100644 tests/Unit/VatTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b512dce --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,23 @@ +name: Composer and phpunit tests + +on: [push] + +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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50ecf26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.idea +/vendor +/atol_log +*.log +/config.php +*cache* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..201ee5a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Антон Аксенов (aka Anthony Axenov) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..880c49d --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# АТОЛ Онлайн + +Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/). + +## Системные требования + +* PHP 7.2+ +* composer +* php-json + +## Начало работы + +### Подключение библиотеки + +1. Подключить пакет к вашему проекту: + ```bash + composer require axenov/atol-online + ``` +2. В нужном месте проекта объявить параметры, используя константы, и подключить **composer**, если это не сделано ранее: + ```php + require($project_root.'/vendor/autoload.php'); + ``` + где $project_root — абсолютный путь к корневой директории вашего проекта. + +### Тестирование кода библиотеки + +Файлы тестов находятся в директории `/tests` корня репозитория. + +Для запуска тестов необходимо перейти в корень вашего проекта и выполнить команду: + +```bash +./vendor/bin/phpunit +``` + +## Настройка ККТ + +Для работы с облачной ККТ необходимы следующие параметры: +* логин; +* пароль; +* код группы. + +Чтоб получить их, нужно: +1. авторизоваться на [online.atol.ru](https://online.atol.ru/lk/Account/Login); +2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**. + Скачается XML-файл с нужными настройками. + +Также для работы потребуются: +* ИНН продавца; +* URL места расчёта (ссылка на ваш интернет-сервис). + +## Использование библиотеки + +### Доступные методы и классы + +Весь исходный код находится в директории [`/src`](/src). + +**Комментарии phpDoc есть буквально к каждому классу, методу или полю. +Прокомментировано вообще всё.** + +1. Обращайтесь к [документации](/docs). +2. Обращайтесь к [исходному коду](/src). +3. Обращайтесь к [тестам](/test). +4. **Используйте подсказки вашей IDE.** + +Тогда у вас не возникнет затруднений. + +Для тех, кто решил подробно разобраться в работе библиотеки, отдельно отмечу нюансы, которые могут ускользнуть от внимания: +1. Класс `AtolOnline\Api\Kkt` унаследован от `GuzzleHttp\Client` со всеми вытекающими; +2. Все классы, унаследованные от `AtolOnline\Entities\AtolEntity` приводятся к JSON-строке. + +### Общий алгоритм + +1. Задать настройки ККТ +2. Собрать данные о покупателе +3. Собрать данные о продавце +4. Собрать данные о предметах расчёта (товары, услуги и пр.) +5. Создать документ, добавив в него покупателя, продавца и предметы расчёта +6. Отправить документ на регистрацию: + 6.1. *Необязательно:* задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа. +7. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации. + > Если с документом был передан `callback_url`, то ответ придёт на этот самый URL. + Если с документом **НЕ** был передан `callback_url` **либо** callback от АТОЛа не пришёл в течение 300 секунд (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа. +8. Проверить состояние документа (нет необходимости, если передавался `callback_url`): + 8.1. взять `uuid` ответа, полученного на запрос фискализации; + 8.2. отправить его в запросе состояния документа. + > Данные о документе можно получить только в течение 32 суток после успешной фискализации. + +## Об отправке электронного чека покупателю + +После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в связке с вашей ККТ: +* **по email**, если в документе указан email клиента; +* **по смс**: + * если в документе указан номер телефона клиента; + * если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД). + +> Если заданы email и телефон, то отдаётся приоритет email. + +## Дополнительные ресурсы + +**Discord-сервер для обсуждения этой библиотеки: [discord.gg/mFYTQmp](https://discord.gg/mFYTQmp)** + +Функционал, находящийся в разработке: [ROADMAP.md](ROADMAP.md) + +Официальные ресурсы АТОЛ Онлайн: +* **[Вся документация](https://online.atol.ru/lib/)** +* Telegram-канал: [@atolonline](https://t.me/atolonline) + +## Лицензия + +Вы имеете право использовать код из этого репозитория только на условиях **[лицензии MIT](LICENSE)**. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..df8300d --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,67 @@ +# Roadmap + +Здесь перечислены реализованные функции и находящиеся в разработке. + +Порядок их упоминания здесь может не совпадать с порядком реализации. + +Эталонная реализация подразумевает полную поддержку всех методов API и обеих схем документов: +* [Документы прихода, возврата прихода, расхода, возврата расхода](https://online.atol.ru/possystem/v4/schema/sell) +* [Документы коррекции прихода, коррекции расхода](https://online.atol.ru/possystem/v4/schema/correction) + +## Общий функционал библиотеки + +- [x] Переключение настроек доступа к ККТ при переключении тестового режима +- [x] Тесты для класса налоговой ставки (+ массив) +- [ ] Тесты для класса оплаты (+ массив) +- [x] Тесты для класса предмета расчёта (+ массив) +- [x] Тесты для класса клиента +- [x] Тесты для класса компании +- [ ] Тесты для класса данных коррекций +- [ ] Тесты для класса документа +- [ ] Тесты для класса ответа ККТ +- [ ] Тесты для регистрации документа прихода +- [ ] Тесты для регистрации документа возврата прихода +- [ ] Тесты для регистрации документа коррекции прихода +- [ ] Тесты для регистрации документа расхода +- [ ] Тесты для регистрации документа возврата расхода +- [ ] Тесты для регистрации документа коррекции расхода +- [ ] Вообще все расчёты вообще везде должны быть строго в копейках. Рубли (дроби) должны быть только в JSON-представлениях +- [ ] Валидатор схемы для документов прихода, возврата прихода, расхода, возврата расхода +- [ ] Валидатор схемы для документов коррекции прихода, коррекции расхода + +## Поддержка методов API (регистрация документов) + +- [x] приход +- [x] расход +- [x] возврат прихода +- [x] возврат расхода +- [x] коррекция прихода +- [x] коррекция расхода +- [x] проверка статуса документа + +## Документы прихода, возврата прихода, расхода, возврата расхода + +- [x] Пoддержка `receipt.client` (обязательный) +- [x] Пoддержка `receipt.company` (обязательный) +- [x] Пoддержка `receipt.items` (обязательный) +- [x] Пoддержка `receipt.total` (обязательный) +- [x] Пoддержка `receipt.payments` (обязательный) +- [x] Пoддержка `receipt.vats` +- [ ] Пoддержка `receipt.additional_check_props` +- [x] Пoддержка `receipt.cashier` +- [ ] Пoддержка `receipt.additional_user_props` +- [ ] Пoддержка `receipt.agent_info` +- [ ] Пoддержка `receipt.supplier_info` +- [ ] Пoддержка `receipt.items.agent_info` +- [ ] Пoддержка `receipt.items.supplier_info` +- [ ] Пoддержка `receipt.items.nomenclature_code` +- [ ] Пoддержка `receipt.items.excise` +- [ ] Пoддержка `receipt.items.country_code` +- [ ] Пoддержка `receipt.items.declaration_number` + +## Документы коррекции прихода, коррекции расхода + +- [x] Пoддержка `correction.company` (обязательный) +- [x] Пoддержка `correction.vats` (обязательный) +- [x] Пoддержка `correction.correction_info` (обязательный) +- [x] Пoддержка `correction.cashier` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..748f5e4 --- /dev/null +++ b/composer.json @@ -0,0 +1,45 @@ +{ + "name": "axenov/atol-online", + "description": "Library to use cloud cash register in e-commerce according to Russian Federal Law #54", + "license": "MIT", + "type": "library", + "version": "0.1.0-b", + "keywords": [ + "54-fz", + "atol", + "atol-online" + ], + "homepage": "https://github.com/anthonyaxenov/atol-online", + "authors": [ + { + "name": "Anthony Axenov", + "homepage": "http://anthonyaxenov.ru", + "email": "anthonyaxenov@gmail.com" + } + ], + "support": { + "source": "https://github.com/anthonyaxenov/atol-online", + "issues": "https://github.com/anthonyaxenov/atol-online/issues", + "chat": "https://discord.gg/mFYTQmp" + }, + "require": { + "php": ">=7.2", + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5", + "psr/log": "^1.1", + "ramsey/uuid": "^3.9" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "autoload": { + "classmap": [ + "src/AtolOnline/Api/", + "src/AtolOnline/Exceptions/", + "src/AtolOnline/Entities/", + "src/AtolOnline/Traits/", + "src/AtolOnline/Constants/", + "tests/" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a46edf9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1996 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dae801b00d2c80c28d290f676970945b", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2019-12-23T11:57:10+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2019-11-01T11:05:21+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7779489a47d443f845271badbdcedfe4df8e06fb", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2019-12-17T08:18:51+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-11-27T13:56:44+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-10-21T16:45:58+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/579bb7356d91f9456ccd505f24ca8b667966a0a7", + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-12-15T19:12:40+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-12-28T18:55:12+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-12-22T21:05:45+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2019-11-20T13:55:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-09-17T06:23:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7870c78da3c5e4883eaef36ae47853ebb3cb86f2", + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-12-25T14:49:39+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-11-20T08:46:58+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "shasum": "" + }, + "require": { + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2019-02-01T05:30:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-11-24T13:36:37+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.2", + "ext-json": "*" + }, + "platform-dev": [] +} diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 0000000..5664159 --- /dev/null +++ b/docs/client.md @@ -0,0 +1,75 @@ +# Работа с клиентами (покупателями) + +Объект покупателя инициализируется следующим образом: + +```php +$customer = new AtolOnline\Entities\Client(); +``` + +У объекта покупателя могут быть указаны любые из следующих атрибутов: +* email (тег ФФД 1008); +* ИНН (тег ФФД 1128); +* наименование (тег ФФД 1127); +* номер телефона (тег ФФД 1008). + +> Все эти атрибуты являются **необязательными**. +> Если указаны одновременно и email, и номер телефона, то ОФД отправит чек только на email. + +Указать эти атрибуты можно двумя способами: + +```php +// 1 способ - через конструктор +$customer = new AtolOnline\Entities\Client( + 'John Doe', // наименование + '+1/22/99*73s dsdas654 5s6', // номер телефона +122997365456 + 'john@example.com', // email + '+fasd3\qe3fs_=nac990139928czc' // номер ИНН 3399013928 +); + +// 2 способ - через сеттеры +$customer = (new AtolOnline\Entities\Client()) + ->setEmail('john@example.com') + ->setInn('+fasd3\q3fs_=nac9901 3928c-c') // 3399013928 + ->setName('John Doe') + ->setPhone('+1/22/99*73s dsdas654 5s6'); // +122997365456 + +// либо комбинация этих способов +``` + +Метод `setEmail()` проверяет входную строку на длину (до 64 символов) и валидность формата email. +Выбрасывает исключения: +* `AtolEmailTooLongException` (если слишком длинный email); +* `AtolEmailValidateException` (если email невалиден). + +Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр). +Выбрасывает исключение `AtolInnWrongLengthException` (если длина строка ИНН некорректна). + +Метод `setName()` проверяет входную строку на длину (до 256 символов). +Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное наименование). + +Метод `setPhone()` чистит входную строку от всех символов, кроме цифр и знака `+`, и проверяет длину (до 64 символов). +Выбрасывает исключение `AtolPhoneTooLongException` (если слишком длинный номер телефона). + +Конструктор может выбрасывать любое из указанных выше исключений, если в него передаются значения. + +Получить установленные значения атрибутов можно через геттеры: + +```php +$customer->getInn(); +$customer->getEmail(); +$customer->getName(); +$customer->getPhone(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $customer; +$json_string = (string)$customer; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $customer->jsonSerialize(); +``` diff --git a/docs/company.md b/docs/company.md new file mode 100644 index 0000000..51ae5dc --- /dev/null +++ b/docs/company.md @@ -0,0 +1,72 @@ +# Работа с компанией (продавцом) + +Объект компании инициализируется следующим образом: + +```php +$customer = new AtolOnline\Entities\Company(); +``` + +У объекта компании должны быть указаны все следующие атрибуты: +* email (тег ФФД 1117); +* ИНН (тег ФФД 1018); +* тип системы налогообложения (тег ФФД 1055) - все типы перечислены в классе `AtolOnline\Constants\SnoTypes`; +* адрес места расчётов (тег ФФД 1187) - для интернет-сервисов указывается URL с протоколом. + +> Все эти атрибуты являются **обязательными**. +> Для тестового режима используйте значения ИНН и адреса места расчётов, [указанные здесь](https://online.atol.ru/files/ffd/test_sreda.txt). + +Указать эти атрибуты можно двумя способами: + +```php +// 1 способ - через конструктор +$company = new AtolOnline\Entities\Company( + AtolOnline\Constants\SnoTypes::OSN, // тип СНО + '5544332219', // номер ИНН + 'https://v4.online.atol.ru', // адрес места расчётов + 'company@example.com' // email +); + +// 2 способ - через сеттеры +$company = (new AtolOnline\Entities\Company()) + ->setEmail('company@example.com') + ->setInn('5544332219') + ->setSno(AtolOnline\Constants\SnoTypes::USN_INCOME) + ->setPaymentAddress('https://v4.online.atol.ru'); + +// либо комбинация этих способов +``` + +Метод `setEmail()` проверяет входную строку на длину (до 64 символов) и валидность формата email. +Выбрасывает исключения: +* `AtolEmailTooLongException` (если слишком длинный email); +* `AtolEmailValidateException` (если email невалиден). + +Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр). +Выбрасывает исключение `AtolInnWrongLengthException` (если длина строка ИНН некорректна). + +Метод `setPaymentAddress()` проверяет длину (до 256 символов). +Выбрасывает исключение `AtolPaymentAddressTooLongException` (если слишком длинный адрес места расчётов). + +Конструктор может выбрасывать любое из указанных выше исключений, если в него передаются параметры. + +Получить установленные значения параметров можно через геттеры: + +```php +$company->getInn(); +$company->getEmail(); +$company->getPaymentAddress(); +$company->getSno(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $company; +$json_string = (string)$company; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $company->jsonSerialize(); +``` diff --git a/docs/correction_info.md b/docs/correction_info.md new file mode 100644 index 0000000..4b55109 --- /dev/null +++ b/docs/correction_info.md @@ -0,0 +1,65 @@ +# Работа с данными коррекции + +Объект для данных коррекции инициализируется следующим образом: + +```php +$info = new AtolOnline\Entities\CorrectionInfo(); +``` + +У объекта должны быть указаны все следующие обязательные атрибуты: +* тип коррекции (тег ФФД 1173) - все типы перечислены в классе `AtolOnline\Constants\CorrectionTypes`; +* дата документа основания для коррекции в формате `d.m.Y` (тег ФФД 1178); +* номер документа основания для коррекции (тег ФФД 1179); +* описание коррекции (тег ФФД 1177). + +Указать эти атрибуты можно двумя способами: + +```php +use AtolOnline\{Entities\CorrectionInfo, Constants\CorrectionTypes}; + +// 1 способ - через конструктор +$info = new CorrectionInfo( + CorrectionTypes::SELF, // тип коррекции + '01.01.2019', // дата документа коррекции + '12345', // номер документа коррекции + 'test' // описание коррекции +); + +// 2 способ - через сеттеры +$info = (new CorrectionInfo()) + ->setType(CorrectionTypes::INSTRUCTION) + ->setDate('01.01.2019') + ->setName('test') + ->setNumber('9999'); + +// либо комбинация этих способов +``` + +Получить установленные значения атрибутов можно через геттеры: + +```php +$info->getType(); +$info->getDate(); +$info->getName(); +$info->getNumber(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $customer; +$json_string = (string)$customer; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $customer->jsonSerialize(); +``` + + + + + + + diff --git a/docs/documents.md b/docs/documents.md new file mode 100644 index 0000000..71121e1 --- /dev/null +++ b/docs/documents.md @@ -0,0 +1,159 @@ +# Работа с документами + +Объект документа инициализируется следующим образом: + +```php +$doc = new AtolOnline\Entities\Document(); +``` + +Для документов **прихода, возврата прихода, расхода и возврата расхода** должны быть указаны все следующие обязательные атрибуты: +* [клиент](/docs/client.md); +* [компания](/docs/company.md); +* [предметы расчёта](/docs/items.md); +* [оплаты](/docs/payments.md). + +Для документов **коррекции прихода и коррекции расхода** должны быть указаны все следующие обязательные атрибуты: +* [компания](/docs/company.md); +* [оплаты](/docs/payments.md); +* [ставки НДС](/docs/vats.md); +* [данные коррекции](/docs/correction_info.md). + +Для любых документов также могут быть указаны следующие необязательные атрибуты: +* ФИО кассира (тег ФФД - 1021). + +Установка атрибутов документа происходит через сеттеры. + +## Работа с клиентом + +Для этого существуют следующие методы: + +```php +$doc->setClient($client); +$doc->getClient(); +``` + +> О работе с клиентами более подробно читайте [здесь](/docs/client.md). + +## Работа с компанией + +Для этого существуют следующие методы: + +```php +$doc->setCompany($company); +$doc->getCompany(); +``` + +> О работе с компаниями более подробно читайте [здесь](/docs/company.md). + +## Работа с предметами расчёта + +Внутри документа существует [массив предметов расчёта](/docs/items.md#array). +По умолчанию он пуст. +Напрямую для манипуляций объект массива недоступен. +Работа с ним происходит через методы документа: + +```php +$doc->setItems([$item1, $item2]); +$doc->addItem($item3); +$doc->getItems(); +``` + +Соответственно, эти методы выбрасывают те же исключения, что методы самого массива. + +## Работа с оплатами + +Внутри документа существует [массив оплат](/docs/payments.md#array). +По умолчанию он пуст. +Напрямую для манипуляций объект массива недоступен. +Работа с ним происходит через методы документа: + +```php +$doc->setPayments([$payment1, $payment2]); +$doc->addPayment($payment3); +$doc->getPayments(); +``` + +Соответственно, эти методы выбрасывают те же исключения, что методы самого массива. + +Следует отметить, что если при выполнении метода `addPayment()` выполняются следующие условия: +* аргументом передан объект оплаты, у которого не задана сумма, +* ранее документу не задавались оплаты, +то автоматически этому объекту оплаты задаётся полная сумма чека. + +## Работа со ставками НДС + +Внутри документа существует [массив ставок НДС](/docs/vats.md#array). +По умолчанию он пуст. +Напрямую для манипуляций объект массива недоступен. +Работа с ним происходит через методы документа: + +```php +$doc->setVats([$vat1, $vat2]); +$doc->addVat($vat3); +$doc->getVats(); +``` + +Соответственно, эти методы выбрасывают те же исключения, что методы самого массива. + +Также существует метод `clearVats()`, который удаляет все вложенные объекты ставок НДС - из предметов расчёта и самого документа. + +Следует отметить, что если при выполнении метода `addVat()` выполняются следующие условия: +* аргументом передан объект ставки, у которого не задана сумма, +* ранее документу не задавались ставки, +то автоматически этому объекту налога задаётся полная сумма чека, от которой расчитывается итоговый размер налога. + +## Общая сумма документа + +Расчёт происходит автоматически в следующих случаях: +* изменение предметов расчёта (`setItems()`, `addItem()`); +* добавление оплат (`addPayment()` в случае, когда оплата передана без суммы); +* изменение ставок НДС (`setVats()`, `clearVats()`, `addVat()` в случае, когда ставка передана без суммы); +* приведение объекта документа к строке. + +Также можно вызвать вручную метод `calcTotal()`. +Он расчитывает полную сумму чека по предметам расчёта и пересчитывает **все** налоговые ставки. + +Получить итог можно с помощью метода `getTotal()`. + +Всё в рублях. + + +## Работа с данными коррекции + +Если документ создаётся с целью коррекции прихода или расхода, то он обязательно должен содержать [данные коррекции](/docs/correction_info.md). + +Задать и получить эти данные очень просто: + +```php +$doc->setCorrectionInfo(new AtolOnline\Entities\CorrectionInfo( + AtolOnline\Constants\CorrectionTypes::SELF, // тип коррекции + '01.01.2019', // дата документа коррекции + '12345', // номер документа коррекции + 'test' // описание коррекции +)); +$doc->getCorrectionInfo(); +``` + +## Работа с кассиром + +Для этого существуют следующие методы: + +```php +$doc->setCashier('Иванова Лариса Васильевна'); +$doc->getCashier(); +``` + +## Прочее + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $doc; +$json_string = (string)$doc; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $doc->jsonSerialize(); +``` diff --git a/docs/items.md b/docs/items.md new file mode 100644 index 0000000..b137e3f --- /dev/null +++ b/docs/items.md @@ -0,0 +1,194 @@ +# Работа с предметами расчёта + +## Один объект + +Объект предмета расчёта инициализируется следующим образом: + +```php +$vat = new AtolOnline\Entities\Item(); +``` + +У объекта предмета расчёта должны быть указаны все следующие обязательные атрибуты: +* наименование (тег ФФД - 1030); +* цена (тег ФФД - 1079); +* количество, вес (тег ФФД - 1023). + +У объекта предмета расчёта также могут быть указаны следующие необязательные атрибуты: +* единица измерения количества (тег ФФД - 1197); +* признак способа оплаты (тег ФФД - 1214) - перечислены в классе `AtolOnline\Constants\PaymentMethods`; +* признак предмета расчёта (тег ФФД - 1212) - перечислены в классе `AtolOnline\Constants\PaymentObjects`; +* [ставка НДС](/docs/vats.md); +* дополнительный реквизит (тег ФФД - 1191). + +Установить многие (но не все) атрибуты можно следующими способами: + +```php +use AtolOnline\{ + Constants\PaymentMethods, + Constants\PaymentObjects, + Constants\VatTypes, + Entities\Item +}; + +// 1 способ - через конструктор +$item = new Item( + 'Банан', // наименование + 100, // цена + 1, // количество, вес + 'кг', // единица измерения + VatTypes::VAT20, // ставка НДС + PaymentObjects::SERVICE, // признак предмета расчёта + PaymentMethods::FULL_PAYMENT // признак способа расчёта +); + +// 2 способ - через сеттеры +$item = new Item(); +$item->setName('Банан'); +$item->setPrice(100); +$item->setQuantity(2.41); +//$item->setQuantity(2.41, 'кг'); +$item->setMeasurementUnit('кг'); +$item->setVatType(VatTypes::VAT20); +$item->setPaymentObject(PaymentObjects::COMMODITY); +$item->setPaymentMethod(PaymentMethods::FULL_PAYMENT); +``` + +Метод `setName()` проверяет входную строку на длину (до 128 символов). +Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное наименование). + +Метод `setPrice()` проверяет аргумент на величину (до 42949672.95) и пересчитывает общую стоимость. +Выбрасывает исключение `AtolPriceTooHighException` (если цена слишком высока). + +Метод `setMeasurementUnit()` проверяет входную строку на длину (до 16 символов). +Выбрасывает исключение `AtolUnitTooLongException` (если слишком длинная строка единицы измерения). + +Метод `setQuantity()` проверяет первый аргумент на величину (до 99999.999) и пересчитывает общую стоимость. +Выбрасывает исключения: +* `AtolQuantityTooHighException` (если количество слишком велико); +* `AtolPriceTooHighException` (если общая стоимость слишком велика). + +Также вторым аргументом может принимать единицу измерения количества. +В этом случае дополнительно работает сеттер `setMeasurementUnit()`. + +Метод `setVatType()` задаёт тип ставки НДС, пересчитывает размер налога и общую стоимость. +Выбрасывает исключение `AtolPriceTooHighException` (если цена слишком высока). +Может принимать `null` для удаления налога. + +Дополнительный реквизит устанавливается отдельным методом `setUserData()`: + +```php +$item->setUserData('some data'); +``` + +Он проверяет строку на длину (до 64 символов). +Выбрасывает исключение `AtolUserdataTooLongException` (если слишком длинный дополнительный реквизит). + +Для установки признака предмета расчёта существует метод `setPaymentObject()`. +На вход следует передавать одной из значений, перечисленных в классе `AtolOnline\Constants\PaymentObjects`. + +```php +$item->setPaymentObject(AtolOnline\Constants\PaymentObjects::JOB); +``` + +Для установки признака способа оплаты существует метод `setPaymentMethod()`. +На вход следует передавать одной из значений, перечисленных в классе `AtolOnline\Constants\PaymentMethods`. + +```php +$item->setPaymentMethod(AtolOnline\Constants\PaymentMethods::FULL_PAYMENT); +``` + +Для получения заданных значений атрибутов реализованы соответствующие геттеры: + +```php +$item->getName(); +$item->getPrice(); +$item->getQuantity(); +$item->getMeasurementUnit(); +$item->getPaymentMethod(); +$item->getPaymentObject(); +$item->getVat(); // возвращает объект ставки НДС либо null +$item->getUserData(); +``` + +Для пересчёта общей стоимости и размера налога существует метод `calcSum()`. + +```php +$item->calcSum(); +``` + +Этот метод отрабатывает при вызове `setPrice()`, `setQuantity()` и `setVatType()`. +Выбрасывает исключение `AtolPriceTooHighException` (если общая сумма слишком высока). + +Получить уже расчитанную общую сумму можно простым геттером: + +```php +$item->getSum(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $item; +$json_string = (string)$item; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $item->jsonSerialize(); +``` + + +## Массив объектов предметов расчёта + +> Максимальное количество объектов в массиве - 100. + +Массив инициализируется следующим образом: + +```php +$item_array = new AtolOnline\Entities\ItemArray(); +``` + +Чтобы задать содержимое массива, используйте метод `set()`: + +```php +$item_array->set([ + $item_object1, + $item_object2 +]); +``` + +Очистить его можно передачей в сеттер пустого массива: + +```php +$item_array->set([]); +``` + +Чтобы добавить объект к существующим элементам массива, используйте метод `add()`: + +```php +$item = new AtolOnline\Entities\Item('Банан', 100, 1); +$item_array->add($item); +``` + +Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением. +Выбрасывают исключение `AtolTooManyItemsException` (если в массиве уже максимальное количество объектов). + +Чтобы получить содержимое массива, используйте метод `get()`: + +```php +$item_array->get(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $item_array; +$json_string = (string)$item_array; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $item_array->jsonSerialize(); +``` diff --git a/docs/kkt.md b/docs/kkt.md new file mode 100644 index 0000000..df65401 --- /dev/null +++ b/docs/kkt.md @@ -0,0 +1,217 @@ +# Работа с ККТ + +Объект ККТ инициализируется следующим образом: + +```php +$kkt = new AtolOnline\Api\Kkt(); +``` + +## Настройка ККТ + +Для работы с облачной ККТ необходимы следующие параметры: +* логин кассы; +* пароль кассы; +* код группы кассы; + +Чтоб получить их, нужно: +1. авторизоваться на [online.atol.ru](https://online.atol.ru/lk/Account/Login); +2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**. + Скачается XML-файл с нужными настройками. + +Установить эти параметры можно двумя путями: + +```php +// 1 способ - через конструктор +$kkt = new AtolOnline\Api\Kkt($group, $login, $password); + +// 2 способ - через сеттеры +$kkt = (new AtolOnline\Api\Kkt()) + ->setLogin($login) + ->setGroup($group) + ->setPassword($password); +``` + +Получить заданные параметры можно через соответствующие геттеры: + +```php +$kkt->getLogin(); +$kkt->getPassword(); +$kkt->getGroup(); +``` + +Также для работы потребуются: +* ИНН продавца; +* URL места расчёта (ссылка на ваш интернет-сервис). + +Эти параметры нужно задать [объекту компании](/docs/company.md), который будет передаваться в документах через эту ККТ. + +## Тестовый режим + +На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе. +АТОЛ предоставляет нам отдельную тестовую ККТ. +[Её настройки](https://online.atol.ru/files/ffd/test_sreda.txt) уже указаны в коде библиотеки. +*Под тестовым режимом работы подразумевается использование этой тестовой ККТ.* + +В библиотеке есть переключатель настроек ККТ. +С его помощью можете поменять вашу боевую ККТ на тестовую и наоборот. +Это можно сделать одним из следующих способов: + +```php +// включить в любом месте кода: +$kkt->setTestMode(); +$kkt->setTestMode(true); +$kkt->setTestMode(false); // выключить +``` + +> Если вы включили тестовый режим (как показано выше), то используются именно эта ККТ, а не ваша. +> После выключения тестового режима настройки доступа к ККТ меняются на ваши (используется уже ваша ККТ). + +Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом режиме (на собственной ККТ). +В этом случае важно понимать следующее: +1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**; +2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html); +3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах; +4. вся ответственность за пп. 1-3 и последствия ложится только на вас. + +## Регистрация документа + +Для регистрации документа **прихода** необходимо вызвать метод `sell()`: + +```php +$result = $kkt->sell($document); +``` + +Для регистрации документа **возврата прихода** необходимо вызвать метод `sellRefund()`: + +```php +$result = $kkt->sellRefund($document); +``` + +Для регистрации документа **расхода** необходимо вызвать метод `buy()`: + +```php +$result = $kkt->buy($document); +``` + +Для регистрации документа **возврата расхода** необходимо вызвать метод `buyRefund()`: + +```php +$result = $kkt->buyRefund($document); +``` + +Для операций, перечисленных выше, документы не должны содержать [данных коррекции](/docs/documents.md#correction). +Тогда как для операций коррекции, которые описаны ниже, эти данные должны присутствовать. + +Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellRefund()`: + +```php +$result = $kkt->sellCorrection($document); +``` + +Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyRefund()`: + +```php +$result = $kkt->buyCorrection($document); +``` + +### Передача callback_url + +Перед регистрацией документа можно указать `callback_url`. +АТОЛ отправит на указанный URL результат регистрации. +Вам необходимо расположить по этому адресу скрипт, обрабатывающий этот результат. + +```php +$kkt->setCallbackUrl('http://example.com/process-kkt-result'); +$kkt->getCallbackUrl(); +``` + +## Обработка результата регистрации + +Методы `sell()`, `sellRefund()`, `sellCorrection()`, `buy()`, `buyRefund()` и `buyCorrection()` возвращают объект `AtolOnline\Api\KktResponse`. + +Этот же объект можно получить через геттер `getLastResponse()`. + +Этот объект содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа. + +```php +$result = $kkt->getLastResponse(); +$headers = $result->getHeaders(); // вернёт заголовки +$code = $result->getCode(); // вернёт код ответа +$body = $result->getContent(); // вернёт JSON-декодированное тело ответа +``` + +Обращаться к полям JSON-декодированного объекта можно опуская вызов метода `getContent()` таким образом: + +```php +// вернёт значение поля uuid +$uuid = $result->getContent()->uuid; +$uuid = $result->uuid; +// вернёт текст ошибки +$err_text = $result->getContent()->error->text; +$err_text = $result->error->text; +``` + +Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`: + +```php +$kkt->isValid(); // вернёт true, если ошибок нет +``` + +## Проверка статуса документа + +Если перед отправкой документа на регистрацию был задан callback_url через метод `setCallbackUrl()`, то ответ придёт на указанный адрес автоматически, как только документ обработается на стороне ККТ. +Ответ может быть как об успешной регистрации, так и ошибочной. + +В любом случае, вам доступны два метода, с помощью которых вы можете проверять статус документа самостоятельно: + +```php +$kkt->getDocumentStatus(); +$kkt->pollDocumentStatus(); +``` + +Эти методы принимают на вход `uuid` кода регистрации. +Этот UUID можно получить из ответа, полученного при отправке документа на регистрацию: + +```php +$sell_result = $kkt->sell($document); +$kkt->pollDocumentStatus($sell_result->uuid); +$kkt->getDocumentStatus($sell_result->uuid); +``` + +Метод `pollDocumentStatus()` многократно опрашивает ККТ на предмет состояния документа. +Метод может принимать до трёх параметров: +* uuid; +* количество попыток (по умолчанию — 5); +* время между попытками в секундах (по умолчанию — 1). + +```php +// Проверять статус 10 раз на протяжении 20 секунд — каждые две секунды +$kkt->pollDocumentStatus($sell_result->uuid, 10, 20); +``` + +Учитывайте, что метод вернёт результат как только сменится статус регистрации на успешный или ошибочный. + +Использовать его лучше сразу после отправки документа на регистрацию (как в примере выше). + +> Как правило, полная регистрация одного документа занимает 4-5 секунд. + +Метод `getDocumentStatus()` принимает на вход только `uuid` и запрашивает состояние документа лишь единожды. +Использовать его целесообразнее в те моменты, когда нет необходимости знать успех регистрации сразу после отправки документа. + +> Обратите внимание, что АТОЛ позволяет получать статус документа в течение 32 суток с момента его регистрации. + +Методы `pollDocumentStatus()` и `getDocumentStatus()` возвращают объект `AtolOnline\Api\KktResponse`. +Оба выбрасывают исключение `AtolUuidValidateException` (если переданная строка UUID невалидна). + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $item; +$json_string = (string)$item; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $item->jsonSerialize(); +``` diff --git a/docs/payments.md b/docs/payments.md new file mode 100644 index 0000000..a2e55af --- /dev/null +++ b/docs/payments.md @@ -0,0 +1,126 @@ +# Работа с оплатами + +## Один объект + +Объект оплаты инициализируется следующим образом: + +```php +$payment = new AtolOnline\Entities\Payment(); +``` + +У объекта оплаты должны быть указаны все следующие атрибуты: +* тип оплаты (теги ФФД: 1031, 1081, 1215, 1216, 1217) - все типы перечислены в классе `AtolOnline\Constants\PaymentTypes` (по умолчанию `ELECTRON`) +* сумма оплаты (теги ФФД: 1031, 1081, 1215, 1216, 1217; по умолчанию 0) + +> Все эти атрибуты являются **обязательными**. + +Установить атрибуты можно следующими способами: + +```php +// 1 способ - через конструктор +$payment = new AtolOnline\Entities\Payment( + AtolOnline\Constants\PaymentTypes::OTHER, // тип оплаты + 123.45 // сумма оплаты +); + +// 2 способ - через сеттер +$payment = (new AtolOnline\Entities\Payment()) + ->setType(AtolOnline\Constants\PaymentTypes::OTHER) // тип оплаты + ->setSum(123.45); // сумма оплаты +``` + +Размер налога высчитывается автоматически из общей суммы. +Сумму, от которой нужно расчитать размер налога, можно передать следующими способами: + +```php +// 1 способ - через конструктор +$payment = new AtolOnline\Entities\Payment( + AtolOnline\Constants\PaymentTypes::CASH, // тип оплаты + 1234.56 // сумма оплаты в рублях +); + +// 2 способ - через сеттер +$payment = (new AtolOnline\Entities\Payment()) + ->setType(AtolOnline\Constants\PaymentTypes::CASH) // тип оплаты + ->setSum(1234.56); // сумма оплаты в рублях +``` + +Получить установленную сумму оплаты в рублях можно через геттер `getSum()`: + +```php +var_dump($payment->getSum()); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $payment; +$json_string = (string)$payment; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $payment->jsonSerialize(); +``` + + +## Массив объектов оплат + +> Максимальное количество объектов в массиве - 10. + +Массив инициализируется следующим образом: + +```php +$payment_array = new AtolOnline\Entities\PaymentArray(); +``` + +Чтобы задать содержимое массива, используйте метод `set()`: + +```php +use AtolOnline\{Constants\PaymentTypes, Entities\Payment}; + +$payment_array->set([ + new Payment(PaymentTypes::ELECTRON, 123), + new Payment(PaymentTypes::ELECTRON, 53.2), + new Payment(PaymentTypes::ELECTRON, 23.99), + new Payment(PaymentTypes::ELECTRON, 11.43) +]); +``` + +Очистить его можно передачей в сеттер пустого массива: + +```php +$payment_array->set([]); +``` + +Чтобы добавить объект к существующим элементам массива, используйте метод `add()`: + +```php +use AtolOnline\{Constants\PaymentTypes, Entities\Payment}; + +$payment = new Payment(PaymentTypes::PRE_PAID, 20); +$payment_array->add($payment); +``` + +Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением. +Выбрасывают исключение `AtolTooManyPaymentsException` (если в массиве уже максимальное количество объектов). + +Чтобы получить содержимое массива, используйте метод `get()`: + +```php +$payment_array->get(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $payment_array; +$json_string = (string)$payment_array; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $payment_array->jsonSerialize(); +``` diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..56cc90e --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,14 @@ +# Документация к библиотеке atol-online + +Содержание: +1. [Работа с клиентами (покупателями)](client.md) +2. [Работа с компанией (продавцом)](company.md) +3. [Работа с оплатами](payments.md) +4. [Работа со ставками НДС](vats.md) +5. [Работа с предметами расчёта](items.md) +6. [Работа с документами](documents.md) +7. [Работа с ККТ](kkt.md) + +--- + +Если вы нашли опечатку или какое-то несоответствие — делайте pull-request. diff --git a/docs/vats.md b/docs/vats.md new file mode 100644 index 0000000..e1f4f42 --- /dev/null +++ b/docs/vats.md @@ -0,0 +1,186 @@ +# Работа со ставками НДС + +## Один объект + +Объект ставки НДС инициализируется следующим образом: + +```php +$vat = new AtolOnline\Entities\Vat(); +``` + +У объекта ставки должны быть указаны все следующие атрибуты: +* тип ставки (теги ФФД: 1199 для предмета расчёта; 1105, 1104, 1103, 1102, 1107, 1106 для чека) - все типы перечислены в классе `AtolOnline\Constants\VatTypes` (по умолчанию `NONE`) +* размер налога (теги ФФД: 1200 для предмета расчёта; 1105, 1104, 1103, 1102, 1107, 1106 для чека) + +> Все эти атрибуты являются **обязательными**. + +Установить тип ставки НДС можно следующими способами: + +```php +// 1 способ - через конструктор +$vat = new AtolOnline\Entities\Vat( + AtolOnline\Constants\VatTypes::VAT20 // тип ставки +); + +// 2 способ - через сеттер +$vat = (new AtolOnline\Entities\Vat()) + ->setType(AtolOnline\Constants\VatTypes::VAT20); // тип ставки +``` + +Размер налога высчитывается автоматически из общей суммы. +Сумму, от которой нужно расчитать размер налога, можно передать следующими способами: + +```php +// 1 способ - через конструктор +$vat = new AtolOnline\Entities\Vat( + AtolOnline\Constants\VatTypes::VAT10, // тип ставки + 1234.56 // общая сумма в рублях +); + +// 2 способ - через сеттер +$vat = (new AtolOnline\Entities\Vat()) + ->setType(AtolOnline\Constants\VatTypes::VAT10) // тип ставки + ->setSum(150); // общая сумма в рублях +``` + +Сумму можно установить и до установки типа ставки. +Объект её запомнит и пересчитает итоговый размер налога при смене типа ставки: + +```php +$vat = (new AtolOnline\Entities\Vat()) + ->setSum(150) // общая сумма в рублях + ->setType(AtolOnline\Constants\VatTypes::VAT10); // тип ставки +``` + +Получить установленную расчётную сумму в рублях можно через геттер `getSum()`: + +```php +var_dump($vat->getSum()); +// double(150) +``` + +Получить расчитанный размер налога в рублях можно через геттер `getFinalSum()`: + +```php +var_dump($vat->getFinalSum()); +// double(15): для примера выше это 10% от 150р = 15р +``` + +Общую сумму, из которой расчитывается размер налога, можно увеличить, используя метод `addSum()`. +Указанная в рублях сумма увеличится и итоговый размер налога пересчитается. +Для уменьшения суммы следует передать отрицательное число. + +Разберём комплексный пример изменения типа ставки и расчётной суммы: + +```php +use AtolOnline\{Entities\Vat, Constants\VatTypes}; + +$vat = new Vat(VatTypes::VAT20, 120); +echo "НДС20 от 120р: "; +var_dump($vat->getFinalSum()); + +echo "НДС10 от 120р: "; +$vat->setType(VatTypes::VAT10); +var_dump($vat->getFinalSum()); + +$vat->addSum(40); +echo "НДС10 от {$vat->getSum()}р: "; +var_dump($vat->getFinalSum()); + +$vat->setType(VatTypes::VAT20)->addSum(-20); +echo "НДС20 от {$vat->getSum()}р: "; +var_dump($vat->getFinalSum()); + +$vat->setType(VatTypes::VAT120); +echo "НДС20/120 от {$vat->getSum()}р: "; +var_dump($vat->getFinalSum()); +``` + +Результат будет следующим: + +``` +НДС20 от 120р: +double(24) +НДС10 от 120р: +double(12) +НДС10 от 160р: +double(16) +НДС20 от 140р: +double(28) +НДС20/120 от 140р: +double(23.33) +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $vat; +$json_string = (string)$vat; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $vat->jsonSerialize(); +``` + + +## Массив объектов ставок НДС + +> Максимальное количество в массиве - 6. + +Массив инициализируется следующим образом: + +```php +$vat_array = new AtolOnline\Entities\VatArray(); +``` + +Чтобы задать содержимое массива, используйте метод `set()`: + +```php +use AtolOnline\{Constants\VatTypes, Entities\Vat}; + +$vat_array->set([ + new Vat(VatTypes::VAT10, 123), + new Vat(VatTypes::VAT110, 53.2), + new Vat(VatTypes::VAT20, 23.99), + new Vat(VatTypes::VAT120, 11.43) +]); +``` + +Очистить его можно передачей в сеттер пустого массива: + +```php +$vat_array->set([]); +``` + +Чтобы добавить объект к существующим элементам массива, используйте метод `add()`: + +```php +use AtolOnline\{Constants\VatTypes, Entities\Vat}; + +$vat = new Vat(VatTypes::VAT20, 20); +$vat_array->add($vat); +``` + +Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением. +Выбрасывают исключение `AtolTooManyVatsException` (если в массиве уже максимальное количество объектов). + +Чтобы получить содержимое массива, используйте метод `get()`: + +```php +$vat_array->get(); +``` + +Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`: + +```php +echo $vat_array; +$json_string = (string)$vat_array; +``` + +Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`: + +```php +$json_array = $vat_array->jsonSerialize(); +``` diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..8814837 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,25 @@ + + + + + ClientTest.php + CompanyTest.php + VatTest.php + ./tests/Unit + + + ItemTest.php + ./tests/Feature + + + \ No newline at end of file diff --git a/src/AtolOnline/Api/Kkt.php b/src/AtolOnline/Api/Kkt.php new file mode 100644 index 0000000..7e695c4 --- /dev/null +++ b/src/AtolOnline/Api/Kkt.php @@ -0,0 +1,500 @@ +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 (empty($login)) { + throw new AtolKktLoginEmptyException(); + } elseif (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 (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['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\AtolUuidValidateException Некорректный UUID документа + */ + public function getDocumentStatus(string $uuid) + { + $uuid = trim($uuid); + if (!Uuid::isValid($uuid)) { + throw new AtolUuidValidateException($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; + } + + /** + * Сбрасывает настройки ККТ по умолчанию + */ + 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->auth_token; + } + 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 + * @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 + */ + 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 Объект документа + * @return \AtolOnline\Api\KktResponse + * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа + * @throws \Exception + */ + protected function registerDocument(string $api_method, string $type, Document $document) + { + $type = trim($type); + if (!in_array($type, ['receipt', 'correction'])) { + throw new AtolWrongDocumentTypeException($type); + } + $this->auth(); + $data = [ + 'timestamp' => date('d.m.y H:i:s'), + 'external_id' => Uuid::uuid4()->toString(), + 'service' => ['callback_url' => $this->getCallbackUrl()], + $type => $document, + ]; + return $this->sendAtolRequest('POST', trim($api_method), $data); + } + + /** + * Возвращает текущий токен авторизации + * + * @return string + */ + protected function getAuthToken() + { + return $this->auth_token; + } +} diff --git a/src/AtolOnline/Api/KktResponse.php b/src/AtolOnline/Api/KktResponse.php new file mode 100644 index 0000000..de31ce6 --- /dev/null +++ b/src/AtolOnline/Api/KktResponse.php @@ -0,0 +1,123 @@ +code = $response->getStatusCode(); + $this->headers = $response->getHeaders(); + $this->content = json_decode($response->getBody()); + } + + /** + * Возвращает заголовки ответа + * + * @return array + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * Возвращает запрошенный параметр из декодированного объекта результата + * + * @param $name + * @return mixed + */ + public function __get($name) + { + return $this->getContent()->$name; + } + + /** + * Возвращает код ответа + * + * @return int + */ + public function getCode(): int + { + return $this->code; + } + + /** + * Возвращает объект результата запроса + * + * @return \stdClass + */ + public function getContent(): stdClass + { + return $this->content; + } + + /** + * Проверяет успешность запроса по соержимому результата + * + * @return bool + */ + public function isValid() + { + return !empty($this->getCode()) + && !empty($this->getContent()) + && empty($this->getContent()->error) + && (int)$this->getCode() < 400; + } + + /** + * Возвращает текстовое представление + */ + public function __toString() + { + return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE); + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return [ + 'code' => $this->code, + 'headers' => $this->headers, + 'body' => $this->content, + ]; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Constants/CorrectionTypes.php b/src/AtolOnline/Constants/CorrectionTypes.php new file mode 100644 index 0000000..5c6f894 --- /dev/null +++ b/src/AtolOnline/Constants/CorrectionTypes.php @@ -0,0 +1,28 @@ +jsonSerialize(), JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/Client.php b/src/AtolOnline/Entities/Client.php new file mode 100644 index 0000000..51784d5 --- /dev/null +++ b/src/AtolOnline/Entities/Client.php @@ -0,0 +1,149 @@ +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 (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 (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; + } +} diff --git a/src/AtolOnline/Entities/Company.php b/src/AtolOnline/Entities/Company.php new file mode 100644 index 0000000..2569410 --- /dev/null +++ b/src/AtolOnline/Entities/Company.php @@ -0,0 +1,137 @@ +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 (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(), + ]; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/CorrectionInfo.php b/src/AtolOnline/Entities/CorrectionInfo.php new file mode 100644 index 0000000..fc24156 --- /dev/null +++ b/src/AtolOnline/Entities/CorrectionInfo.php @@ -0,0 +1,171 @@ +setType($type); + } + if ($base_date) { + $this->setDate($base_date); + } + if ($base_number) { + $this->setNumber($base_number); + } + if ($base_name) { + $this->setName($base_name); + } + } + + /** + * Возвращает номер документа основания для коррекции. + * Тег ФФД - 1179. + * + * @return string|null + */ + public function getNumber(): ?string + { + return $this->base_name; + } + + /** + * Устанавливает номер документа основания для коррекции. + * Тег ФФД - 1179. + * + * @param string $number + * @return $this + */ + public function setNumber(string $number) + { + $this->base_number = trim($number); + return $this; + } + + /** + * Возвращает описание коррекции. + * Тег ФФД - 1177. + * + * @return string|null + */ + public function getName(): ?string + { + return $this->base_name; + } + + /** + * Устанавливает описание коррекции. + * Тег ФФД - 1177. + * + * @param string $name + * @return $this + */ + public function setName(string $name) + { + $this->base_name = trim($name); + return $this; + } + + /** + * Возвращает дату документа основания для коррекции. + * Тег ФФД - 1178. + * + * @return string|null + */ + public function getDate(): ?string + { + return $this->base_date; + } + + /** + * Устанавливает дату документа основания для коррекции. + * Тег ФФД - 1178. + * + * @param string $date Строка в формате d.m.Y + * @return $this + */ + public function setDate(string $date) + { + $this->base_date = $date; + return $this; + } + + /** + * Возвращает тип коррекции. + * Тег ФФД - 1173. + * + * @return string|null + */ + public function getType(): ?string + { + return $this->type; + } + + /** + * Устанавливает тип коррекции. + * Тег ФФД - 1173. + * + * @param string $type + * @return $this + */ + public function setType(string $type) + { + $this->type = $type; + return $this; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return [ + 'type' => $this->getType() ?? '', // обязателен + 'base_date' => $this->getDate() ?? '', // обязателен + 'base_number' => $this->getNumber() ?? '', // обязателен + 'base_name' => $this->getName() ?? '' // обязателен + ]; + } +} diff --git a/src/AtolOnline/Entities/Document.php b/src/AtolOnline/Entities/Document.php new file mode 100644 index 0000000..fc8ff00 --- /dev/null +++ b/src/AtolOnline/Entities/Document.php @@ -0,0 +1,354 @@ +vats = new VatArray(); + $this->payments = new PaymentArray(); + $this->items = new ItemArray(); + } + + /** + * Удаляет все налоги из документа и предметов расчёта + * + * @return $this + * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма + * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС + */ + public function clearVats() + { + $this->setVats([]); + foreach ($this->getItems() as &$item) { + $item->setVatType(null); + } + $this->calcTotal(); + return $this; + } + + /** + * Добавляет новую ставку НДС в массив ставок НДС + * + * @param \AtolOnline\Entities\Vat $vat Объект ставки НДС + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС + */ + public function addVat(Vat $vat) + { + if (count($this->getVats()) == 0 && !$vat->getSum()) { + $vat->setSum($this->calcTotal()); + } + $this->vats->add($vat); + $this->calcTotal(); + return $this; + } + + /** + * Возвращает массив ставок НДС + * + * @return \AtolOnline\Entities\Vat[] + */ + public function getVats(): array + { + return $this->vats->get(); + } + + /** + * Устанавливает массив ставок НДС + * + * @param \AtolOnline\Entities\Vat[] $vats Массив ставок НДС + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС + * @throws \Exception + */ + public function setVats(array $vats) + { + $this->vats->set($vats); + $this->calcTotal(); + return $this; + } + + /** + * Добавляет новую оплату в массив оплат + * + * @param \AtolOnline\Entities\Payment $payment Объект оплаты + * @return $this + * @throws \Exception + * @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат + */ + public function addPayment(Payment $payment) + { + if (count($this->getPayments()) == 0 && !$payment->getSum()) { + $payment->setSum($this->calcTotal()); + } + $this->payments->add($payment); + return $this; + } + + /** + * Возвращает массив оплат + * + * @return \AtolOnline\Entities\Payment[] + */ + public function getPayments(): array + { + return $this->payments->get(); + } + + /** + * Устанавливает массив оплат + * + * @param \AtolOnline\Entities\Payment[] $payments Массив оплат + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат + */ + public function setPayments(array $payments) + { + $this->payments->set($payments); + return $this; + } + + /** + * Добавляет новый предмет расчёта в массив предметов расчёта + * + * @param \AtolOnline\Entities\Item $item Объект предмета расчёта + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта + */ + public function addItem(Item $item) + { + $this->items->add($item); + return $this; + } + + /** + * Возвращает массив предметов расчёта + * + * @return \AtolOnline\Entities\Item[] + */ + public function getItems(): array + { + return $this->items->get(); + } + + /** + * Устанавливает массив предметов расчёта + * + * @param \AtolOnline\Entities\Item[] $items Массив предметов расчёта + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта + */ + public function setItems(array $items) + { + $this->items->set($items); + return $this; + } + + /** + * Возвращает заданного клиента (покупателя) + * + * @return Client + */ + public function getClient(): Client + { + return $this->client; + } + + /** + * Устанавливает клиента (покупателя) + * + * @param Client|null $client + * @return $this + */ + public function setClient(?Client $client) + { + $this->client = $client; + return $this; + } + + /** + * Возвращает заданную компанию (продавца) + * + * @return Company + */ + public function getCompany(): Company + { + return $this->company; + } + + /** + * Устанавливает компанию (продавца) + * + * @param Company|null $company + * @return $this + */ + public function setCompany(?Company $company) + { + $this->company = $company; + return $this; + } + + /** + * Возвращает ФИО кассира. Тег ФФД - 1021. + * + * @return string|null + */ + public function getCashier(): ?string + { + return $this->cashier; + } + + /** + * Устанавливает ФИО кассира. Тег ФФД - 1021. + * + * @param string|null $cashier + * @return $this + * @throws \AtolOnline\Exceptions\AtolCashierTooLongException + */ + public function setCashier(?string $cashier) + { + $cashier = trim($cashier); + if (strlen($cashier) > 64) { + throw new AtolCashierTooLongException($cashier); + } + $this->cashier = $cashier; + return $this; + } + + /** + * Возвращает данные коррекции + * + * @return \AtolOnline\Entities\CorrectionInfo|null + */ + public function getCorrectionInfo(): ?CorrectionInfo + { + return $this->correction_info; + } + + /** + * Устанавливает данные коррекции + * + * @param \AtolOnline\Entities\CorrectionInfo|null $correction_info + * @return $this + */ + public function setCorrectionInfo(?CorrectionInfo $correction_info) + { + $this->correction_info = $correction_info; + return $this; + } + + /** + * Пересчитывает, сохраняет и возвращает итоговую сумму чека по всем позициям (включая НДС). Тег ФФД - 1020. + * + * @return float + * @throws \Exception + */ + public function calcTotal() + { + $sum = 0; + foreach ($this->items->get() as $item) { + $sum += $item->calcSum(); + } + foreach ($this->vats->get() as $vat) { + $vat->setSum($sum); + } + return $this->total = round($sum, 2); + } + + /** + * Возвращает итоговую сумму чека. Тег ФФД - 1020. + * + * @return float + */ + public function getTotal(): float + { + return $this->total; + } + + /** + * @inheritDoc + * @throws \Exception + */ + public function jsonSerialize() + { + $json = [ + 'company' => $this->getCompany()->jsonSerialize(), // обязательно + 'payments' => $this->payments->jsonSerialize(), // обязательно + 'cashier' => $this->getCashier() ?? '', + ]; + if ($this->getCorrectionInfo()) { + $json['correction_info'] = $this->getCorrectionInfo()->jsonSerialize(); // обязательно для коррекционных + } else { + $json['client'] = $this->getClient()->jsonSerialize(); // обязательно для некоррекционных + $json['items'] = $this->items->jsonSerialize(); // обязательно для некоррекционных + $json['total'] = $this->calcTotal(); // обязательно для некоррекционных + } + if ($this->getVats()) { + $json['vats'] = $this->vats->jsonSerialize(); + } + return $json; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/Item.php b/src/AtolOnline/Entities/Item.php new file mode 100644 index 0000000..6bfc1ad --- /dev/null +++ b/src/AtolOnline/Entities/Item.php @@ -0,0 +1,396 @@ +setName($name); + } + if ($price) { + $this->setPrice($price); + } + if ($payment_object) { + $this->setPaymentObject($payment_object); + } + if ($quantity) { + $this->setQuantity($quantity); + } + if ($vat_type) { + $this->setVatType($vat_type); + } + if ($measurement_unit) { + $this->setMeasurementUnit($measurement_unit); + } + if ($payment_method) { + $this->setPaymentMethod($payment_method); + } + } + + /** + * Возвращает наименование. Тег ФФД - 1030. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Устаналивает наименование. Тег ФФД - 1030. + * + * @param string $name Наименование + * @return $this + * @throws AtolNameTooLongException Слишком длинное имя/наименование + */ + public function setName(string $name) + { + $name = trim($name); + if (strlen($name) > 128) { + throw new AtolNameTooLongException($name, 128); + } + $this->name = $name; + return $this; + } + + /** + * Возвращает цену в рублях. Тег ФФД - 1079. + * + * @return float + */ + public function getPrice() + { + return self::toRub($this->price); + } + + /** + * Устанавливает цену в рублях. Тег ФФД - 1079. + * + * @param float $rubles Цена за одну единицу в рублях + * @return $this + * @throws AtolPriceTooHighException Слишком высокая цена за одну единицу + */ + public function setPrice(float $rubles) + { + if ($rubles > 42949672.95) { + throw new AtolPriceTooHighException($rubles, 42949672.95); + } + $this->price = self::toKop($rubles); + $this->calcSum(); + return $this; + } + + /** + * Возвращает количество. Тег ФФД - 1023. + * + * @return float + */ + public function getQuantity(): float + { + return $this->quantity; + } + + /** + * Устанавливает количество. Тег ФФД - 1023. + * + * @param float $quantity Количество + * @param string|null $measurement_unit Единица измерения количества + * @return $this + * @throws AtolQuantityTooHighException Слишком большое количество + * @throws AtolPriceTooHighException Слишком высокая общая стоимость + * @throws AtolUnitTooLongException Слишком длинное название единицы измерения + */ + public function setQuantity(float $quantity, string $measurement_unit = null) + { + $quantity = round($quantity, 3); + if ($quantity > 99999.999) { + throw new AtolQuantityTooHighException($quantity, 99999.999); + } + $this->quantity = $quantity; + $this->calcSum(); + if ($measurement_unit) { + $this->setMeasurementUnit($measurement_unit); + } + return $this; + } + + /** + * Возвращает заданную единицу измерения количества. Тег ФФД - 1197. + * + * @return string + */ + public function getMeasurementUnit(): string + { + return $this->measurement_unit; + } + + /** + * Устанавливает единицу измерения количества. Тег ФФД - 1197. + * + * @param string $measurement_unit Единица измерения количества + * @return $this + * @throws AtolUnitTooLongException Слишком длинное название единицы измерения + */ + public function setMeasurementUnit(string $measurement_unit) + { + $measurement_unit = trim($measurement_unit); + if (strlen($measurement_unit) > 16) { + throw new AtolUnitTooLongException($measurement_unit, 16); + } + $this->measurement_unit = $measurement_unit; + return $this; + } + + /** + * Возвращает признак способа оплаты. Тег ФФД - 1214. + * + * @return string + */ + public function getPaymentMethod(): string + { + return $this->payment_method; + } + + /** + * Устанавливает признак способа оплаты. Тег ФФД - 1214. + * + * @param string $payment_method Признак способа оплаты + * @return $this + * @todo Проверка допустимых значений + */ + public function setPaymentMethod(string $payment_method) + { + $this->payment_method = trim($payment_method); + return $this; + } + + /** + * Возвращает признак предмета расчёта. Тег ФФД - 1212. + * + * @return string + */ + public function getPaymentObject(): string + { + return $this->payment_object; + } + + /** + * Устанавливает признак предмета расчёта. Тег ФФД - 1212. + * + * @param string $payment_object Признак предмета расчёта + * @return $this + * @todo Проверка допустимых значений + */ + public function setPaymentObject(string $payment_object) + { + $this->payment_object = trim($payment_object); + return $this; + } + + /** + * Возвращает ставку НДС + * + * @return \AtolOnline\Entities\Vat|null + */ + public function getVat(): ?Vat + { + return $this->vat; + } + + /** + * Устанавливает ставку НДС + * + * @param string|null $vat_type Тип ставки НДС. Передать null, чтобы удалить ставку. + * @return $this + * @throws \AtolOnline\Exceptions\AtolPriceTooHighException + */ + public function setVatType(?string $vat_type) + { + if ($vat_type) { + $this->vat + ? $this->vat->setType($vat_type) + : $this->vat = new Vat($vat_type); + } else { + $this->vat = null; + } + $this->calcSum(); + return $this; + } + + /** + * Возвращает дополнительный реквизит. Тег ФФД - 1191. + * + * @return string|null + */ + public function getUserData(): ?string + { + return $this->user_data; + } + + /** + * Устанавливает дополнительный реквизит. Тег ФФД - 1191. + * + * @param string $user_data Дополнительный реквизит. Тег ФФД - 1191. + * @return $this + * @throws AtolUserdataTooLongException Слишком длинный дополнительный реквизит + */ + public function setUserData(string $user_data) + { + $user_data = trim($user_data); + if (strlen($user_data) > 64) { + throw new AtolUserdataTooLongException($user_data, 64); + } + $this->user_data = $user_data; + return $this; + } + + /** + * Возвращает стоимость. Тег ФФД - 1043. + * + * @return float + */ + public function getSum(): float + { + return self::toRub($this->sum); + } + + /** + * Расчитывает стоимость и размер НДС на неё + * + * @return float + * @throws AtolPriceTooHighException Слишком большая сумма + */ + public function calcSum() + { + $sum = $this->quantity * $this->price; + if (self::toRub($sum) > 42949672.95) { + throw new AtolPriceTooHighException($sum, 42949672.95); + } + $this->sum = $sum; + if ($this->vat) { + $this->vat->setSum(self::toRub($sum)); + } + return $this->getSum(); + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + $json = [ + 'name' => $this->getName(), // обязательно + 'price' => $this->getPrice(), // обязательно + 'quantity' => $this->getQuantity(), // обязательно + 'sum' => $this->getSum(), // обязательно + 'measurement_unit' => $this->getMeasurementUnit(), + 'payment_method' => $this->getPaymentMethod(), + 'payment_object' => $this->getPaymentObject() + //TODO nomenclature_code + //TODO agent_info + //TODO supplier_info + //TODO excise + //TODO country_code + //TODO declaration_number + ]; + if ($this->getVat()) { + $json['vat'] = $this->getVat()->jsonSerialize(); + } + if ($this->getUserData()) { + $json['user_data'] = $this->getUserData(); + } + return $json; + } +} diff --git a/src/AtolOnline/Entities/ItemArray.php b/src/AtolOnline/Entities/ItemArray.php new file mode 100644 index 0000000..6951fb8 --- /dev/null +++ b/src/AtolOnline/Entities/ItemArray.php @@ -0,0 +1,111 @@ +set($items); + } + } + + /** + * Устанавливает массив предметов расчёта + * + * @param \AtolOnline\Entities\Item[] $items Массив предметов расчёта + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта + */ + public function set(array $items) + { + if ($this->validateCount($items)) { + $this->items = $items; + } + return $this; + } + + /** + * Добавляет предмет расчёта в массив + * + * @param \AtolOnline\Entities\Item $item Объект предмета расчёта + * @return $this + * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта + */ + public function add(Item $item) + { + if ($this->validateCount()) { + $this->items[] = $item; + } + return $this; + } + + /** + * Возвращает массив предметов расчёта + * + * @return \AtolOnline\Entities\Item[] + */ + public function get() + { + return $this->items; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + $result = []; + foreach ($this->get() as $item) { + $result[] = $item->jsonSerialize(); + } + return $result; + } + + /** + * Проверяет количество элементов в массиве + * + * @param array|null $items Если передать массив, то проверит количество его элементов. + * Иначе проверит количество уже присвоенных элементов. + * @return bool + * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта + */ + protected function validateCount(array $items = null) + { + if (($items && is_array($items) && count($items) >= self::MAX_COUNT) || count($this->items) == self::MAX_COUNT) { + throw new AtolTooManyItemsException(self::MAX_COUNT); + } + return true; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/Payment.php b/src/AtolOnline/Entities/Payment.php new file mode 100644 index 0000000..52d5944 --- /dev/null +++ b/src/AtolOnline/Entities/Payment.php @@ -0,0 +1,97 @@ +setType($payment_type); + $this->setSum($sum); + } + + /** + * Возвращает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217. + * + * @return int + */ + public function getType(): int + { + return $this->type; + } + + /** + * Устанавливает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217. + * + * @param int $type + * @return $this + */ + public function setType(int $type) + { + $this->type = $type; + return $this; + } + + /** + * Возвращает сумму оплаты + * + * @return float + */ + public function getSum(): float + { + return $this->sum; + } + + /** + * Устанавливает сумму оплаты + * + * @param float $sum + * @return $this + */ + public function setSum(float $sum) + { + $this->sum = $sum; + return $this; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return [ + 'type' => $this->getType(), + 'sum' => $this->getSum(), + ]; + } +} diff --git a/src/AtolOnline/Entities/PaymentArray.php b/src/AtolOnline/Entities/PaymentArray.php new file mode 100644 index 0000000..6abb2e2 --- /dev/null +++ b/src/AtolOnline/Entities/PaymentArray.php @@ -0,0 +1,111 @@ +set($payments); + } + } + + /** + * Устанавливает массив оплат + * + * @param Payment[] $payments + * @return $this + * @throws AtolTooManyPaymentsException Слишком много оплат + */ + public function set(array $payments) + { + if ($this->validateCount($payments)) { + $this->payments = $payments; + } + return $this; + } + + /** + * Добавляет новую оплату к заданным + * + * @param Payment $payment Объект оплаты + * @return $this + * @throws AtolTooManyPaymentsException Слишком много оплат + */ + public function add(Payment $payment) + { + if ($this->validateCount()) { + $this->payments[] = $payment; + } + return $this; + } + + /** + * Возвращает массив оплат + * + * @return Payment[] + */ + public function get() + { + return $this->payments; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + $result = []; + foreach ($this->get() as $payment) { + $result[] = $payment->jsonSerialize(); + } + return $result; + } + + /** + * Проверяет количество элементов в массиве + * + * @param Payment[]|null $payments Если передать массив, то проверит количество его элементов. + * Иначе проверит количество уже присвоенных элементов. + * @return bool + * @throws AtolTooManyPaymentsException Слишком много оплат + */ + protected function validateCount(array $payments = null) + { + if (($payments && is_array($payments) && count($payments) >= self::MAX_COUNT) || count($this->payments) == self::MAX_COUNT) { + throw new AtolTooManyPaymentsException(self::MAX_COUNT); + } + return true; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/Vat.php b/src/AtolOnline/Entities/Vat.php new file mode 100644 index 0000000..ca56174 --- /dev/null +++ b/src/AtolOnline/Entities/Vat.php @@ -0,0 +1,186 @@ +type = $type; + if ($rubles) { + $this->setSum($rubles); + } + } + + /** + * Устанавливает размер НДС от суммы в копейках + * + * @param string $type Тип ставки НДС + * @param int $kopeks Копейки + * @return float|int + * @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/ + * @see https://glavkniga.ru/situations/k500734 + * @see https://www.b-kontur.ru/nds-kalkuljator-online + */ + protected static function calculator(string $type, int $kopeks) + { + switch ($type) { + case VatTypes::NONE: + case VatTypes::VAT0: + return 0; + case VatTypes::VAT10: + return $kopeks * 10 / 100; + case VatTypes::VAT110: + return $kopeks * 10 / 110; + case VatTypes::VAT18: + return $kopeks * 18 / 100; + case VatTypes::VAT118: + return $kopeks * 18 / 118; + case VatTypes::VAT20: + return $kopeks * 20 / 100; + case VatTypes::VAT120: + return $kopeks * 20 / 120; + } + } + + /** + * Возвращает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Устанавливает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106. + * Автоматически пересчитывает итоговый размер НДС от исходной суммы. + * + * @param string $type Тип ставки НДС + * @return $this + */ + public function setType(string $type) + { + $this->type = $type; + $this->setFinal(); + return $this; + } + + /** + * Возвращает расчитанный итоговый размер ставки НДС в рублях. Тег ФФД - 1200. + * + * @return float + */ + public function getFinalSum() + { + return self::toRub($this->sum_final); + } + + /** + * Устанавливает исходную сумму, от которой будет расчитываться итоговый размер НДС. + * Автоматически пересчитывает итоговый размер НДС от исходной суммы. + * + * @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС + * @return $this + */ + public function setSum(float $rubles) + { + $this->sum_original = self::toKop($rubles); + $this->setFinal(); + return $this; + } + + /** + * Возвращает исходную сумму, от которой расчитывается размер налога + * + * @return float + */ + public function getSum(): float + { + return self::toRub($this->sum_original); + } + + /** + * Прибавляет указанную сумму к общей исходной сумме. + * Автоматически пересчитывает итоговый размер НДС от новой исходной суммы. + * + * @param float $rubles + * @return $this + */ + public function addSum(float $rubles) + { + $this->sum_original += self::toKop($rubles); + $this->setFinal(); + return $this; + } + + /** + * Расчитывает и возвращает размер НДС от указанной суммы в рублях. + * Не изменяет итоговый размер НДС. + * + * @param float|null $rubles + * @return float + */ + public function calc(float $rubles): float + { + return self::toRub(self::calculator($this->type, self::toKop($rubles))); + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return [ + 'type' => $this->getType(), + 'sum' => $this->getFinalSum(), + ]; + } + + /** + * Расчитывает и устанавливает итоговый размер ставки от исходной суммы в копейках + */ + protected function setFinal() + { + $this->sum_final = self::calculator($this->type, $this->sum_original); + return $this; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Entities/VatArray.php b/src/AtolOnline/Entities/VatArray.php new file mode 100644 index 0000000..6badb1e --- /dev/null +++ b/src/AtolOnline/Entities/VatArray.php @@ -0,0 +1,111 @@ +set($vats); + } + } + + /** + * Устанавливает массив ставок НДС + * + * @param Vat[] $vats Массив ставок НДС + * @return $this + * @throws AtolTooManyVatsException Слишком много ставок НДС + */ + public function set(array $vats) + { + if ($this->validateCount($vats)) { + $this->vats = $vats; + } + return $this; + } + + /** + * Добавляет новую ставку НДС в массив + * + * @param Vat $vat Объект ставки НДС + * @return $this + * @throws AtolTooManyVatsException Слишком много ставок НДС + */ + public function add(Vat $vat) + { + if ($this->validateCount()) { + $this->vats[] = $vat; + } + return $this; + } + + /** + * Возвращает массив ставок НДС + * + * @return Vat[] + */ + public function get() + { + return $this->vats; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + $result = []; + foreach ($this->get() as $vat) { + $result[] = $vat->jsonSerialize(); + } + return $result; + } + + /** + * Проверяет количество элементов в массиве + * + * @param array|null $vats Если передать массив, то проверит количество его элементов. + * Иначе проверит количество уже присвоенных элементов. + * @return bool + * @throws AtolTooManyVatsException Слишком много ставок НДС + */ + protected function validateCount(array $vats = null) + { + if (($vats && is_array($vats) && count($vats) >= self::MAX_COUNT) || count($this->vats) == self::MAX_COUNT) { + throw new AtolTooManyVatsException(self::MAX_COUNT); + } + return true; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Exceptions/AtolCashierTooLongException.php b/src/AtolOnline/Exceptions/AtolCashierTooLongException.php new file mode 100644 index 0000000..5917713 --- /dev/null +++ b/src/AtolOnline/Exceptions/AtolCashierTooLongException.php @@ -0,0 +1,34 @@ +email; + } + + /** + * Устанавливает почту. Тег ФФД: 1008, 1117. + * + * @param string $email + * @return $this + * @throws AtolEmailTooLongException + * @throws AtolEmailValidateException + */ + public function setEmail(string $email) + { + $email = trim($email); + if (strlen($email) > 64) { + throw new AtolEmailTooLongException($email, 64); + } + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + throw new AtolEmailValidateException($email); + } + $this->email = $email; + return $this; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Traits/HasInn.php b/src/AtolOnline/Traits/HasInn.php new file mode 100644 index 0000000..933530e --- /dev/null +++ b/src/AtolOnline/Traits/HasInn.php @@ -0,0 +1,53 @@ +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; + } +} \ No newline at end of file diff --git a/src/AtolOnline/Traits/RublesKopeksConverter.php b/src/AtolOnline/Traits/RublesKopeksConverter.php new file mode 100644 index 0000000..95ecc78 --- /dev/null +++ b/src/AtolOnline/Traits/RublesKopeksConverter.php @@ -0,0 +1,40 @@ + копейки + * + * @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); + } +} diff --git a/tests/BasicTestCase.php b/tests/BasicTestCase.php new file mode 100644 index 0000000..a2adc2b --- /dev/null +++ b/tests/BasicTestCase.php @@ -0,0 +1,48 @@ +assertJson((string)$entity); + return $this; + } + + /** + * + */ + public function tearDown(): void + { + + //parent::tearDown(); + } +} \ No newline at end of file diff --git a/tests/Feature/ItemTest.php b/tests/Feature/ItemTest.php new file mode 100644 index 0000000..df0f52e --- /dev/null +++ b/tests/Feature/ItemTest.php @@ -0,0 +1,178 @@ +checkAtolEntity($item); + $this->assertEquals('Банан', $item->getName()); + $this->assertEquals(65.99, $item->getPrice()); + $this->assertEquals(2.74, $item->getQuantity()); + $this->assertEquals('кг', $item->getMeasurementUnit()); + $this->assertEquals(VatTypes::NONE, $item->getVat()->getType()); + $this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject()); + $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod()); + } + + /** + * Тестирует установку параметров через сеттеры + * + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + * @throws AtolOnline\Exceptions\AtolUserdataTooLongException + */ + public function testSetters() + { + $item = new Item(); + $item->setName('Банан'); + $item->setPrice(65.99); + $item->setQuantity(2.74); + $item->setMeasurementUnit('кг'); + $item->setVatType(VatTypes::NONE); + $item->setPaymentObject(PaymentObjects::COMMODITY); + $item->setPaymentMethod(PaymentMethods::FULL_PAYMENT); + $item->setUserData('Some user data'); + $this->checkAtolEntity($item); + $this->assertEquals('Банан', $item->getName()); + $this->assertEquals(65.99, $item->getPrice()); + $this->assertEquals(2.74, $item->getQuantity()); + $this->assertEquals('кг', $item->getMeasurementUnit()); + $this->assertEquals(VatTypes::NONE, $item->getVat()->getType()); + $this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject()); + $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod()); + $this->assertEquals('Some user data', $item->getUserData()); + } + + /** + * Тестирует установку ставки НДС разными путями + * + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testSetVat() + { + $item = new Item(); + $item->setVatType(VatTypes::NONE); + $this->assertEquals(VatTypes::NONE, $item->getVat()->getType()); + $item->setVatType(VatTypes::VAT20); + $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType()); + } + + /** + * Тестирует исключение о слишком длинном наименовании + * + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testAtolNameTooLongException() + { + $item = new Item(); + $this->expectException(AtolNameTooLongException::class); + $item->setName('Банан Банан Банан Банан Банан Банан Банан Банан Банан Банан Банан Банан'); + } + + /** + * Тестирует исключение о слишком высоком количестве + * + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testAtolQuantityTooHighException() + { + $item = new Item(); + $this->expectException(AtolQuantityTooHighException::class); + $item->setQuantity(100000.1); + } + + /** + * Тестирует исключение о слишком высокой цене + * + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testAtolPriceTooHighException() + { + $item = new Item(); + $this->expectException(AtolPriceTooHighException::class); + $item->setPrice(42949673.1); + } + + /** + * Тестирует исключение о слишком длинных польз. данных + * + * @throws AtolOnline\Exceptions\AtolUserdataTooLongException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testAtolUserdataTooLongException() + { + $item = new Item(); + $this->expectException(AtolUserdataTooLongException::class); + $item->setUserData('User data User data User data User data User data User data User data'); + } + + /** + * Тестирует исключение о слишком длинной единице измерения + * + * @throws AtolOnline\Exceptions\AtolNameTooLongException + * @throws AtolOnline\Exceptions\AtolPriceTooHighException + * @throws AtolOnline\Exceptions\AtolQuantityTooHighException + * @throws AtolOnline\Exceptions\AtolUnitTooLongException + */ + public function testAtolUnitTooLongException() + { + $item = new Item(); + $this->expectException(AtolUnitTooLongException::class); + $item->setMeasurementUnit('кг кг кг кг кг кг кг кг кг '); + } +} \ No newline at end of file diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php new file mode 100644 index 0000000..93dcedf --- /dev/null +++ b/tests/Unit/ClientTest.php @@ -0,0 +1,131 @@ +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 + * @throws \AtolOnline\Exceptions\AtolEmailTooLongException + * @throws \AtolOnline\Exceptions\AtolEmailValidateException + * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException + * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException + */ + public function testAtolNameTooLongException() + { + $customer = new Client(); + $this->expectException(AtolNameTooLongException::class); + $customer->setName('John Doe John Doe John Doe John Doe John Doe '. + 'John Doe John Doe John Doe John Doe John Doe John Doe John Doe John '. + 'Doe John Doe John Doe John Doe John DoeJohn Doe John Doe John Doe '. + 'John Doe John Doe John Doe John Doe John Doe John Doe John Doe John '. + 'Doe John Doe John Doe John Doe John Doe John Doe John Doe'); + } + + /** + * Тестирует исключение о слишком длинном телефоне + * + * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException + * @throws \AtolOnline\Exceptions\AtolNameTooLongException + * @throws \AtolOnline\Exceptions\AtolEmailTooLongException + * @throws \AtolOnline\Exceptions\AtolEmailValidateException + * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException + */ + public function testAtolPhoneTooLongException() + { + $customer = new Client(); + $this->expectException(AtolPhoneTooLongException::class); + $customer->setPhone('99999999999999999999999999999999999999999999999999999999999999999999999999'); + } + + /** + * Тестирует исключение о слишком длинной почте + * + * @throws \AtolOnline\Exceptions\AtolEmailTooLongException + * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException + * @throws \AtolOnline\Exceptions\AtolNameTooLongException + * @throws \AtolOnline\Exceptions\AtolEmailValidateException + * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException + */ + public function testAtolEmailTooLongException() + { + $customer = new Client(); + $this->expectException(AtolEmailTooLongException::class); + $customer->setEmail('johnjohnjohnjohnjohnjohndoedoedoedoe@exampleexampleexampleexample.com'); + } + + /** + * Тестирует исключение о некорректной почте + * + * @throws \AtolOnline\Exceptions\AtolEmailValidateException + * @throws \AtolOnline\Exceptions\AtolEmailTooLongException + * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException + * @throws \AtolOnline\Exceptions\AtolNameTooLongException + * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException + */ + public function testAtolEmailValidateException() + { + $customer = new Client(); + $this->expectException(AtolEmailValidateException::class); + $customer->setEmail('John Doe'); + } + + /** + * Тестирует исключение о некорректной длине ИНН + * + * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException + * @throws \AtolOnline\Exceptions\AtolEmailTooLongException + * @throws \AtolOnline\Exceptions\AtolEmailValidateException + * @throws \AtolOnline\Exceptions\AtolNameTooLongException + * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException + */ + public function testAtolInnWrongLengthException() + { + $company = new Client(); + $this->expectException(AtolInnWrongLengthException::class); + $company->setInn('123456789'); + $company->setInn('1234567890123'); + } +} \ No newline at end of file diff --git a/tests/Unit/CompanyTest.php b/tests/Unit/CompanyTest.php new file mode 100644 index 0000000..b59d373 --- /dev/null +++ b/tests/Unit/CompanyTest.php @@ -0,0 +1,110 @@ +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 + * @throws AtolOnline\Exceptions\AtolEmailTooLongException + * @throws AtolOnline\Exceptions\AtolEmailValidateException + * @throws AtolOnline\Exceptions\AtolPaymentAddressTooLongException + */ + public function testAtolInnWrongLengthException() + { + $company = new Company(); + $this->expectException(AtolInnWrongLengthException::class); + $company->setInn('321'); + $company->setInn('1234567890123'); + } + + /** + * Тестирует исключение о слишком длинном платёжном адресе + * + * @throws AtolOnline\Exceptions\AtolPaymentAddressTooLongException + * @throws AtolOnline\Exceptions\AtolEmailTooLongException + * @throws AtolOnline\Exceptions\AtolEmailValidateException + * @throws AtolOnline\Exceptions\AtolInnWrongLengthException + */ + public function testAtolPaymentAddressTooLongException() + { + $company = new Company(); + $this->expectException(AtolPaymentAddressTooLongException::class); + $company->setPaymentAddress('John Doe John Doe John Doe John Doe '. + 'John Doe John Doe John Doe John Doe John Doe John Doe John Doe John Doe John '. + 'Doe John Doe John Doe John Doe John DoeJohn Doe John Doe John Doe John Doe '. + 'John Doe John Doe John Doe John Doe John Doe John Doe John Doe John Doe John '. + 'Doe John Doe John Doe John Doe John Doe'); + } + + /** + * Тестирует исключение о слишком длинной почте + * + * @throws AtolOnline\Exceptions\AtolEmailTooLongException + * @throws AtolOnline\Exceptions\AtolEmailValidateException + * @throws AtolOnline\Exceptions\AtolInnWrongLengthException + * @throws AtolOnline\Exceptions\AtolPaymentAddressTooLongException + */ + public function testAtolEmailTooLongException() + { + $company = new Company(); + $this->expectException(AtolEmailTooLongException::class); + $company->setEmail('johnjohnjohnjohnjohnjohndoedoedoedoe@exampleexampleexampleexample.com'); + } + + /** + * Тестирует исключение о некорректной почте + * + * @throws AtolOnline\Exceptions\AtolEmailValidateException + * @throws AtolOnline\Exceptions\AtolEmailTooLongException + * @throws AtolOnline\Exceptions\AtolInnWrongLengthException + * @throws AtolOnline\Exceptions\AtolPaymentAddressTooLongException + */ + public function testAtolEmailValidateException() + { + $company = new Company(); + $this->expectException(AtolEmailValidateException::class); + $company->setEmail('John Doe'); + } +} \ No newline at end of file diff --git a/tests/Unit/VatTest.php b/tests/Unit/VatTest.php new file mode 100644 index 0000000..82f3261 --- /dev/null +++ b/tests/Unit/VatTest.php @@ -0,0 +1,51 @@ +assertEquals(0, $vat->getFinalSum(), 'Test '.$vat_type.' | 1 step'); + $vat->setSum($sum); + $this->assertEquals($expected_set, $vat->getFinalSum(), 'Test '.$vat_type.' | 2 step'); + $vat->addSum(20); + $this->assertEquals($expected_add, $vat->getFinalSum(), 'Test '.$vat_type.' | 3 step'); + $vat->addSum(-20); + } + + /** + * Провайдер данных для тестирования разных типов ставок НДС + * + * @return array + */ + public function vatProvider() + { + return [ + [VatTypes::NONE, 100, 0, 0], + [VatTypes::VAT0, 100, 0, 0], + [VatTypes::VAT10, 100, 10, 12], + [VatTypes::VAT18, 100, 18, 21.6], + ]; + } +} \ No newline at end of file