Compare commits

...

136 Commits

Author SHA1 Message Date
Anthony Axenov 0a03521a50
Исправлен бейджик build (ci)
https://github.com/badges/shields/issues/8671
2022-12-20 14:43:39 +08:00
Anthony Axenov 268134d47f
Merge pull request #27 from anthonyaxenov/dev
Миграция на php8.1
2022-12-15 11:17:20 +08:00
Anthony Axenov 6f4e756ec9
Убрал TODO из README, вместо этого просто завёл issues #7, #23, #24, #25, #26 2022-12-15 11:15:46 +08:00
Anthony Axenov cda2018aa6
Дополнение о версии в README 2022-12-15 11:02:32 +08:00
Anthony Axenov 2ab45e8c28
Мелочи по докам, TODO в README 2022-12-15 09:34:57 +08:00
Anthony Axenov c7baedae4f
Ссылки на пожертвования в README 2022-12-15 09:15:02 +08:00
Anthony Axenov c3e55f37ce
Ссылки на пожертвования в README 2022-12-15 00:30:40 +08:00
Anthony Axenov 95e3a9cac5
Подчищен composer.json
* обновлены версии основных зависимостей
* удалён myclabs/php-enum
* добавлен psalm с конфигом
* добавлен phpcs с конфигом
* добавлен php-cs-fixer с конфигом
* вероятно, будут ещё настроечные коммиты
2022-12-15 00:24:03 +08:00
Anthony Axenov 4157ab68f5
Миграция на php8.1
* enum-ы теперь enum-ы, а не говно -- теперь всё переведено на них, где это было возможно
* некоторые свойства классов объявлены в конструкторе
* некоторые классы перемещены в корневой неймспейс
* исправлен код-стайл, вычищен некоторый мусор, выправлены тесты... работы над этим продолжаются
2022-12-15 00:19:55 +08:00
Anthony Axenov 692ae43f9f
Ссылки на пожертвования 2022-12-14 23:52:39 +08:00
Anthony Axenov 45542b204a
Удалена поддержка php8.0, корректировки по README 2022-12-14 23:52:08 +08:00
Anthony Axenov b9eae25d2c
Мелочи по README 2022-08-13 21:59:01 +08:00
Anthony Axenov a72963a9ec
Обновлены зависимости 2022-08-13 21:53:03 +08:00
Anthony Axenov 6e31af95c5
Actual homepage url 2022-02-13 23:21:56 +08:00
Anthony Axenov 83a32873b3
Liberapay in funding 2022-01-30 23:01:18 +08:00
Anthony Axenov 9f4b3f2427
Liberapay shields in readme 2022-01-30 23:00:48 +08:00
Anthony Axenov aa390bf453
Merge pull request #19 from anthonyaxenov/dev
Актуализация README
2021-12-20 10:44:14 +08:00
Anthony Axenov d8f13e3054 Merge branch 'master' into dev 2021-12-20 10:43:07 +08:00
Anthony Axenov 080c7e7247
Исправлен README 2021-12-20 10:41:46 +08:00
Anthony Axenov 4bc92ac0ad
Merge pull request #17 from anthonyaxenov/dev
#16 убраны лишние проверки в тестах фискализатора
2021-12-20 00:45:24 +08:00
Anthony Axenov 9fa55e7c5f #16 убраны лишние проверки в тестах фискализатора 2021-12-19 23:07:03 +08:00
Anthony Axenov baf97cad20
Merge pull request #15 from anthonyaxenov/dev
v1.0.0
2021-12-19 23:00:14 +08:00
Anthony Axenov 2e8099e0a4 Актуализирован readme 2021-12-19 22:58:47 +08:00
Anthony Axenov d7f3c81fac Мелкофикс gh-actions 2021-12-19 22:52:21 +08:00
Anthony Axenov e22c1cb091 Финализация оставшихся классов 2021-12-19 22:50:52 +08:00
Anthony Axenov 58bc344a86 Более или менее актуальная документация 2021-12-19 22:30:21 +08:00
Anthony Axenov fdc5ab112a Переименования классов для пущей простоты 2021-12-19 22:29:53 +08:00
Anthony Axenov 71d1f2900c Большие доработки по фискилизации
- у `AtolClient` теперь возможно получить последний отправленный запрос `getLastRequest()`
- у `AtolClient::auth()` удалены аргументы за ненадобностью
- улучшен `Client::jsonSerialize()`
- исправлен `Receipt::jsonSerialize()`
- у `Receipt` и `Correction` появились методы фискализации, вкусный сахарок
- удалён енам `DocumentTypes` за ненадобностью
- исправлены тесты монитора и документов
- рабочий фискализатор с получением результатов и покрытием
2021-12-18 14:45:00 +08:00
Anthony Axenov b4cc0fec53 Мелочи по конфигу composer 2021-12-18 14:09:07 +08:00
Anthony Axenov 573af15bac Фиксы геттеров логина и пароля для `AtolClient` 2021-12-16 18:35:49 +08:00
Anthony Axenov 19653776c5 Фикс оплат и ставок в данных коррекции 2021-12-16 18:33:13 +08:00
Anthony Axenov c7d07a18f1 Мелкофиксы по кодстайлу 2021-12-12 14:50:29 +08:00
Anthony Axenov 464a8f0706 Более корректный порядок тестов 2021-12-12 14:36:04 +08:00
Anthony Axenov 294a3ef2f3 Допокрытие `ArrayAccess`-методов 2021-12-12 11:09:12 +08:00
Anthony Axenov b4af189292 Обновление зависимостей 2021-12-11 15:54:42 +08:00
Anthony Axenov 6787ce3ad7 Класс документа коррекции `Correction` с покрытием и всякая мелочёвка
- финализация Receipt + Payment
- фиксы phpdoc
2021-12-11 15:53:57 +08:00
Anthony Axenov 1d6abfd475 Более симпатичные бейджики в README 2021-12-09 20:14:35 +08:00
Anthony Axenov 058ce5ed3d Доработки коллекций, чека и тестов
- `EntityCollection` сильно упрощён, добавлен выброс исключений при пустом содержимом
- `Receipt::setItems(), setPayments() и setVats()` получили одинаковые проверки входящих данных
- округление в `Vat::setSum()`
- доработаны тесты коллекций
2021-12-09 20:14:35 +08:00
Anthony Axenov 16d1146826 Четвёртая итерация `Receipt`
- 100% покрытие
- элвисы в разных сеттерах
2021-12-08 19:04:14 +08:00
Anthony Axenov 703c5178f5 Минорные апдейты зависимостей 2021-12-08 16:11:39 +08:00
Anthony Axenov fdc64954f9 Третья итерация `Receipt`
- просчёт ставок секи в `setVats()`
- просчёт суммы чека в `setItems()`
- геттеры `getItems()` и `getVats()` возвращают пустую коллекцию, если в чеке они отсутствуют
- фикс `vats => supplier_info` в `jsonSerialize()`
- тесты поставщика, ставок, расчёта ставок и суммы чека
2021-12-08 16:01:25 +08:00
Anthony Axenov 793549aaac `Entity` теперь имплементирует `Arrayable` и `ArrayAccess` для совместимости с иммутабельными методами коллекций 2021-12-08 15:56:07 +08:00
Anthony Axenov b57acf8b05 `EntityCollection` помечен для переделки 2021-12-08 15:50:06 +08:00
Anthony Axenov b39e76f312 Вторая итерация `Receipt`
- фикс nullable-свойств и геттеров
- проверка на пустоту в `setPayments()`, `setItems()` и `setVats()`
- часть тестов с покрытием (конструктор, агент, исключения при пустых коллекциях)
2021-12-07 20:09:12 +08:00
Anthony Axenov a34a6927d1 Небольшой рефакторинг по тестам
- `BasicTestCase::assertAtolable() => assertIsAtolable()`
- генерация тестовых объектов `Vat`, `Payment` и `Item` вынесены в `BasicTestCase`
2021-12-07 20:04:03 +08:00
Anthony Axenov 1f3d5d2f3d Первая итерация `Receipt` 2021-12-06 20:07:17 +08:00
Anthony Axenov 359264db64 Новая сущность `AdditionalUserProps` с покрытием для будущей поддержки в документе 2021-12-06 19:32:50 +08:00
Anthony Axenov 557c76fefa Доработка коллекций и не только
- коллекция `Items` с покрытием
- вынос коллекций из `AtolOnline\Entities` в `AtolOnline\Collections`
- фикс ] в `AtolException`
- финализирован `CorrectionInfo`
- фиксы по тестам коллекций
- прочие мелочи по phpdoc
2021-12-06 16:14:19 +08:00
Anthony Axenov bf09641c8b Класс коллекции оплат `Payments` 2021-12-06 14:17:05 +08:00
Anthony Axenov 6b5a025051 Класс коллекции ставок НДС `Vats` 2021-12-06 14:16:50 +08:00
Anthony Axenov 3d3eba5b4e Базовый класс коллекции объектов `EntityCollection` 2021-12-06 14:16:34 +08:00
Anthony Axenov a5c88cd7d3 Функции проверки наследования классов вернул из `Helpers` в `BasicTestCase` и отрефакторил
- `isSameClass()`
- `checkImplementsInterfaces()`
- `checkUsesTraits()` + переписана под наследование
2021-12-06 14:15:47 +08:00
Anthony Axenov b451c7dc68 Мелочи по phpdoc 2021-12-06 14:13:48 +08:00
Anthony Axenov 7d0c526663 Готов класс оплаты `Payment` для будущей поддержки оплат в документе 2021-12-03 23:53:42 +08:00
Anthony Axenov 65ec639014 Округление цены и количества в сетьерах `Item` до 2 и 3 зн после запятой соответственно
Также переименованы исключения о слишком высоких цене и сумме предмета расчёта, чтобы избежать конфликтов с другими
2021-12-03 23:52:41 +08:00
Anthony Axenov d533164d1b Поддержка `correction_info` 2021-12-03 20:09:14 +08:00
Anthony Axenov 05fd25e810 Фикс `AgentInfoTest` 2021-12-03 19:06:15 +08:00
Anthony Axenov 3e03fdca61 Финализация всех `Entities` 2021-12-03 18:24:21 +08:00
Anthony Axenov c077f98cf9 `Item` - пересчёт НДС при изменении цены, количества и акциза 2021-12-03 18:24:00 +08:00
Anthony Axenov 2260233e3f Общие сеттеры-геттеры сущностей вынесены в трейты `HasEmail`, `HasInn`, `HasPhones`
Кодстайл и микрорефакторинг сущностей
2021-12-03 18:23:00 +08:00
Anthony Axenov c30c7d069f Непереработанные классы переименованы для наглядности 2021-12-03 18:16:28 +08:00
Anthony Axenov 8f235d4730 Удалён ROADMAP, поправлен README 2021-12-03 18:15:16 +08:00
Anthony Axenov 85750cd211 Обновление зависимостей
Также подключен jetbrains/phpstorm-attributes, чтобы всё по красоте
2021-12-03 18:10:06 +08:00
Anthony Axenov 2a66889e46 Поддержка `nomenclature_code` у предмета расчёта + мелкофиксы
- теперь `getSum()` проверяет по `Constraints::MAX_COUNT_ITEM_SUM` вместо `MAX_COUNT_ITEM_PRICE` (как и должен был изначально)
- подправил `TooLongException`
- всякие phpdoc-и
2021-12-03 11:52:40 +08:00
Anthony Axenov 1c0d8ba64d Корректировка readme 2021-12-02 16:14:01 +08:00
Anthony Axenov fc37580078
Merge pull request #10 from anthonyaxenov/gha
Настройка gh-actions для работы с codecov.io
2021-12-02 15:57:20 +08:00
Anthony Axenov 96137d20b2 Настройка gh-actions для работы с codecov.io 2021-12-02 15:56:32 +08:00
Anthony Axenov 267431ec28 Мелочи в readme и phpunit.xml (удалены дефолтные параметры) 2021-12-02 01:10:54 +08:00
Anthony Axenov cb24bb1fb0 Доработки енамов и тегов ФФД 2021-12-02 01:10:16 +08:00
Anthony Axenov 11646113b6 Доработки `Item`
- поддержка `excise`, покрыта тестами
- фикс `setVat()`
- улучшен `jsonSerialize()`
2021-12-02 01:09:25 +08:00
Anthony Axenov 650b46923e Бейджики в readme 2021-12-01 20:12:11 +08:00
Anthony Axenov 3f3eb68ea2 Корректировки в composer.json 2021-12-01 20:11:57 +08:00
Anthony Axenov 5ccb0e9db4 Кучад доработок, главным образом вокруг `Item`
- `Item` почти готов и весь покрыт тестами. Пока остались нереализованными `nomenclature_code` и `excise`
- `Client::setPhone()` теперь выбрасывает InvalidPhoneException
- доработка и создание новых исключений (не буду все перечислять, смотри диффы)
- мелочи по phpdoc и всяким текстовкам
2021-12-01 20:11:08 +08:00
Anthony Axenov bce21f9658 Правки по документации
Актуализирована часть про `Vat`, пересобрал README, мелочи
2021-11-28 12:01:28 +08:00
Anthony Axenov a7205ff754 Переработан `Vat`, покрыт тестами 2021-11-28 00:58:05 +08:00
Anthony Axenov e0ff5a261a Новые теги в `Ffd105Tags` + мелочи 2021-11-28 00:57:19 +08:00
Anthony Axenov 2ebb172f2e Мелочи по конвертации денег 2021-11-28 00:55:34 +08:00
Anthony Axenov e2141551d5 `Enum` стал абстрактным внутри `Enums`, наследникам созданы `getFfdTags()` 2021-11-28 00:55:28 +08:00
Anthony Axenov c9670a1321 Удалены бесполезные `ItemArray`, `VatArray` и `PaymentArray`, будут заменены коллекциями 2021-11-28 00:49:53 +08:00
Anthony Axenov 9d2617858d `AgentInfo`: перенос валидации типа агента из конструктора в сеттер 2021-11-28 00:44:43 +08:00
Anthony Axenov f548032843 Ссылки на телеграм и патреон 2021-11-27 18:53:23 +08:00
Anthony Axenov e0d792d3a4 Реализован и покрыт тестами `AgentInfo` (переименован из `Agent`) 2021-11-27 17:59:50 +08:00
Anthony Axenov 16c8d8a676 Реализован и покрыт тестами `MoneyTransferOperator` 2021-11-26 09:16:46 +08:00
Anthony Axenov 8b79b2be51 Мелкофиксы
- `Ffd105Tags::CLIENT_CONTACTS` => `CLIENT_PHONE_EMAIL`
+ мелочи по `Client`, `ClientTest` и `PayingAgentTest`
2021-11-24 18:55:53 +08:00
Anthony Axenov 95dbc3a5b7 Класс `Supplier` обзавёлся `name` + `inn` и допокрыт тестами 2021-11-24 18:54:48 +08:00
Anthony Axenov 790831e933 `MoneyTransferOperator` => `ReceivePaymentsOperator` 2021-11-24 18:45:01 +08:00
Anthony Axenov 3b75c8b983 Класс `Supplier`, покрытый тестами
Это было слишком легко :(
2021-11-24 17:57:24 +08:00
Anthony Axenov c5b57ec26d Класс `MoneyTransferOperator`, покрытый тестами
Также мелкофиксы по phpdoc `PayingAgent` и его тестам
2021-11-24 17:54:04 +08:00
Anthony Axenov 42d194116f Туча доработок
- класс `PayingAgent`, покрытый тестами
- новые константы для тегов ФФД 1.05 `Ffd105Tags`
- `Entity::jsonSerialize()` object -> array (again)
- `TooManyException::$max` int -> float
- тесты по psr-4, потому что почему бы и нет
- некоторые провайдеры вынесены в `BasicTestCase`
- улучшен тест покупателя
2021-11-24 01:30:54 +08:00
Anthony Axenov b5a01debd2 Новый енам AgentTypes 2021-11-22 14:53:10 +08:00
Anthony Axenov 9d3344a0df Мелкофиксы в конструкторе KktMonitor 2021-11-22 14:52:27 +08:00
Anthony Axenov 00b2643f42 Новые функции в Helpers для проверки классов 2021-11-22 14:52:05 +08:00
Anthony Axenov 3bf8667437 Рефакторинг исключений, новые константы ограничений
Описывать все слишком долго, TLDR:
- упрощены корневые AtolException, {BasicTooLongException => TooLongException}, {BasicTooManyException => TooManyException}
- InvalidSnoException заменён на InvalidEnumValueException
- добавлены новые константы, общий порядок изменён в соответствии с порядком упоминания в документации, ссылки на которую тоже добавлены с указанием страниц

Помимо этого, в enum-ах теперь должен быть предусмотрен метод getFfdTags()
2021-11-22 14:51:10 +08:00
Anthony Axenov e1303f4fdb Правки по документации
- в мониторинг добавлено о приведении к строке
- актуализированы Client и Company
- мелочи в соотв. классах
2021-11-21 19:06:55 +08:00
Anthony Axenov 920c08c610 Правки по документации
- добавлено про мониторинг
- убрано про `base_name` из коррекции
2021-11-21 18:58:03 +08:00
Anthony Axenov d281071970 Удалён метод `AtolOnline\Entities\Kkt::getNetworkError()` за ненадобностью 2021-11-21 18:57:20 +08:00
Anthony Axenov 95499174b0 Warning in readme 2021-11-21 00:33:43 +08:00
Anthony Axenov 6d0cac2cbc Первая итерация рефаторинга фискализатора 2021-11-21 00:19:35 +08:00
Anthony Axenov 6620acf1bf Мелкофикс теста KktMonitor::GetOne() 2021-11-21 00:17:40 +08:00
Anthony Axenov e89369348a Метод KktMonitor::auth() перенесён в AtolClient для совместимости в фискализатором 2021-11-21 00:15:59 +08:00
Anthony Axenov b35b9bfa87 Удалена поддержка base_name в документе коррекции (#5) 2021-11-20 23:46:39 +08:00
Anthony Axenov e1120051c1 Новая сущность Kkt для ответов мониторинга (#8) + мелкий рефакторинг
- enum-константы перенесены в своё пространство Enums
- новые исключения EmptyMonitorDataException + NotEnoughMonitorDataException
- KktMonitor::getAll() теперь возвращает коллекцию объектов Kkt
- KktMonitor::getOne() теперь возвращает объект Kkt
- местами актуализированы return types + phpdoc

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

7
.github/FUNDING.yml vendored
View File

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

48
.github/workflows/ci.yml vendored 100644
View File

@ -0,0 +1,48 @@
# https://github.com/shivammathur/setup-php/blob/master/README.md
name: CI
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ dev ]
jobs:
Tests:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- 8.1
steps:
- uses: actions/checkout@v2
- name: Cache Composer dependencies
uses: actions/cache@v2
with:
path: ~/.composer/cache
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
restore-keys: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
- name: Setup PHP v ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: json, curl, tokenizer, mbstring, xdebug
coverage: xdebug
tools: composer, phpunit
- name: Install dependencies with composer
run: composer install --no-ansi --no-interaction --no-progress
- name: Run tests with phpunit/phpunit
run: vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml
- name: Send code coverage report to Codecov.io
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: bash <(curl -s https://codecov.io/bash) || true

View File

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

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
/config.php
*cache*
/test.php
/.coverage-report

25
.php-cs-fixer.php 100644
View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->exclude('docs')
->notPath('test.php');
return (new PhpCsFixer\Config())
->setFinder($finder)
->setIndent(' ')
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'group_to_single_imports' => false,
]);

138
README.md
View File

@ -1,116 +1,78 @@
# АТОЛ Онлайн
![Build](https://github.com/anthonyaxenov/atol-online/workflows/Build/badge.svg?branch=master)
Библиотека для фискализации чеков по 54-ФЗ через [облачные ККТ АТОЛ](https://online.atol.ru/).
Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/).
Текущая поддерживаемая версия API: **5.1**
[![GitHub Workflow Status (master)](https://img.shields.io/github/actions/workflow/status/anthonyaxenov/atol-online/ci.yml?branch=master&logo=github)](https://github.com/anthonyaxenov/atol-online/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/anthonyaxenov/atol-online/branch/master/graph/badge.svg?token=WR2IV7FTF0)](https://codecov.io/gh/anthonyaxenov/atol-online)
[![Stable Version](https://img.shields.io/packagist/v/axenov/atol-online?label=stable)](https://packagist.org/packages/axenov/atol-online)
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/axenov/atol-online?color=%23787cb4)](https://packagist.org/packages/axenov/atol-online)
[![License](https://img.shields.io/packagist/l/axenov/atol-online?color=%23369883)](LICENSE)
[![buymeacoffee](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/axenov)
**[Документация](/docs/readme.md)**
Текущие поддерживаемые версии АТОЛ Онлайн:
| Протокол | API | ФФД | Статус |
|----------|------|------|----------------|
| v4 | 5.10 | 1.05 | Поддерживается |
| v5 | 3.0 | 1.2 | В планах |
Поддерживаемые возможности:
* Мониторинг ККТ и ФН
* Фискализация документов на облачной ККТ
* Валидация данных до отправки документа на ККТ (насколько это возможно, согласно схеме)
* Расчёты денег в копейках
* PSR-4 автозагрузка, покрытие настоящими тестами, fluent-setters, докблоки
## Системные требования
* PHP 7.2+
* composer
* php-json
* `php v8.1` и выше
* `php-json`
* `php-mbstring`
* [composer](https://getcomposer.org/)
> Для использования на php8.0 используйте версии библиотеки до 1.0.2 включительно.
## Начало работы
### Подключение библиотеки
1. Подключить пакет к вашему проекту:
1. Подключить пакет к проекту:
```bash
composer require axenov/atol-online
```
2. В нужном месте проекта объявить параметры, используя константы, и подключить **composer**, если это не сделано ранее:
2. В нужном месте проекта подключить автозагрузчик composer-зависимостей, если это не сделано ранее:
```php
require($project_root.'/vendor/autoload.php');
require($project_root . '/vendor/autoload.php');
```
где `$project_root` — абсолютный путь к корневой директории вашего проекта.
> При использовании фреймворков это обычно не требуется.
### Использование библиотеки
Вы имеете право использовать и распространять код на условиях **[лицензии MIT](LICENSE)**.
Дополнительная информация может быть найдена здесь:
1. [Документации к библиотеке](/docs)
2. [Документация АТОЛ Онлайн](https://online.atol.ru/lib/)
3. [Исходный код](/src), докблоки
4. [Тесты](/tests/AtolOnline/Tests)
### Тестирование кода библиотеки
Файлы тестов находятся в директории `/tests` корня репозитория.
Для запуска тестов необходимо перейти в корень вашего проекта и выполнить команду:
Для запуска тестов необходимо перейти в корень репозитория и выполнить одну из команд:
```bash
./vendor/bin/phpunit
composer psalm # статический анализ
composer phpcs # синтаксический анализ
composer test # полное тестирование без покрытия
composer coverage # полное тестирование с покрытием
```
## Настройка ККТ
Для работы с облачной ККТ необходимы следующие параметры:
* логин;
* пароль;
* код группы.
Чтоб получить их, нужно:
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)**.
После тестирования с покрытием в корне репозитория создаётся отчёт, который сохраняется в директории `.coverage`.
Для тестирования с покрытием необходим `php-xdebug` с параметром `xdebug.mode = coverage,...`.

View File

@ -1,67 +0,0 @@
# 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`

View File

@ -1,51 +1,84 @@
{
"name": "axenov/atol-online",
"description": "Library to use cloud cash register in e-commerce according to Russian Federal Law #54",
"description": "Библиотека для работы с API АТОЛ Онлайн (облачные ККТ для приёма платежей по 54-ФЗ)",
"license": "MIT",
"type": "library",
"keywords": [
"54-fz",
"kkt",
"e-commerce",
"cash",
"cash register",
"payment",
"payment system",
"api",
"чек",
"atol",
"atol-online"
"атол",
"payment",
"e-commerce",
"atol online",
"атол онлайн",
"atol-online",
"атол-онлайн",
"фискализация",
"fiscalization",
"payment system"
],
"homepage": "https://github.com/anthonyaxenov/atol-online",
"readme": "https://github.com/anthonyaxenov/atol-online/blob/master/README.md",
"authors": [
{
"name": "Anthony Axenov",
"homepage": "http://anthonyaxenov.ru",
"homepage": "https://axenov.dev",
"email": "anthonyaxenov@gmail.com"
}
],
"support": {
"rss": "https://github.com/anthonyaxenov/atol-online/discussions/categories/announcements",
"chat": "https://github.com/anthonyaxenov/atol-online/discussions",
"source": "https://github.com/anthonyaxenov/atol-online",
"issues": "https://github.com/anthonyaxenov/atol-online/issues",
"chat": "https://discord.gg/mFYTQmp"
"docs": "https://github.com/anthonyaxenov/atol-online/blob/master/docs/readme.md"
},
"funding": [
{
"type": "Yoomoney",
"url": "https://yoomoney.ru/to/41001685237530"
},
{
"type": "Buy Me a Coffee",
"url": "https://www.buymeacoffee.com/axenov"
}
],
"require": {
"php": ">=7.2",
"php": ">=8.1",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.5",
"psr/log": "^1.1",
"ramsey/uuid": "^3.9"
"ext-mbstring": "*",
"guzzlehttp/guzzle": "^7.5",
"psr/log": "^3",
"ramsey/uuid": "^4.6",
"illuminate/collections": "^v9.43",
"jetbrains/phpstorm-attributes": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
"friendsofphp/php-cs-fixer": "^3.13",
"phpunit/phpunit": "^9.5",
"psalm/plugin-phpunit": "^0.18",
"squizlabs/php_codesniffer": "^3.7",
"vimeo/psalm": "^4.30"
},
"autoload": {
"psr-4": {
"AtolOnline\\": "src/"
}
},
"autoload-dev": {
"classmap": [
"src/AtolOnline/Api/",
"src/AtolOnline/Api/Schemas/",
"src/AtolOnline/Exceptions/",
"src/AtolOnline/Entities/",
"src/AtolOnline/Traits/",
"src/AtolOnline/Constants/",
"tests/"
]
},
"scripts": {
"phpcs": "vendor/bin/phpcs",
"psalm": "vendor/bin/psalm",
"test": "vendor/bin/phpunit --colors=always",
"coverage": "php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html .coverage"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
}
}

5153
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +0,0 @@
# Работа с клиентами (покупателями)
[Вернуться к содержанию](readme.md)
---
Объект покупателя инициализируется следующим образом:
```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();
```
---
[Вернуться к содержанию](readme.md)

19
docs/collection.md 100644
View File

@ -0,0 +1,19 @@
# Коллекция сущностей
[Вернуться к содержанию](readme.md#toc)
---
Коллекциями являются объекты, способные хранить в себе [сущности](entity.md).
Они унаследованы от `Illuminate/Support/Collection` и полностью поддерживают все
[стандартные методы](https://laravel.com/docs/master/collections) коллекций Laravel.
Помимо этого, они валидируют количество и вид сущностей, которые могут хранить в себе, согласно схеме API АТОЛ Онлайн.
Коллекции ведут себя аналогично самим сущностям в части приведения к массивам и json-ификации.
---
Читай также: [Сущность](entity.md)
[Вернуться к содержанию](readme.md#toc)

View File

@ -1,80 +0,0 @@
# Работа с компанией (продавцом)
[Вернуться к содержанию](readme.md)
---
Объект компании инициализируется следующим образом:
```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();
```
---
[Вернуться к содержанию](readme.md)

View File

@ -1,66 +0,0 @@
# Работа с данными коррекции
[Вернуться к содержанию](readme.md)
---
Объект для данных коррекции инициализируется следующим образом:
```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();
```
---
[Вернуться к содержанию](readme.md)

View File

@ -1,167 +0,0 @@
# Работа с документами
[Вернуться к содержанию](readme.md)
---
Объект документа инициализируется следующим образом:
```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()`.
Всё в рублях.
<a name='correction'></a>
## Работа с данными коррекции
Если документ создаётся с целью коррекции прихода или расхода, то он обязательно должен содержать [данные коррекции](/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();
```
---
[Вернуться к содержанию](readme.md)

52
docs/entity.md 100644
View File

@ -0,0 +1,52 @@
# Сущность
[Вернуться к содержанию](readme.md#toc)
---
Сущностями являются все классы, которые необходимы для взаимодействия с API.
Они находятся в директори `src/Entities` и расширяют абстрактный класс `AtolOnline\Entities\Entity`.
Каждая сущность содержит в себе только те данные, которые необходимы согласно схемы API АТОЛ Онлайн.
Ниже перечислены возможности сущностей.
## Приведение к строке JSON
```php
echo $entity;
$json_string = (string)$entity;
```
## Приведение к массиву
```php
// результат идентичен
$json_array1 = $entity->jsonSerialize();
$json_array2 = $entity->toArray();
```
## Чтение из массива
```php
$var = new \AtolOnline\Entities\Client('Иванов Иван');
echo $var['name']; // 'Иванов Иван'
$var['name'] = 'Петров Пётр'; // BadMethodCallException
```
## Fluent-сеттеры
Реализованы на уровне конкретных классов сущностей, но у некоторых могут полностью отсуствовать.
```php
$entity->setFoo($value)->setBar('bar')->...
```
Сеттеры валидируют и фильтруют данные согласно схеме АТОЛ Онлайн API, а в случае ошибочных значений -- выбрасывают
исключения.
---
Читай также: [Коллекция сущностей](collection.md)
[Вернуться к содержанию](readme.md#toc)

237
docs/fiscalizing.md 100644
View File

@ -0,0 +1,237 @@
# Фискализация документов
[Вернуться к содержанию](readme.md#toc)
---
## Доступ к ККТ
Для работы с облачной ККТ необходимы следующие параметры:
* логин;
* пароль;
* код группы.
Чтоы получить их, нужно:
1. авторизоваться в личном кабинете [online.atol.ru](https://online.atol.ru/lk/Account/Login);
2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**.
Скачается XML-файл с нужными настройками.
Также для работы потребуются:
* ИНН продавца;
* URL места расчёта (ссылка на ваш интернет-сервис).
## Использование
Объект ККТ инициализируется следующим образом:
```php
$kkt = new AtolOnline\Api\Fiscalizer();
```
Установить параметры подключения можно двумя путями:
```php
use AtolOnline\Api\Fiscalizer;
// 1 способ - через конструктор
$kkt = new Fiscalizer(group: 'mygroup', login: 'mylogin', password: 'mypassword');
// 2 способ - через сеттеры
$kkt = (new Fiscalizer())
->setLogin($login)
->setGroup($group)
->setPassword($password);
```
<a name="testmode"></a>
## Тестовый режим
По умолчанию фискализатор создаётся для работы в тестовом режиме. Это означает, что работа с АТОЛ Онлайн API будет
происходить [в тестовой среде](https://online.atol.ru/files/ffd/test_sreda.txt).
> Под тестовым режимом работы подразумевается использование тестовых ККТ, которые принадлежат компании АТОЛ.
Управление тестовым режимом происходит следующим образом:
```php
$kkt = new Fiscalizer(); // включен по умолчанию
$kkt = new Fiscalizer(false); // выключен явно
$kkt->setTestMode(); // включен явно
$kkt->setTestMode(true); // включен явно
$kkt->setTestMode(false); // выключен явно
```
**При включенном тестовом режиме используются тестовые ККТ**, т.к. перед отправкой запроса подменяются:
* логин;
* пароль;
* группа ККТ;
* ИНН клиента (покупателя);
* ИНН и адрес места расчётов компании (продавца).
Таким образом:
* использовать тестовый режим -- безопасно;
* при переключении тестового режима устанавливать заново свои параметры подключения не требуется.
**При выключенном тестовом режиме используются ваши ККТ.**
Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом
режиме (на собственной ККТ). В этом случае важно понимать следующее:
1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**;
2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html);
3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах;
4. вся ответственность за пп. 1-3 и последствия ложится только на вас.
## Авторизация на ККТ
Перед первым запросом на ККТ происходит аутентификация на сервере по логину и паролю. В ответ приходит авторизационный
токен, срок жизни коего равен **24 часам**. После первой успешной операции возможно получить этот токен следующим
образом:
```php
$kkt->getToken(); // вернёт строку длиной 128 символа
```
Этот токен можно сохранить и переиспользовать в течение всего срока его жизни, но далее следует получить новый токен.
Ранее полученный токен следует указывать до отправки запросов следующим образом:
```php
$kkt->setToken($token_string);
```
Если токен был установлен перед выполнением операции, то при выполнении операции будет использоваться именно он. Если
операция завершится ошибочно из-за истёкшего токена, следует повторить операцию без использования метода `setToken()`,
либо обнулив его следующим образом:
```php
$kkt->setToken(null);
```
Тогда будет получен новый токен.
## Регистрация документа
Для регистрации документа **прихода** необходимо вызвать метод `sell()`:
```php
$result = $kkt->sell($document);
$result2 = $receipt->sell($kkt);
```
Для регистрации документа **возврата прихода** необходимо вызвать метод `sellRefund()`:
```php
$result = $kkt->sellRefund($document);
$result2 = $receipt->sellRefund($kkt);
```
Для регистрации документа **расхода** необходимо вызвать метод `buy()`:
```php
$result = $kkt->buy($document);
$result2 = $receipt->buy($kkt);
```
Для регистрации документа **возврата расхода** необходимо вызвать метод `buyRefund()`:
```php
$result = $kkt->buyRefund($document);
$result2 = $receipt->buyRefund($kkt);
```
Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellCorrection()`:
```php
$result = $kkt->sellCorrect($document);
$result2 = $correction->sellCorrect($kkt);
```
Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyCorrection()`:
```php
$result = $kkt->buyCorrect($document);
$result2 = $correction->buyCorrect($kkt);
```
### Собственный идентификатор документа (`external_id`)
Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки. По умолчанию это UUID версии 4.
Чтобы использовать собственный идентификатор, следует передать нужное строковое значение вторым параметром в любой из
шести описанных выше методов, например:
```php
$result = $kkt->sellRefund($document, 'order_' . $order->id);
```
Если `external_id` не указан явно или имеет пустое значение, то будет сгенерирован новый UUID. Узнать его можно будет
только в ответе от ККТ после регистрации документа в очереди на фискализацию.
### Передача `callback_url`
Перед регистрацией документа можно указать `callback_url`. АТОЛ отправит на указанный URL результат регистрации. По
этому адресу должен располагаться ваш собственный обработчик статуса фискализации.
```php
$kkt->setCallbackUrl('http://example.com/process-kkt-result');
$kkt->getCallbackUrl();
```
## Проверка статуса документа
Если перед отправкой документа на регистрацию был задан `callback_url` через метод `setCallbackUrl()`, то ответ придёт
на указанный адрес автоматически, как только документ обработается на стороне ККТ. Ответ может быть как об успешной
регистрации, так и ошибочной.
В любом случае, вам доступны два метода, с помощью которых вы можете проверять статус документа самостоятельно:
```php
$kkt->getDocumentStatus(); // делает единичный запрос
$kkt->pollDocumentStatus(); // делает запросы до получения конечного статуса (не-wait)
```
Эти методы принимают на вход `uuid` кода регистрации. Этот UUID нужно взять из ответа, полученного при отправке
документа на регистрацию:
```php
$sell_result = $kkt->sell($document);
$status = $kkt->pollDocumentStatus($sell_result->uuid);
```
Метод `pollDocumentStatus()` многократно опрашивает ККТ на предмет состояния документа. Метод может принимать до трёх
параметров:
* uuid;
* количество попыток (по умолчанию — 5);
* время между попытками в секундах (по умолчанию — 1).
```php
// Проверять статус 10 раз на протяжении 20 секунд — каждые две секунды
$kkt->pollDocumentStatus($sell_result->uuid, 10, 20);
```
Учитывайте, что метод вернёт результат как только сменится статус регистрации на успешный `done` или ошибочный `error`.
Использовать его лучше сразу после отправки документа на регистрацию (как в примере выше).
> Как правило, фискализация одного документа занимает 4-6 секунд с учётом регистрации.
Метод `getDocumentStatus()` принимает на вход только `uuid` и запрашивает состояние документа лишь единожды.
Использовать его целесообразнее в те моменты, когда нет необходимости знать успех регистрации сразу после отправки
документа.
> Обратите внимание, что АТОЛ позволяет получать статус документа в течение 32 суток с момента его регистрации.
---
Читай также: [Обработка ответа API](response.md)
[Вернуться к содержанию](readme.md#toc)

View File

@ -1,202 +0,0 @@
# Работа с предметами расчёта
[Вернуться к содержанию](readme.md)
---
## Один объект
Объект предмета расчёта инициализируется следующим образом:
```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();
```
<a name="array"></a>
## Массив объектов предметов расчёта
> Максимальное количество объектов в массиве - 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();
```
---
[Вернуться к содержанию](readme.md)

View File

@ -1,281 +0,0 @@
# Работа с ККТ
[Вернуться к содержанию](readme.md)
---
Объект ККТ инициализируется следующим образом:
```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) уже указаны в коде библиотеки.
*Под тестовым режимом работы подразумевается использование другой (тестовой) ККТ.*
При включенном тестовом режиме:
* меняется логин, пароль и группа (для обращения на тестовую ККТ)
* между авторизацией и операцией над документом, в `Company` документа переопределяется ИНН, СНО и адрес места
расчётов на те, что указаны в [параметрах тестовой среды](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 и последствия ложится только на вас.
## Авторизация ККТ
Перед первым запросом на ККТ происходит авторизация на сервере по логину и паролю.
В ответ приходит авторизационный токен, срок жизни коего равен 24 часам.
После первой успешной операции возможно получить этот токен следующим образом:
```php
$kkt->getAuthToken(); // вернёт строку длиной 128 символа
```
Этот токен можно сохранить и переиспользовать в течение всего срока его жизни.
Спустя это время следует получить новый токен.
Однажды полученный токен, то для дальнейшего использования следует указывать его следующим образом:
```php
$kkt->setAuthToken($token_string);
```
Если токен был установлен перед выполнением операции, то при выполнении операции будет использоваться именно он, а новый
запрашиваться не будет. Если операция завершится ошибочно из-за истёкшего токена, следует повторить операцию без
использования метода `setAuthToken()`, либо обнулив его следующим образом:
```php
$kkt->setAuthToken(null);
```
## Регистрация документа
Для регистрации документа **прихода** необходимо вызвать метод `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).
Тогда как для операций коррекции, которые описаны ниже, эти данные должны присутствовать.
Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellCorrection()`:
```php
$result = $kkt->sellCorrection($document);
```
Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyCorrection()`:
```php
$result = $kkt->buyCorrection($document);
```
### Собственный идентификатор документа
Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки.
По умолчанию это UUID версии 4.
Чтобы использовать собственный идентификатор, следует передать нужное строковое значение вторым параметром в любой из
шести описанных выше методов, например:
```php
$kkt->sell($document, $my_unique_id);
$kkt->sellRefund($document, $my_unique_id);
$kkt->buy($document, $my_unique_id);
$kkt->buyRefund($document, $my_unique_id);
$kkt->sellCorrection($document, $my_unique_id);
$kkt->buyCorrection($document, $my_unique_id);
```
Если `$my_unique_id` равен `null` или пустой строке, то будет сгенерирован новый UUID.
Узнать его можно будет только в ответе от ККТ.
### Передача callback_url
Перед регистрацией документа можно указать `callback_url`.
АТОЛ отправит на указанный 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->getLastResponse()->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();
```
---
[Вернуться к содержанию](readme.md)

119
docs/monitoring.md 100644
View File

@ -0,0 +1,119 @@
# Мониторинг ККТ
[Вернуться к содержанию](readme.md#toc)
---
Библиотека предоставляет возможность следить за состоянием ваших облачных ККТ через API.
## Инициализация
Для этого следует использовать класс `KktMonitor`:
```php
// можно передать параметры подключения в конструктор
$monitor = new AtolOnline\Api\Monitor(
login: 'mylogin',
password: 'qwerty'
);
// можно - отдельными сеттерами
$monitor = new AtolOnline\Api\Monitor();
->setLogin($credentials['login'])
->setPassword($credentials['password']);
```
Логин и пароль для мониторинга те же, что для регистрации документов.
**По умолчанию монитор работает в тестовом режиме.**
Перевести его в боевой режим можно:
```php
// передачей в конструктор `false` первым параметром:
$monitor = new AtolOnline\Api\Monitor(false, /*...*/);
// или отдельным сеттером
$monitor->setTestMode(false);
```
**Тестовый режим** нужен для проверки работоспособности библиотеки и API АТОЛ.
**В боевом режиме** можно получать данные по своим ККТ.
## Получение данных обо всех своих ККТ
Для получения данных обо всех своих ККТ следует вызвать метод `AtolOnline\Api\KktMonitor::getAll()`:
```php
$kkts = $monitor->getAll();
```
В ответе будет итерируемая коллекция объектов `AtolOnline\Entities\Kkt`.
Каждый из этих объектов содержит атрибуты:
```php
// для примера получим первую ККТ из всех
$kkt = $kkts->first();
// посмотрим на её атрибуты:
$kkt->serialNumber; // Заводской номер ККТ
$kkt->registrationNumber; // Регистрационный номер машины (РНМ)
$kkt->deviceNumber; // Номер автоматического устройства (внутренний идентификатор устройства)
$kkt->fiscalizationDate; // Дата активации (фискализации) ФН с указанием таймзоны
$kkt->fiscalStorageExpiration; // Дата замены ФН (Срок действия ФН), с указанием таймзоны
$kkt->signedDocuments; // Количество подписанных документов в ФН
$kkt->fiscalStoragePercentageUse; // Наполненость ФН в %
$kkt->fiscalStorageINN; // ИНН компании (указанный в ФН)
$kkt->fiscalStorageSerialNumber; // Заводской (серийный) номер ФН
$kkt->fiscalStoragePaymentAddress; // Адрес расчёта, указанный в ФН
$kkt->groupCode; // Код группы кассы
$kkt->timestamp; // Время и дата формирования данных, UTC
$kkt->isShiftOpened; // Признак открыта смена (true) или закрыта (false)
$kkt->shiftNumber; // Номер смены (или "Номер закрытой смены", когда смена закрыта)
$kkt->shiftReceipt; // Номер документа за смену (или "Кол-во чеков закрытой смены", когда смена закрыта)
$kkt->unsentDocs; // Количество неотправленных документов. Указывается, если значение отлично от 0.
$kkt->firstUnsetDocTimestamp; // Дата первого неотправленного документа. Указывается, если есть неотправленные документы.
$kkt->networkErrorCode; // Код ошибки сети
```
Эти поля описаны в документации мониторинга на [стр. 11](https://online.atol.ru/files/API_service_information.pdf)
Сопоставления кодов ошибок и их описаний доступны в массиве `AtolOnline\Entities\Kkt::ERROR_CODES`.
Объект класса приводится к JSON-строке автоматически или принудительно:
```php
echo $kkt;
$json_string = (string)$kkt;
```
Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
```php
$json_array = $kkt->jsonSerialize();
```
## Получение данных об одной из своих ККТ
Для этого следует вызвать метод `AtolOnline\Api\KktMonitor::getOne()`, передав на вход серийный номер (`serialNumber`)
нужной ККТ:
```php
$kkt = $monitor->getOne($kkts->first()->serialNumber);
```
Метод вернёт единственный объект `AtolOnline\Entities\Kkt` с атрибутами, описанными выше.
## Получение последнего ответа от сервера
Класс `AtolOnline\Api\KktMonitor` расширяет абстрактный класс `AtolOnline\Api\AtolClient`.
Это значит, что последний ответ от API АТОЛ всегда сохраняется объектом класса `AtolOnline\Api\AtolReponse`. К нему
можно обратиться через метод `AtolOnline\Api\KktMonitor::getResponse()`, независимо от того, что возвращают другие
методы монитора.
---
Читай также: [Обработка ответа API](response.md)
[Вернуться к содержанию](readme.md#toc)

View File

@ -1,134 +0,0 @@
# Работа с оплатами
[Вернуться к содержанию](readme.md)
---
## Один объект
Объект оплаты инициализируется следующим образом:
```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();
```
<a name="array"></a>
## Массив объектов оплат
> Максимальное количество объектов в массиве - 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();
```
---
[Вернуться к содержанию](readme.md)

View File

@ -1,15 +1,47 @@
# Документация к библиотеке atol-online
# Документация к библиотеке
Содержание:
1. [Работа с клиентами (покупателями)](client.md)
2. [Работа с компанией (продавцом)](company.md)
3. [Работа с оплатами](payments.md)
4. [Работа со ставками НДС](vats.md)
5. [Работа с предметами расчёта](items.md)
6. [Работа с данными коррекции](correction_info.md)
7. [Работа с документами](documents.md)
8. [Работа с ККТ](kkt.md)
<a id="toc"></a>
## Содержание
---
* [Общий алгоритм](#getstarted)
* [Сущность](entity.md)
* [Коллекция сущностей](collection.md)
* [Мониторинг ККТ](monitoring.md)
* [Фискализация документа](fiscalizing.md)
* [Обработка ответа API](response.md)
Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.
Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.
<a id="getstarted"></a>
## Общий алгоритм
1. Создать документ `AtolOnline\Entities\Receipt` или `AtolOnline\Entities\Correction`,
добавив в него все необходимые данные
2. Отправить документ на регистрацию:
2.1. *Необязательно:* при отправке задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа;
2.2. *Необязательно:* при отправке задать `external_id`, чтобы присвоить свой уникальный идентификатор документа;
3. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации.
> Если с документом был передан `callback_url`, то ответ придёт на этот самый URL.
> Он должен быть обработан вашим сервисом в соответствии с бизнес-процессом.
> Если с документом **не был** передан `callback_url` **либо** callback от АТОЛа не был получен/обработан в течение
> 300 секунд (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа.
4. Проверить состояние документа:
4.1. взять `uuid` ответа, полученного на запрос фискализации;
4.2. отправить его в запросе состояния документа.
> Данные о документе можно получить только в течение 32 суток после успешной фискализации.
В зависимости от специфики бизнеса, в документах можно/нужно передавать разную информацию. Подробности в документации
АТОЛ Онлайн и исходниках библиотеки.
## Об отправке электронного чека покупателю
После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в
связке с вашей ККТ:
* **по email**, если в документе указан email клиента;
* **по смс**:
* если в документе указан номер телефона клиента;
* если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД).
> Если заданы email и телефон, то ОФД отдаёт приоритет email.

57
docs/response.md 100644
View File

@ -0,0 +1,57 @@
# Обработка ответа API
[Вернуться к содержанию](readme.md#toc)
---
Объект класса `AtolOnline\Api\AtolResponse` возвращается всеми методами, которые обращаются к АТОЛ Онлайн API.
Поскольку классы `AtolOnline\Api\Fiscalizer` и `AtolOnline\Api\KktMonitor` наследуются от
абстрактного `AtolOnline\Api\AtolClient`, они оба предоставляют метод `getLastReponse()` для возврата последнего
полученного ответа от API.
Таким образом, а общем случае необязательно сразу сохранять ответ в переменную:
```php
$response = $kkt->sell($receipt);
```
Достаточно обратиться к нему позднее:
```php
$kkt->sell($receipt);
// ...
$response = $kkt->getLastResponse();
```
Однако, при сложной логике и многократных запросах следует пользоваться этим с умом и осторожностью.
Объект `AtolResponse` содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа.
```php
$headers = $response->getHeaders(); // вернёт заголовки
$code = $response->getCode(); // вернёт код ответа
$body = $response->getContent(); // вернёт JSON-декодированный объект тела ответа
```
Обращаться к полям тела ответа можно опуская вызов метода `getContent()`:
```php
// вернёт значение поля uuid
$uuid = $response->getContent()->uuid;
$uuid = $response->uuid;
// вернёт текст ошибки
$err_text = $response->getContent()->error->text;
$err_text = $response->error->text;
```
Проверка успешности операции доступна через метод `isSuccessful()`:
```php
$response->isSuccessful(); // вернёт true, если ошибок нет
```
---
[Вернуться к содержанию](readme.md#toc)

View File

@ -1,194 +0,0 @@
# Работа со ставками НДС
[Вернуться к содержанию](readme.md)
---
## Один объект
Объект ставки НДС инициализируется следующим образом:
```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();
```
<a name="array"></a>
## Массив объектов ставок НДС
> Максимальное количество в массиве - 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();
```
---
[Вернуться к содержанию](readme.md)

26
phpcs.xml 100644
View File

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<!--
~ Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
~
~ This code is licensed under MIT.
~ Этот код распространяется по лицензии MIT.
~ https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
-->
<ruleset name="atol-online standards">
<description>atol-online coding standards</description>
<file>src</file>
<file>tests</file>
<exclude-pattern>*/docs/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg value="p"/>
<rule ref="PSR12">
<exclude name="PSR12.Files.FileHeader.SpacingInsideBlock"/>
</rule>
</ruleset>

View File

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

62
psalm.xml 100644
View File

@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!--
~ Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
~
~ This code is licensed under MIT.
~ Этот код распространяется по лицензии MIT.
~ https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
-->
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorLevel="7"
addParamDefaultToDocblockType="true"
allowStringToStandInForClass="true"
disableSuppressAll="true"
ensureArrayIntOffsetsExist="true"
ensureArrayStringOffsetsExist="true"
findUnusedCode="true"
findUnusedPsalmSuppress="true"
findUnusedVariablesAndParams="true"
runTaintAnalysis="true"
sealAllMethods="true"
sealAllProperties="true"
strictBinaryOperands="true"
>
<!-- Default values:
allowNamedArgumentCalls="true"
checkForThrowsDocblock="false"
checkForThrowsInGlobalScope="false"
hideExternalErrors="false"
hoistConstants="false"
ignoreInternalFunctionFalseReturn="true"
ignoreInternalFunctionNullReturn="true"
inferPropertyTypesFromConstructor="true"
memoizeMethodCallResults="false"
rememberPropertyAssignmentsAfterCall="true"
reportInfo="true"
reportMixedIssues="true" - when errorLevel>=3
resolveFromConfigFile="true"
skipChecksOnUnresolvableIncludes="false"
throwExceptionOnError="false"
useDocblockPropertyTypes="false"
useDocblockTypes="true"
usePhpDocMethodsWithoutMagicCall="false"
usePhpDocPropertiesWithoutMagicCall="false"
-->
<projectFiles>
<directory name="src"/>
<directory name="tests"/>
<ignoreFiles>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>

View File

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

View File

@ -1,52 +1,61 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
/** @noinspection PhpMultipleClassDeclarationsInspection */
declare(strict_types=1);
namespace AtolOnline\Api;
use JetBrains\PhpStorm\{
ArrayShape,
Pure};
use JsonSerializable;
use Psr\Http\Message\ResponseInterface;
use stdClass;
use Stringable;
/**
* Класс AtolResponse, описывающий ответ от ККТ
*
* @property mixed $error
* @package AtolOnline\Api
*/
class KktResponse implements JsonSerializable
final class AtolResponse implements JsonSerializable, Stringable
{
/**
* @var int Код ответа сервера
*/
protected $code;
protected int $code;
/**
* @var \stdClass Содержимое ответа сервера
* @var object|array|null Содержимое ответа сервера
*/
protected $content;
protected object | array | null $content;
/**
* @var array Заголовки ответа
*/
protected $headers;
protected array $headers;
/**
* AtolResponse constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->code = $response->getStatusCode();
$this->headers = $response->getHeaders();
$this->content = json_decode($response->getBody());
$this->content = json_decode((string)$response->getBody());
}
/**
* Возвращает заголовки ответа
*
@ -56,18 +65,19 @@ class KktResponse implements JsonSerializable
{
return $this->headers;
}
/**
* Возвращает запрошенный параметр из декодированного объекта результата
*
* @param $name
* @return mixed
*/
public function __get($name)
#[Pure]
public function __get($name): mixed
{
return $this->getContent()->$name;
return $this->getContent()?->$name;
}
/**
* Возвращает код ответа
*
@ -77,42 +87,50 @@ class KktResponse implements JsonSerializable
{
return $this->code;
}
/**
* Возвращает объект результата запроса
*
* @return stdClass|null
* @return mixed
*/
public function getContent(): ?stdClass
public function getContent(): mixed
{
return $this->content;
}
/**
* Проверяет успешность запроса по соержимому результата
*
* @return bool
*/
public function isValid()
#[Pure]
public function isSuccessful(): bool
{
return !empty($this->getCode())
&& !empty($this->getContent())
&& empty($this->getContent()->error)
&& (int)$this->getCode() < 400;
&& $this->getCode() < 400;
}
/**
* Возвращает текстовое представление
*/
public function __toString()
public function __toString(): string
{
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
}
/**
* @inheritDoc
*/
public function jsonSerialize()
#[ArrayShape(
[
'code' => 'int',
'headers' => 'array|\string[][]',
'body' => 'mixed',
]
)]
public function jsonSerialize(): array
{
return [
'code' => $this->code,
@ -120,4 +138,4 @@ class KktResponse implements JsonSerializable
'body' => $this->content,
];
}
}
}

View File

@ -0,0 +1,379 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Api;
use AtolOnline\{
Constraints,
TestEnvParams};
use AtolOnline\Entities\{
Correction,
Receipt};
use AtolOnline\Exceptions\{
AuthFailedException,
EmptyGroupException,
EmptyLoginException,
EmptyPasswordException,
InvalidCallbackUrlException,
InvalidEntityInCollectionException,
InvalidInnLengthException,
InvalidPaymentAddressException,
InvalidUuidException,
TooLongCallbackUrlException,
TooLongLoginException,
TooLongPasswordException,
TooLongPaymentAddressException};
use GuzzleHttp\Exception\GuzzleException;
use JetBrains\PhpStorm\Pure;
use Ramsey\Uuid\Uuid;
/**
* Класс фискализатора для регистрации документов на ККТ
*/
final class Fiscalizer extends AtolClient
{
/**
* @var string|null Группа ККТ
*/
private ?string $group = null;
/**
* @var string|null URL для приёма POST-запроса от API АТОЛ с результатом регистрации документа
*/
private ?string $callback_url = null;
/**
* Конструктор
*
* @param bool $test_mode
* @param string|null $login
* @param string|null $password
* @param string|null $group
* @param array $config
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
* @throws EmptyGroupException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/
public function __construct(
bool $test_mode = true,
?string $login = null,
?string $password = null,
?string $group = null,
array $config = []
) {
parent::__construct($test_mode, $login, $password, $config);
!is_null($group) && $this->setGroup($group);
}
/**
* Возвращает группу доступа к ККТ в соответствии с флагом тестового режима
*
* @return string|null
*/
#[Pure]
public function getGroup(): ?string
{
return $this->isTestMode()
? TestEnvParams::FFD105()['group']
: $this->group;
}
/**
* Устанавливает группу доступа к ККТ
*
* @param string $group
* @return $this
* @throws EmptyGroupException
*/
public function setGroup(string $group): self
{
// критерии к длине строки не описаны ни в схеме, ни в документации
empty($group = trim($group)) && throw new EmptyGroupException();
$this->group = $group;
return $this;
}
/**
* Возвращает URL для приёма колбеков
*
* @return string|null
*/
public function getCallbackUrl(): ?string
{
return $this->callback_url;
}
/**
* Устанавливает URL для приёма колбеков
*
* @param string|null $url
* @return $this
* @throws TooLongCallbackUrlException
* @throws InvalidCallbackUrlException
*/
public function setCallbackUrl(?string $url = null): self
{
$url = trim((string)$url);
if (mb_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
throw new TooLongCallbackUrlException($url);
} elseif (!empty($url) && !preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
throw new InvalidCallbackUrlException();
}
$this->callback_url = $url ?: null;
return $this;
}
/**
* Регистрирует документ прихода
*
* @param Receipt $receipt Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sell(Receipt $receipt, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('sell', $receipt, $external_id);
}
/**
* Регистрирует документ возврата прихода
*
* @param Receipt $receipt Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sellRefund(Receipt $receipt, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('sell_refund', $receipt, $external_id);
}
/**
* Регистрирует документ коррекции прихода
*
* @param Correction $correction Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sellCorrect(Correction $correction, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('sell_correction', $correction, $external_id);
}
/**
* Регистрирует документ расхода
*
* @param Receipt $receipt Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buy(Receipt $receipt, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('buy', $receipt, $external_id);
}
/**
* Регистрирует документ возврата расхода
*
* @param Receipt $receipt Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buyRefund(Receipt $receipt, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('buy_refund', $receipt, $external_id);
}
/**
* Регистрирует документ коррекции расхода
*
* @param Correction $correction Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buyCorrect(Correction $correction, ?string $external_id = null): ?AtolResponse
{
return $this->registerDocument('buy_correction', $correction, $external_id);
}
/**
* Проверяет статус чека на ККТ один раз
*
* @param string $uuid UUID регистрации
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidUuidException
*/
public function getDocumentStatus(string $uuid): ?AtolResponse
{
!Uuid::isValid($uuid = trim($uuid)) && throw new InvalidUuidException($uuid);
return $this->auth()
? $this->sendRequest('GET', $this->getFullUrl('report/' . $uuid))
: null;
}
/**
* Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
* Вернёт результат как только при очередной проверке сменится статус регистрации документа.
*
* @param string $uuid UUID регистрации
* @param int $retry_count Количество попыток
* @param int $timeout Таймаут в секундах между попытками
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidUuidException
*/
public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1): ?AtolResponse
{
$try = 0;
do {
$response = $this->getDocumentStatus($uuid);
if ($response->isSuccessful() && $response->getContent()->status == 'done') {
break;
} else {
sleep($timeout);
}
++$try;
} while ($try < $retry_count);
return $response;
}
/**
* Отправляет документ на регистрацию
*
* @param string $api_method Метод API
* @param Receipt|Correction $document Документ
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
protected function registerDocument(
string $api_method,
Receipt | Correction $document,
?string $external_id = null
): ?AtolResponse {
$this->isTestMode() && $document->getCompany()
->setInn(TestEnvParams::FFD105()['inn'])
->setPaymentAddress(TestEnvParams::FFD105()['payment_address']);
$this->isTestMode() && $document instanceof Receipt
&& $document->getClient()->setInn(TestEnvParams::FFD105()['inn']);
$this->getCallbackUrl() && $data['service'] = ['callback_url' => $this->getCallbackUrl()];
return $this->auth()
? $this->sendRequest(
'POST',
$this->getFullUrl($api_method),
array_merge($data ?? [], [
'timestamp' => date('d.m.Y H:i:s'),
'external_id' => $external_id ?: Uuid::uuid4()->toString(),
$document::DOC_TYPE => $document->jsonSerialize(),
])
)
: null;
}
/**
* @inheritDoc
*/
#[Pure]
protected function getAuthEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/possystem/v4/getToken'
: 'https://online.atol.ru/possystem/v4/getToken';
}
/**
* @inheritDoc
*/
#[Pure]
protected function getMainEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/possystem/v4/'
: 'https://online.atol.ru/possystem/v4/';
}
/**
* Возвращает полный URL метода API
*
* @param string $api_method
* @return string
*/
#[Pure]
protected function getFullUrl(string $api_method): string
{
return $this->getMainEndpoint() . $this->getGroup() . '/' . trim($api_method);
}
}

130
src/Api/Monitor.php 100644
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,108 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Constants;
/**
* Константы, определяющие признаки предметов расчёта. Тег ФФД - 1212.
*
* @package AtolOnline\Constants
*/
class PaymentObjects
{
/**
* Товар, кроме подакцизного
*/
const COMMODITY = 'commodity';
/**
* Товар подакцизный
*/
const EXCISE = 'excise';
/**
* Работа
*/
const JOB = 'job';
/**
* Услуга
*/
const SERVICE = 'service';
/**
* Ставка азартной игры
*/
const GAMBLING_BET = 'gambling_bet';
/**
* Выигрыш азартной игры
*/
const GAMBLING_PRIZE = 'gambling_prize';
/**
* Лотерея
*/
const LOTTERY = 'lottery';
/**
* Выигрыш лотереи
*/
const LOTTERY_PRIZE = 'lottery_prize';
/**
* Предоставление результатов интеллектуальной деятельности
*/
const INTELLECTUAL_ACTIVITY = 'intellectual_activity';
/**
* Платёж (задаток, кредит, аванс, предоплата, пеня, штраф, бонус и пр.)
*/
const PAYMENT = 'payment';
/**
* Агентское вознаграждение
*/
const AGENT_COMMISSION = 'agent_commission';
/**
* Составной предмет расчёта
*/
const COMPOSITE = 'composite';
/**
* Другой предмет расчёта
*/
const ANOTHER = 'another';
/**
* Имущественное право
*/
const PROPERTY_RIGHT = 'property_right';
/**
* Внереализационный доход
*/
const NON_OPERATING_GAIN = 'non-operating_gain';
/**
* Страховые взносы
*/
const INSURANCE_PREMIUM = 'insurance_premium';
/**
* Торговый сбор
*/
const SALES_TAX = 'sales_tax';
/**
* Курортный сбор
*/
const RESORT_FEE = 'resort_fee';
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,171 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
/**
* Класс CorrectionInfo, описывающий данные коррекции
*
* @package AtolOnline\Entities
*/
class CorrectionInfo extends Entity
{
/**
* @var int Тип коррекции. Тег ФФД - 1173.
*/
protected $type;
/**
* @var string Дата документа основания для коррекции. Тег ФФД - 1178.
*/
protected $base_date;
/**
* @var string Номер документа основания для коррекции. Тег ФФД - 1179.
*/
protected $base_number;
/**
* @var string Описание коррекции. Тег ФФД - 1177.
*/
protected $base_name;
/**
* CorrectionInfo constructor.
*
* @param string|null $type Тип коррекции
* @param string|null $base_date Дата документа
* @param string|null $base_number Номер документа
* @param string|null $base_name Описание коррекции
*/
public function __construct(?string $type = null, ?string $base_date = null, ?string $base_number = null, ?string $base_name = null)
{
if ($type) {
$this->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() ?? '' // обязателен
];
}
}

View File

@ -1,429 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolCashierTooLongException;
use AtolOnline\Exceptions\AtolException;
use AtolOnline\Exceptions\AtolInvalidJsonException;
/**
* Класс, описывающий документ
*
* @package AtolOnline\Entities
*/
class Document extends Entity
{
/**
* @var \AtolOnline\Entities\ItemArray Массив предметов расчёта
*/
protected $items;
/**
* @var \AtolOnline\Entities\VatArray Массив ставок НДС
*/
protected $vats;
/**
* @var \AtolOnline\Entities\PaymentArray Массив оплат
*/
protected $payments;
/**
* @var \AtolOnline\Entities\Company Объект компании (продавца)
*/
protected $company;
/**
* @var \AtolOnline\Entities\Client Объект клиента (покупателя)
*/
protected $client;
/**
* @var int Итоговая сумма чека. Тег ФФД - 1020.
*/
protected $total = 0;
/**
* @var string ФИО кассира. Тег ФФД - 1021.
*/
protected $cashier;
/**
* @var \AtolOnline\Entities\CorrectionInfo Данные коррекции
*/
protected $correction_info;
/**
* Document constructor.
*/
public function __construct()
{
$this->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([]);
return $this;
}
/**
* Добавляет новую ставку НДС в массив ставок НДС
*
* @param \AtolOnline\Entities\Vat $vat Объект ставки НДС
* @return $this
* @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
*/
public function addVat(Vat $vat)
{
$this->vats->add($vat);
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);
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|null
*/
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|null
*/
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 ((function_exists('mb_strlen') ? mb_strlen($cashier) : strlen($cashier)) > 64) {
throw new AtolCashierTooLongException($cashier, 64);
}
$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;
$this->clearVats();
foreach ($this->items->get() as $item) {
$sum += $item->calcSum();
$this->addVat(new Vat($item->getVat()->getType(), $item->getSum()));
}
return $this->total = round($sum, 2);
}
/**
* Возвращает итоговую сумму чека. Тег ФФД - 1020.
*
* @return float
*/
public function getTotal(): float
{
return $this->total;
}
/**
* Собирает объект документа из сырой json-строки
*
* @param string $json
* @return \AtolOnline\Entities\Document
* @throws \AtolOnline\Exceptions\AtolEmailTooLongException
* @throws \AtolOnline\Exceptions\AtolEmailValidateException
* @throws \AtolOnline\Exceptions\AtolException
* @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
* @throws \AtolOnline\Exceptions\AtolInvalidJsonException
* @throws \AtolOnline\Exceptions\AtolNameTooLongException
* @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException
* @throws \AtolOnline\Exceptions\AtolPhoneTooLongException
* @throws \AtolOnline\Exceptions\AtolPriceTooHighException
* @throws \AtolOnline\Exceptions\AtolTooManyException
* @throws \AtolOnline\Exceptions\AtolTooManyItemsException
* @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException
* @throws \AtolOnline\Exceptions\AtolUnitTooLongException
* @throws \AtolOnline\Exceptions\AtolUserdataTooLongException
*/
public static function fromRaw(string $json)
{
$array = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new AtolInvalidJsonException();
}
$doc = new self();
if (isset($array['company'])) {
$doc->setCompany(new Company(
$array['company']['sno'] ?? null,
$array['company']['inn'] ?? null,
$array['company']['payment_address'] ?? null,
$array['company']['email'] ?? null
));
}
if (isset($array['client'])) {
$doc->setClient(new Client(
$array['client']['name'] ?? null,
$array['client']['phone'] ?? null,
$array['client']['email'] ?? null,
$array['client']['inn'] ?? null
));
}
if (isset($array['items'])) {
foreach ($array['items'] as $ar_item) {
$item = new Item(
$ar_item['name'] ?? null,
$ar_item['price'] ?? null,
$ar_item['quantity'] ?? null,
$ar_item['measurement_unit'] ?? null,
$ar_item['vat']['type'] ?? null,
$ar_item['payment_object'] ?? null,
$ar_item['payment_method'] ?? null
);
if (!empty($ar_item['user_data'])) {
$item->setUserData($ar_item['user_data'] ?? null);
}
$doc->addItem($item);
}
}
if (isset($array['payments'])) {
foreach ($array['payments'] as $ar_payment) {
$payment = new Payment();
if (isset($ar_payment['type'])) {
$payment->setType($ar_payment['type']);
}
if (isset($ar_payment['sum'])) {
$payment->setSum($ar_payment['sum']);
}
$doc->payments->add($payment);
}
}
if (isset($array['total']) && $array['total'] != $doc->calcTotal()) {
throw new AtolException('Real total sum not equals to provided in JSON one');
}
return $doc;
}
/**
* Возвращает массив для кодирования в json
*
* @throws \Exception
*/
public function jsonSerialize()
{
if ($this->getCompany()) {
$json['company'] = $this->getCompany()->jsonSerialize(); // обязательно
}
if ($this->getPayments()) {
$json['payments'] = $this->payments->jsonSerialize(); // обязательно
}
if ($this->getCashier()) {
$json['cashier'] = $this->getCashier();
}
if ($this->getCorrectionInfo()) {
$json['correction_info'] = $this->getCorrectionInfo()->jsonSerialize(); // обязательно для коррекционных
} else {
if ($this->getClient()) {
$json['client'] = $this->getClient()->jsonSerialize(); // обязательно для некоррекционных
}
if ($this->getItems()) {
$json['items'] = $this->items->jsonSerialize(); // обязательно для некоррекционных
}
$json['total'] = $this->calcTotal(); // обязательно для некоррекционных
}
if ($this->getVats()) {
$json['vats'] = $this->vats->jsonSerialize();
}
return $json;
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use JsonSerializable;
/**
* Абстрактное описание любой сущности, представляемой как JSON
*
* @package AtolOnline\Entities
*/
abstract class Entity implements JsonSerializable
{
/**
* @inheritDoc
*/
public function __toString()
{
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
}
}

View File

@ -1,395 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{Exceptions\AtolNameTooLongException,
Exceptions\AtolPriceTooHighException,
Exceptions\AtolTooManyException,
Exceptions\AtolUnitTooLongException,
Exceptions\AtolUserdataTooLongException,
Traits\RublesKopeksConverter};
/**
* Предмет расчёта (товар, услуга)
*
* @package AtolOnline\Entities
*/
class Item extends Entity
{
use RublesKopeksConverter;
/**
* @var string Наименование. Тег ФФД - 1030.
*/
protected $name;
/**
* @var int Цена в копейках (с учётом скидок и наценок). Тег ФФД - 1079.
*/
protected $price = 0;
/**
* @var float Количество, вес. Тег ФФД - 1023.
*/
protected $quantity = 0.0;
/**
* @var float Сумма в копейках. Тег ФФД - 1043.
*/
protected $sum = 0;
/**
* @var string Единица измерения количества. Тег ФФД - 1197.
*/
protected $measurement_unit;
/**
* @var Vat Ставка НДС
*/
protected $vat;
/**
* @var string Признак способа расчёта. Тег ФФД - 1214.
*/
protected $payment_method;
/**
* @var string Признак объекта расчёта. Тег ФФД - 1212.
*/
protected $payment_object;
/**
* @var string Дополнительный реквизит. Тег ФФД - 1191.
*/
protected $user_data;
/**
* Item constructor.
*
* @param string|null $name Наименование
* @param float|null $price Цена за одну единицу
* @param float|null $quantity Количество
* @param string|null $measurement_unit Единица измерения
* @param string|null $vat_type Ставка НДС
* @param string|null $payment_object Признак
* @param string|null $payment_method Способ расчёта
* @throws AtolNameTooLongException Слишком длинное наименование
* @throws AtolPriceTooHighException Слишком высокая цена за одну единицу
* @throws AtolTooManyException Слишком большое количество
* @throws AtolUnitTooLongException Слишком длинное название единицы измерения
*/
public function __construct(
?string $name = null,
?float $price = null,
?float $quantity = null,
?string $measurement_unit = null,
$vat_type = null,
?string $payment_object = null,
?string $payment_method = null
) {
if ($name) {
$this->setName($name);
}
if ($price) {
$this->setPrice($price);
}
if ($quantity) {
$this->setQuantity($quantity);
}
if ($measurement_unit) {
$this->setMeasurementUnit($measurement_unit);
}
if ($vat_type) {
$this->setVatType($vat_type);
}
if ($payment_object) {
$this->setPaymentObject($payment_object);
}
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 ((function_exists('mb_strlen') ? mb_strlen($name) : 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 AtolTooManyException Слишком большое количество
* @throws AtolPriceTooHighException Слишком высокая общая стоимость
* @throws AtolUnitTooLongException Слишком длинное название единицы измерения
*/
public function setQuantity(float $quantity, string $measurement_unit = null)
{
$quantity = round($quantity, 3);
if ($quantity > 99999.999) {
throw new AtolTooManyException($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 ((function_exists('mb_strlen') ? mb_strlen($measurement_unit) : 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 ((function_exists('mb_strlen') ? mb_strlen($user_data) : 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;
}
}

View File

@ -1,113 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolTooManyItemsException;
/**
* Класс, описывающий массив предметов расчёта
*
* @package AtolOnline\Entities
*/
class ItemArray extends Entity
{
/**
* Максимальное количество элементов в массиве
* По документации ограничение по количеству предметов расчёта = от 1 до 100,
* однако в схеме sell не указан receipt.properties.items.maxItems
*/
public const MAX_COUNT = 100;
/**
* @var Item[] Массив предметов расчёта
*/
private $items = [];
/**
* ItemArray constructor.
*
* @param Item[]|null $items Массив предметов расчёта
* @throws AtolTooManyItemsException Слишком много предметов расчёта
*/
public function __construct(?array $items = null)
{
if ($items) {
$this->set($items);
}
}
/**
* Устанавливает массив предметов расчёта
*
* @param Item[] $items Массив предметов расчёта
* @return $this
* @throws AtolTooManyItemsException Слишком много предметов расчёта
*/
public function set(array $items)
{
if ($this->validateCount($items)) {
$this->items = $items;
}
return $this;
}
/**
* Добавляет предмет расчёта в массив
*
* @param Item $item Объект предмета расчёта
* @return $this
* @throws AtolTooManyItemsException Слишком много предметов расчёта
*/
public function add(Item $item)
{
if ($this->validateCount()) {
$this->items[] = $item;
}
return $this;
}
/**
* Возвращает массив предметов расчёта
*
* @return Item[]
*/
public function get()
{
return $this->items;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$result = [];
foreach ($this->get() as $item) {
$result[] = $item->jsonSerialize();
}
return $result;
}
/**
* Проверяет количество предметов расчёта
*
* @param Item[]|null $items Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyItemsException Слишком много предметов расчёта
*/
protected function validateCount(?array $items = null): bool
{
if ((!empty($items) && count($items) >= self::MAX_COUNT) || count($this->items) >= self::MAX_COUNT) {
throw new AtolTooManyItemsException(count($items), self::MAX_COUNT);
}
return true;
}
}

View File

@ -1,97 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\Constants\PaymentTypes;
/**
* Класс, описывающий оплату. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
*
* @package AtolOnline\Entities
*/
class Payment extends Entity
{
/**
* @var int Тип оплаты
*/
protected $type;
/**
* @var float Сумма оплаты
*/
protected $sum;
/**
* Payment constructor.
*
* @param int $payment_type Тип оплаты
* @param float $sum Сумма оплаты
*/
public function __construct(int $payment_type = PaymentTypes::ELECTRON, float $sum = 0.0)
{
$this->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(),
];
}
}

View File

@ -1,111 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolTooManyPaymentsException;
/**
* Класс, описывающий массив оплат
*
* @package AtolOnline\Entities
*/
class PaymentArray extends Entity
{
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 10;
/**
* @var Payment[] Массив оплат
*/
private $payments = [];
/**
* ItemArray constructor.
*
* @param Payment[]|null $payments Массив оплат
* @throws AtolTooManyPaymentsException Слишком много оплат
*/
public function __construct(?array $payments = null)
{
if ($payments) {
$this->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 true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyPaymentsException Слишком много оплат
*/
protected function validateCount(?array $payments = null): bool
{
if ((!empty($payments) && count($payments) >= self::MAX_COUNT) || count($this->payments) >= self::MAX_COUNT) {
throw new AtolTooManyPaymentsException(count($payments), self::MAX_COUNT);
}
return true;
}
}

View File

@ -1,187 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{Constants\VatTypes, Traits\RublesKopeksConverter};
/**
* Класс, описывающий ставку НДС
*
* @package AtolOnline\Entities
*/
class Vat extends Entity
{
use RublesKopeksConverter;
/**
* @var string Выбранный тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
*/
private $type;
/**
* @var int Сумма в копейках, от которой пересчитывается размер НДС
*/
private $sum_original = 0;
/**
* @var int Сумма НДС в копейках
*/
private $sum_final = 0;
/**
* Vat constructor.
*
* @param string $type Тип ставки НДС
* @param float|null $rubles Исходная сумма в рублях, от которой нужно расчитать размер НДС
*/
public function __construct(string $type = VatTypes::NONE, float $rubles = null)
{
$this->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;
}
return 0;
}
/**
* Возвращает тип ставки НДС. Тег ФФД - 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;
}
}

View File

@ -1,115 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\AtolTooManyVatsException;
/**
* Класс, описывающий массив ставок НДС
*
* @package AtolOnline\Entities
*/
class VatArray extends Entity
{
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 6;
/**
* @var Vat[] Массив ставок НДС
*/
private $vats = [];
/**
* VatArray constructor.
*
* @param Vat[]|null $vats Массив ставок НДС
* @throws AtolTooManyVatsException Слишком много ставок НДС
*/
public function __construct(?array $vats = null)
{
if ($vats) {
$this->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()) {
if (isset($this->vats[$vat->getType()])) {
$this->vats[$vat->getType()]->addSum($vat->getSum());
} else {
$this->vats[$vat->getType()] = $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 Vat[]|null $vats Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws AtolTooManyVatsException Слишком много ставок НДС
*/
protected function validateCount(?array $vats = null): bool
{
if ((!empty($vats) && count($vats) >= self::MAX_COUNT) || count($this->vats) >= self::MAX_COUNT) {
throw new AtolTooManyVatsException(count($vats), self::MAX_COUNT);
}
return true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,34 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать слишком длинное имя
*
* @package AtolOnline\Exceptions
*/
class AtolNameTooLongException extends AtolTooLongException
{
/**
* @inheritDoc
*/
protected $ffd_tags = [
1026,
1030,
1085,
1225,
1227,
];
/**
* @var string Сообщение об ошибке
*/
protected $message = 'Name is too long';
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +0,0 @@
<?php
/**
* Copyright (c) Антон Аксенов (aka Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке добавить слишком много ставок НДС в массив
*
* @package AtolOnline\Exceptions
*/
class AtolTooManyVatsException extends AtolTooManyException
{
/**
* @inheritDoc
*/
protected $ffd_tags = [
1102,
1103,
1104,
1105,
1106,
1107,
];
/**
* @var string Сообщение об ошибке
*/
protected $message = 'Too many vats';
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,71 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Collections;
use AtolOnline\Exceptions\InvalidEntityInCollectionException;
use Illuminate\Support\Collection;
/**
* Абстрактное описание коллекции любых сущностей
*/
abstract class EntityCollection extends Collection
{
/**
* @inheritDoc
* @throws InvalidEntityInCollectionException
*/
public function jsonSerialize(): array
{
$this->checkCount();
$this->checkItemsClasses();
return parent::jsonSerialize();
}
/**
* Проверяет количество элементов коллекции
*
* @return static
*/
public function checkCount(): static
{
$this->isEmpty() && throw new (static::EMPTY_EXCEPTION_CLASS)();
$this->count() > static::MAX_COUNT && throw new (static::TOO_MANY_EXCEPTION_CLASS)(static::MAX_COUNT);
return $this;
}
/**
* Проверяет корректность класса элемента коллекции
*
* @param mixed $item
* @return static
* @throws InvalidEntityInCollectionException
*/
public function checkItemClass(mixed $item): static
{
if (!is_object($item) || $item::class !== static::ENTITY_CLASS) {
throw new InvalidEntityInCollectionException(static::class, static::ENTITY_CLASS, $item);
}
return $this;
}
/**
* Проверяет корректность классов элементов коллекции
*
* @return static
* @throws InvalidEntityInCollectionException
*/
public function checkItemsClasses(): static
{
return $this->each(fn ($item) => $this->checkItemClass($item));
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constraints;
use AtolOnline\Entities\Item;
use AtolOnline\Exceptions\EmptyItemsException;
use AtolOnline\Exceptions\TooManyItemsException;
/**
* Класс, описывающий коллекцию предметов расчёта для документа
*/
final class Items extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Item::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_ITEMS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyItemsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyItemsException::class;
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constraints;
use AtolOnline\Entities\Payment;
use AtolOnline\Exceptions\EmptyPaymentsException;
use AtolOnline\Exceptions\TooManyPaymentsException;
/**
* Класс, описывающий коллекцию оплат для документа
*/
final class Payments extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Payment::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_PAYMENTS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyPaymentsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyPaymentsException::class;
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constraints;
use AtolOnline\Entities\Vat;
use AtolOnline\Exceptions\EmptyVatsException;
use AtolOnline\Exceptions\TooManyVatsException;
/**
* Класс, описывающий коллекцию ставок НДС для документа
*/
final class Vats extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Vat::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_VATS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyVatsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyVatsException::class;
}

221
src/Constraints.php 100644
View File

@ -0,0 +1,221 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline;
/**
* Класс с константами ограничений
*/
final class Constraints
{
/**
* Максимальная длина Callback URL
*/
public const MAX_LENGTH_CALLBACK_URL = 256;
/**
* Максимальная длина email
*/
public const MAX_LENGTH_EMAIL = 64;
/**
* Максимальная длина логина ККТ
*/
public const MAX_LENGTH_LOGIN = 100;
/**
* Максимальная длина пароля ККТ
*/
public const MAX_LENGTH_PASSWORD = 100;
/**
* Максимальная длина адреса места расчётов
*/
public const MAX_LENGTH_PAYMENT_ADDRESS = 256;
/**
* Максимальная длина наименования покупателя (1227)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
public const MAX_LENGTH_CLIENT_NAME = 256;
/**
* Максимальная длина наименования предмета расчёта (1030)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_LENGTH_ITEM_NAME = 128;
/**
* Максимальная цена за единицу предмета расчёта (1079)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_COUNT_ITEM_PRICE = 42949672.95;
/**
* Максимальное количество (вес) единицы предмета расчёта (1023)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_COUNT_ITEM_QUANTITY = 99999.999;
/**
* Максимальная стоимость всех предметов расчёта в документе прихода, расхода,
* возврата прихода, возврата расхода (1043)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_COUNT_ITEM_SUM = 42949672.95;
/**
* Максимальная длина телефона или email покупателя (1008)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
public const MAX_LENGTH_CLIENT_CONTACT = 64;
/**
* Длина операции для платёжного агента (1044)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 19
*/
public const MAX_LENGTH_PAYING_AGENT_OPERATION = 24;
/**
* Максимальное количество предметов расчёта в документе прихода, расхода, возврата прихода, возврата расхода
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_COUNT_DOC_ITEMS = 100;
/**
* Максимальная длина единицы измерения предмета расчётов
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_LENGTH_MEASUREMENT_UNIT = 16;
/**
* Максимальная длина пользовательских данных для предмета расчётов (1191)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 29
*/
public const MAX_LENGTH_USER_DATA = 64;
/**
* Минимальная длина кода таможенной декларации (1231)
*
* @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/receipt/items/declaration_number"
*/
public const MIN_LENGTH_DECLARATION_NUMBER = 1;
/**
* Максимальная длина кода таможенной декларации (1231)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30
*/
public const MAX_LENGTH_DECLARATION_NUMBER = 32;
/**
* Максимальное количество платежей в любом документе
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30 и 35
*/
public const MAX_COUNT_DOC_PAYMENTS = 10;
/**
* Максимальное количество ставок НДС в любом документе
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 31 и 36
*/
public const MAX_COUNT_DOC_VATS = 6;
/**
* Максимальная сумма одной оплаты
*/
public const MAX_COUNT_PAYMENT_SUM = 99999.999;
/**
* Максимальная длина имени кассира (1021)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
public const MAX_LENGTH_CASHIER_NAME = 64;
/**
* Максимальная длина кода товара в байтах (1162)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
public const MAX_LENGTH_ITEM_CODE = 32;
/**
* Максимальная длина значения дополнительного реквизита чека (1192)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
public const MAX_LENGTH_ADD_CHECK_PROP = 16;
/**
* Максимальная длина наименования дополнительного реквизита пользователя (1085)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
public const MAX_LENGTH_ADD_USER_PROP_NAME = 64;
/**
* Максимальная длина значения дополнительного реквизита пользователя (1086)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
public const MAX_LENGTH_ADD_USER_PROP_VALUE = 256;
/**
* Формат даты документа коррекции
*/
public const CORRECTION_DATE_FORMAT = 'd.m.Y';
/**
* Регулярное выражение для валидации строки ИНН
*
* @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/receipt/client/inn"
*/
public const PATTERN_INN
= /* @lang PhpRegExp */
'/(^[\d]{10}$)|(^[\d]{12}$)/';
/**
* Регулярное выражение для валидации номера телефона
*
* @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/definitions/phone_number"
*/
public const PATTERN_PHONE
= /* @lang PhpRegExp */
'/^([^\s\\\]{0,17}|\+[^\s\\\]{1,18})$/';
/**
* Регулярное выражение для валидации строки Callback URL
*/
public const PATTERN_CALLBACK_URL
= /* @lang PhpRegExp */
'/^http(s?):\/\/[0-9a-zA-Zа-яА-Я]' .
'([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-.?,\'\/\\\+&=%\$#_]*)?$/';
/**
* Регулярное выражение для валидации кода страны происхождения товара
*/
public const PATTERN_OKSM_CODE
= /* @lang PhpRegExp */
'/^[\d]{3}$/';
}

View File

@ -0,0 +1,123 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Constraints;
use AtolOnline\Exceptions\{
EmptyAddUserPropNameException,
EmptyAddUserPropValueException,
TooLongAddUserPropNameException,
TooLongAddUserPropValueException};
use JetBrains\PhpStorm\{
ArrayShape,
Pure};
/**
* Класс, описывающий дополнительный реквизит пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
final class AdditionalUserProps extends Entity
{
/**
* Конструктор объекта покупателя
*
* @param string $name Наименование (1227)
* @param string $value Значение (1008)
* @throws EmptyAddUserPropNameException
* @throws EmptyAddUserPropValueException
* @throws TooLongAddUserPropNameException
* @throws TooLongAddUserPropValueException
*/
public function __construct(
protected string $name,
protected string $value,
) {
$this->setName($name)->setValue($value);
}
/**
* Возвращает наименование реквизита
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Устанавливает наименование реквизита
*
* @param string $name
* @return $this
* @throws TooLongAddUserPropNameException
* @throws EmptyAddUserPropNameException
*/
public function setName(string $name): self
{
$name = trim($name);
if (mb_strlen($name) > Constraints::MAX_LENGTH_ADD_USER_PROP_NAME) {
throw new TooLongAddUserPropNameException($name);
}
if (empty($name)) {
throw new EmptyAddUserPropNameException($name);
}
$this->name = $name;
return $this;
}
/**
* Возвращает установленный телефон
*
* @return string|null
*/
public function getValue(): ?string
{
return $this->value;
}
/**
* Устанавливает значение реквизита
*
* @param string $value
* @return $this
* @throws TooLongAddUserPropValueException
* @throws EmptyAddUserPropValueException
*/
public function setValue(string $value): self
{
$value = trim($value);
if (mb_strlen($value) > Constraints::MAX_LENGTH_CLIENT_NAME) {
throw new TooLongAddUserPropValueException($value);
}
if (empty($value)) {
throw new EmptyAddUserPropValueException($value);
}
$this->value = $value;
return $this;
}
/**
* @inheritDoc
*/
#[Pure]
#[ArrayShape(['name' => 'string', 'value' => 'null|string'])]
public function jsonSerialize(): array
{
return [
'name' => $this->getName(),
'value' => $this->getValue(),
];
}
}

View File

@ -0,0 +1,149 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Enums\AgentType;
/**
* Класс, описывающий данные агента
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 26-28
*/
final class AgentInfo extends Entity
{
/**
* Конструктор
*
* @param AgentType|null $type Признак агента (1057)
* @param PayingAgent|null $payingAgent Платёжный агент
* @param ReceivePaymentsOperator|null $receivePaymentsOperator Оператор по приёму платежей
* @param MoneyTransferOperator|null $moneyTransferOperator Оператор перевода
*/
public function __construct(
protected ?AgentType $type = null,
protected ?PayingAgent $payingAgent = null,
protected ?ReceivePaymentsOperator $receivePaymentsOperator = null,
protected ?MoneyTransferOperator $moneyTransferOperator = null,
) {
$this->setType($type);
}
/**
* Возвращает установленный признак оператора
*
* @return AgentType|null
*/
public function getType(): ?AgentType
{
return $this->type;
}
/**
* Устанавливает признак оператора
*
* @param AgentType|null $type
* @return AgentInfo
*/
public function setType(?AgentType $type): self
{
$this->type = $type;
return $this;
}
/**
* Возвращает установленного платёжного агента
*
* @return PayingAgent|null
*/
public function getPayingAgent(): ?PayingAgent
{
return $this->payingAgent;
}
/**
* Устанавливает платёжного агента
*
* @param PayingAgent|null $agent
* @return AgentInfo
*/
public function setPayingAgent(?PayingAgent $agent): self
{
$this->payingAgent = $agent;
return $this;
}
/**
* Возвращает установленного оператора по приёму платежей
*
* @return ReceivePaymentsOperator|null
*/
public function getReceivePaymentsOperator(): ?ReceivePaymentsOperator
{
return $this->receivePaymentsOperator;
}
/**
* Устанавливает оператора по приёму платежей
*
* @param ReceivePaymentsOperator|null $operator
* @return AgentInfo
*/
public function setReceivePaymentsOperator(?ReceivePaymentsOperator $operator): self
{
$this->receivePaymentsOperator = $operator;
return $this;
}
/**
* Возвращает установленного оператора перевода
*
* @return MoneyTransferOperator|null
*/
public function getMoneyTransferOperator(): ?MoneyTransferOperator
{
return $this->moneyTransferOperator;
}
/**
* Устанавливает оператора перевода
*
* @param MoneyTransferOperator|null $operator
* @return AgentInfo
*/
public function setMoneyTransferOperator(?MoneyTransferOperator $operator): self
{
$this->moneyTransferOperator = $operator;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$json = [];
if ($this?->type) {
$json['type'] = $this->getType();
}
if ($this->payingAgent?->jsonSerialize()) {
$json['paying_agent'] = $this->payingAgent->jsonSerialize();
}
if ($this->receivePaymentsOperator?->jsonSerialize()) {
$json['receive_payments_operator'] = $this->receivePaymentsOperator->jsonSerialize();
}
if ($this->moneyTransferOperator?->jsonSerialize()) {
$json['money_transfer_operator'] = $this->moneyTransferOperator->jsonSerialize();
}
return $json;
}
}

View File

@ -0,0 +1,133 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Constraints;
use AtolOnline\Exceptions\{
InvalidEmailException,
InvalidInnLengthException,
InvalidPhoneException,
TooLongClientNameException,
TooLongEmailException};
use AtolOnline\Traits\{
HasEmail,
HasInn};
use JetBrains\PhpStorm\Pure;
/**
* Класс, описывающий покупателя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
final class Client extends Entity
{
use HasEmail;
use HasInn;
/**
* Конструктор объекта покупателя
*
* @param string|null $name Наименование (1227)
* @param string|null $email Телефон (1008)
* @param string|null $phone Email (1008)
* @param string|null $inn ИНН (1228)
* @throws InvalidEmailException
* @throws InvalidInnLengthException
* @throws InvalidPhoneException
* @throws TooLongClientNameException
* @throws TooLongEmailException
*/
public function __construct(
protected ?string $name = null,
protected ?string $phone = null,
?string $email = null,
?string $inn = null
) {
!is_null($name) && $this->setName($name);
!is_null($email) && $this->setEmail($email);
!is_null($phone) && $this->setPhone($phone);
!is_null($inn) && $this->setInn($inn);
}
/**
* Возвращает наименование покупателя
*
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Устанавливает наименование покупателя
*
* @param string|null $name
* @return $this
* @throws TooLongClientNameException
*/
public function setName(?string $name): self
{
if (is_string($name)) {
$name = preg_replace('/[\n\r\t]/', '', trim($name));
if (mb_strlen($name) > Constraints::MAX_LENGTH_CLIENT_NAME) {
throw new TooLongClientNameException($name);
}
}
$this->name = $name ?: null;
return $this;
}
/**
* Возвращает установленный телефон
*
* @return string|null
*/
public function getPhone(): ?string
{
return $this->phone;
}
/**
* Устанавливает телефон
*
* @param string|null $phone Номер телефона
* @return $this
* @throws InvalidPhoneException
*/
public function setPhone(?string $phone): self
{
if (is_string($phone)) {
$phone = preg_replace('/\D/', '', trim($phone));
if (preg_match(Constraints::PATTERN_PHONE, $phone) !== 1) {
throw new InvalidPhoneException($phone);
}
}
$this->phone = empty($phone) ? null : "+$phone";
return $this;
}
/**
* @inheritDoc
*/
#[Pure]
public function jsonSerialize(): array
{
$json = [];
!is_null($this->getName()) && $json['name'] = $this->getName();
!is_null($this->getPhone()) && $json['phone'] = $this->getPhone();
!is_null($this->getEmail()) && $json['email'] = $this->getEmail();
!is_null($this->getInn()) && $json['inn'] = $this->getInn();
return $json;
}
}

View File

@ -0,0 +1,132 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\{
Constraints,
Enums\SnoType,
Traits\HasEmail,
Traits\HasInn};
use AtolOnline\Exceptions\{
InvalidEmailException,
InvalidInnLengthException,
InvalidPaymentAddressException,
TooLongEmailException,
TooLongPaymentAddressException};
use JetBrains\PhpStorm\ArrayShape;
/**
* Класс, описывающий сущность компании-продавца
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
final class Company extends Entity
{
use HasEmail;
use HasInn;
/**
* Конструктор
*
* @param string $inn ИНН (1018)
* @param SnoType $sno Система налогообложения продавца (1055)
* @param string $paymentAddress Место расчётов (адрес интернет-магазина) (1187)
* @param string $email Почта (1117)
* @throws InvalidEmailException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/
public function __construct(
string $inn,
protected SnoType $sno,
protected string $paymentAddress,
string $email,
) {
$this->setInn($inn)
->setPaymentAddress($paymentAddress)
->setEmail($email);
}
/**
* Возвращает установленный тип налогообложения
*
* @return SnoType
*/
public function getSno(): SnoType
{
return $this->sno;
}
/**
* Устанавливает тип налогообложения
*
* @param SnoType $sno
* @return $this
*/
public function setSno(SnoType $sno): self
{
$this->sno = $sno;
return $this;
}
/**
* Возвращает установленный адрес места расчётов
*
* @return string
*/
public function getPaymentAddress(): string
{
return $this->paymentAddress;
}
/**
* Устанавливает адрес места расчётов
*
* @param string $paymentAddress
* @return $this
* @throws TooLongPaymentAddressException
* @throws InvalidPaymentAddressException
*/
public function setPaymentAddress(string $paymentAddress): self
{
$paymentAddress = trim($paymentAddress);
if (empty($paymentAddress)) {
throw new InvalidPaymentAddressException();
} elseif (mb_strlen($paymentAddress) > Constraints::MAX_LENGTH_PAYMENT_ADDRESS) {
throw new TooLongPaymentAddressException($paymentAddress);
}
$this->paymentAddress = $paymentAddress;
return $this;
}
/**
* @inheritDoc
*/
#[ArrayShape([
'sno' => 'string',
'email' => 'string',
'inn' => 'string',
'payment_address' => 'string',
])]
public function jsonSerialize(): array
{
return [
'inn' => $this->getInn(),
'sno' => $this->getSno(),
'payment_address' => $this->getPaymentAddress(),
'email' => $this->getEmail(),
];
}
}

View File

@ -0,0 +1,252 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{
Api\AtolResponse,
Api\Fiscalizer,
Collections\Payments,
Collections\Vats,
Constraints};
use AtolOnline\Exceptions\{
AuthFailedException,
EmptyLoginException,
EmptyPasswordException,
InvalidEntityInCollectionException,
InvalidInnLengthException,
InvalidPaymentAddressException,
TooLongCashierException,
TooLongPaymentAddressException};
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use JetBrains\PhpStorm\ArrayShape;
/**
* Класс, описывающий документ коррекции
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35
*/
final class Correction extends Entity
{
/**
* Тип документа
*/
public const DOC_TYPE = 'correction';
/**
* @todo вынести в трейт?
* @var string|null ФИО кассира
*/
protected ?string $cashier = null;
/**
* Конструктор
*
* @param Company $company Продавец
* @param CorrectionInfo $correctionInfo Данные коррекции
* @param Payments $payments Коллекция оплат
* @param Vats $vats Коллекция ставок НДС
* @throws InvalidEntityInCollectionException
* @throws Exception
*/
public function __construct(
protected Company $company,
protected CorrectionInfo $correctionInfo,
protected Payments $payments,
protected Vats $vats,
) {
$this->setCompany($company)->setCorrectionInfo($correctionInfo)->setPayments($payments)->setVats($vats);
}
/**
* Возвращает установленного продавца
*
* @return Company
*/
public function getCompany(): Company
{
return $this->company;
}
/**
* Устанаваливает продавца
*
* @param Company $company
* @return $this
*/
public function setCompany(Company $company): self
{
$this->company = $company;
return $this;
}
/**
* Возвращает установленного кассира
*
* @return string|null
*/
public function getCashier(): ?string
{
return $this->cashier;
}
/**
* Устанаваливает кассира
*
* @param string|null $cashier
* @return $this
* @throws TooLongCashierException
*/
public function setCashier(?string $cashier): self
{
if (is_string($cashier)) {
$cashier = trim($cashier);
if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
throw new TooLongCashierException($cashier);
}
}
$this->cashier = $cashier ?: null;
return $this;
}
/**
* Возвращает установленные данные коррекции
*
* @return CorrectionInfo
*/
public function getCorrectionInfo(): CorrectionInfo
{
return $this->correctionInfo;
}
/**
* Устанавливает данные коррекции
*
* @param CorrectionInfo $correctionInfo
* @return Correction
*/
public function setCorrectionInfo(CorrectionInfo $correctionInfo): Correction
{
$this->correctionInfo = $correctionInfo;
return $this;
}
/**
* Возвращает установленную коллекцию оплат
*
* @return Payments
*/
public function getPayments(): Payments
{
return $this->payments;
}
/**
* Устанаваливает коллекцию оплат
*
* @param Payments $payments
* @return $this
* @throws InvalidEntityInCollectionException
*/
public function setPayments(Payments $payments): self
{
$payments->checkCount()->checkItemsClasses();
$this->payments = $payments;
return $this;
}
/**
* Возвращает установленную коллекцию ставок НДС
*
* @return Vats
*/
public function getVats(): Vats
{
return $this->vats ?? new Vats();
}
/**
* Устанаваливает коллекцию ставок НДС
*
* @param Vats $vats
* @return $this
* @throws Exception
*/
public function setVats(Vats $vats): self
{
$vats->checkCount()->checkItemsClasses();
$this->vats = $vats;
return $this;
}
/**
* Регистрирует коррекцию прихода по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sellCorrect(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->sellCorrect($this, $external_id);
}
/**
* Регистрирует коррекцию расхода по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buyCorrect(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->buyCorrect($this, $external_id);
}
/**
* @inheritDoc
* @throws InvalidEntityInCollectionException
*/
#[ArrayShape([
'company' => '\AtolOnline\Entities\Company',
'correction_info' => '\AtolOnline\Entities\CorrectionInfo',
'payments' => 'array',
'vats' => '\AtolOnline\Collections\Vats|null',
'cashier' => 'null|string',
])]
public function jsonSerialize(): array
{
$json = [
'company' => $this->getCompany(),
'correction_info' => $this->getCorrectionInfo(),
'payments' => $this->getPayments()->jsonSerialize(),
'vats' => $this->getVats(),
];
!is_null($this->getCashier()) && $json['cashier'] = $this->getCashier();
return $json;
}
}

View File

@ -0,0 +1,154 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Constraints;
use AtolOnline\Enums\CorrectionType;
use AtolOnline\Exceptions\{
EmptyCorrectionNumberException,
InvalidCorrectionDateException,};
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use JetBrains\PhpStorm\{
ArrayShape};
/**
* Класс, описывающий данные коррекции
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35
*/
final class CorrectionInfo extends Entity
{
/**
* @var DateTimeImmutable Дата документа основания для коррекции (1178)
*/
protected DateTimeImmutable $date;
/**
* Конструктор
*
* @param CorrectionType $type Тип коррекции (1173)
* @param DateTimeInterface|string $date Дата документа основания для коррекции (1178)
* @param string $number Номер документа основания для коррекции (1179)
* @throws InvalidCorrectionDateException
* @throws EmptyCorrectionNumberException
*/
public function __construct(
protected CorrectionType $type,
DateTimeInterface | string $date,
protected string $number,
) {
$this->setType($type)
->setDate($date)
->setNumber($number);
}
/**
* Возвращает тип коррекции
*
* @return CorrectionType|null
*/
public function getType(): ?CorrectionType
{
return $this->type;
}
/**
* Устанавливает тип коррекции
*
* @param CorrectionType $type
* @return $this
*/
public function setType(CorrectionType $type): self
{
$this->type = $type;
return $this;
}
/**
* Возвращает дату документа основания для коррекции
*
* @return DateTimeImmutable
*/
public function getDate(): DateTimeImmutable
{
return $this->date;
}
/**
* Устанавливает дату документа основания для коррекции
*
* @param DateTimeInterface|string $date Строковая дата в формате d.m.Y либо объект DateTime с датой
* @return $this
* @throws InvalidCorrectionDateException
*/
public function setDate(DateTimeInterface | string $date): self
{
try {
if (is_string($date)) {
$this->date = new DateTimeImmutable(trim($date));
} elseif ($date instanceof DateTime) {
$this->date = DateTimeImmutable::createFromMutable($date);
} elseif ($date instanceof DateTimeImmutable) {
$this->date = $date;
}
} catch (Exception $e) {
throw new InvalidCorrectionDateException($date, $e->getMessage());
}
return $this;
}
/**
* Возвращает установленный номер документа основания для коррекции
*
* @return string|null
*/
public function getNumber(): ?string
{
return $this->number;
}
/**
* Устанавливает номер документа основания для коррекции
*
* @param string $number
* @return $this
* @throws EmptyCorrectionNumberException
*/
public function setNumber(string $number): self
{
$number = trim($number);
empty($number) && throw new EmptyCorrectionNumberException();
$this->number = $number;
return $this;
}
/**
* @inheritDoc
*/
#[ArrayShape([
'type' => 'string',
'base_date' => 'string',
'base_number' => 'string',
])]
public function jsonSerialize(): array
{
return [
'type' => $this->getType(),
'base_date' => $this->getDate()->format(Constraints::CORRECTION_DATE_FORMAT),
'base_number' => $this->getNumber(),
];
}
}

View File

@ -0,0 +1,94 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
/** @noinspection PhpMultipleClassDeclarationsInspection */
declare(strict_types=1);
namespace AtolOnline\Entities;
use ArrayAccess;
use BadMethodCallException;
use Illuminate\Contracts\Support\Arrayable;
use JetBrains\PhpStorm\ArrayShape;
use JsonSerializable;
use Stringable;
/**
* Абстрактное описание любой сущности, представляемой как json
*/
abstract class Entity implements JsonSerializable, Stringable, Arrayable, ArrayAccess
{
/**
* @inheritDoc
*/
abstract public function jsonSerialize(): array;
/**
* @inheritDoc
*/
#[ArrayShape([
'company' => "\AtolOnline\Entities\Company",
'correction_info' => "\AtolOnline\Entities\CorrectionInfo",
'payments' => "array",
'vats' => "\AtolOnline\Collections\Vats|null",
'cashier' => "null|string",
])]
public function toArray()
{
return $this->jsonSerialize();
}
/**
* Возвращает строковое представление json-структуры объекта
*
* @return false|string
*/
public function __toString()
{
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
}
/**
* @inheritDoc
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->toArray()[$offset]);
}
/**
* @inheritDoc
*/
public function offsetGet(mixed $offset): mixed
{
return $this->toArray()[$offset];
}
/**
* @inheritDoc
*/
public function offsetSet(mixed $offset, mixed $value): void
{
throw new BadMethodCallException(
'Объект ' . static::class . ' нельзя изменять как массив. Следует использовать сеттеры.'
);
}
/**
* @inheritDoc
*/
public function offsetUnset(mixed $offset): void
{
throw new BadMethodCallException(
'Объект ' . static::class . ' нельзя изменять как массив. Следует использовать сеттеры.'
);
}
}

View File

@ -0,0 +1,559 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Constraints;
use AtolOnline\Enums\{
PaymentMethod,
PaymentObject,
VatType};
use AtolOnline\Exceptions\{
EmptyItemNameException,
InvalidDeclarationNumberException,
InvalidOKSMCodeException,
NegativeItemExciseException,
NegativeItemPriceException,
NegativeItemQuantityException,
TooHighItemPriceException,
TooHighItemQuantityException,
TooHighItemSumException,
TooLongItemCodeException,
TooLongItemNameException,
TooLongMeasurementUnitException,
TooLongUserdataException,
TooManyException};
/**
* Предмет расчёта (товар, услуга)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21-30
*/
final class Item extends Entity
{
/**
* @var string|null Единица измерения (1197)
*/
protected ?string $measurement_unit = null;
/**
* @var string|null Код товара (1162)
*/
protected ?string $code = null;
/**
* @var string|null Код товара (1162) в форматированной шестнадцатиричной форме
*/
protected ?string $codeHex = null;
/**
* @var PaymentMethod|null Признак способа расчёта (1214)
*/
protected ?PaymentMethod $paymentMethod = null;
/**
* @var PaymentObject|null Признак предмета расчёта (1212)
*/
protected ?PaymentObject $paymentObject = null;
/**
* @var string|null Номер таможенной декларации (1321)
*/
protected ?string $declarationNumber = null;
/**
* @var Vat|null Ставка НДС
*/
protected ?Vat $vat = null;
/**
* @var AgentInfo|null Атрибуты агента
*/
protected ?AgentInfo $agentInfo = null;
/**
* @var Supplier|null Атрибуты поставшика
*/
protected ?Supplier $supplier = null;
/**
* @var string|null Дополнительный реквизит (1191)
*/
protected ?string $userData = null;
/**
* @var float|null Сумма акциза, включенная в стоимость (1229)
*/
protected ?float $excise = null;
/**
* @var string|null Цифровой код страны происхождения товара (1230)
*/
protected ?string $countryCode = null;
/**
* Конструктор
*
* @param string|null $name Наименование (1030)
* @param float|null $price Цена в рублях (с учётом скидок и наценок) (1079)
* @param float|null $quantity Количество/вес (1023)
* @throws TooLongItemNameException
* @throws TooHighItemPriceException
* @throws TooManyException
* @throws NegativeItemPriceException
* @throws EmptyItemNameException
* @throws NegativeItemQuantityException
*/
public function __construct(
protected ?string $name = null,
protected ?float $price = null,
protected ?float $quantity = null,
) {
!is_null($name) && $this->setName($name);
!is_null($price) && $this->setPrice($price);
!is_null($quantity) && $this->setQuantity($quantity);
}
/**
* Возвращает наименование
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Устаналивает наименование
*
* @param string $name Наименование
* @return $this
* @throws TooLongItemNameException
* @throws EmptyItemNameException
*/
public function setName(string $name): self
{
if (mb_strlen($name = trim($name)) > Constraints::MAX_LENGTH_ITEM_NAME) {
throw new TooLongItemNameException($name);
}
empty($name) && throw new EmptyItemNameException();
$this->name = $name;
return $this;
}
/**
* Возвращает цену в рублях
*
* @return float
*/
public function getPrice(): float
{
return $this->price;
}
/**
* Устанавливает цену в рублях
*
* @param float $price
* @return $this
* @throws NegativeItemPriceException
* @throws TooHighItemPriceException
* @throws TooHighItemSumException
*/
public function setPrice(float $price): self
{
$price = round($price, 2);
$price > Constraints::MAX_COUNT_ITEM_PRICE && throw new TooHighItemPriceException($this->getName(), $price);
$price < 0 && throw new NegativeItemPriceException($this->getName(), $price);
$this->price = $price;
$this->getVat()?->setSum($this->getSum());
return $this;
}
/**
* Возвращает количество
*
* @return float
*/
public function getQuantity(): float
{
return $this->quantity;
}
/**
* Устанавливает количество
*
* @param float $quantity Количество
* @return $this
* @throws TooHighItemQuantityException
* @throws NegativeItemQuantityException
* @throws TooHighItemSumException
*/
public function setQuantity(float $quantity): self
{
$quantity = round($quantity, 3);
if ($quantity > Constraints::MAX_COUNT_ITEM_QUANTITY) {
throw new TooHighItemQuantityException($this->getName(), $quantity);
}
$quantity < 0 && throw new NegativeItemQuantityException($this->getName(), $quantity);
$this->quantity = $quantity;
$this->getVat()?->setSum($this->getSum());
return $this;
}
/**
* Возвращает стоимость (цена * количество + акциз)
*
* @return float
* @throws TooHighItemSumException
*/
public function getSum(): float
{
$sum = $this->getPrice() * $this->getQuantity() + (float)$this->getExcise();
if ($sum > Constraints::MAX_COUNT_ITEM_SUM) {
throw new TooHighItemSumException($this->getName(), $sum);
}
return $sum;
}
/**
* Возвращает заданную единицу измерения количества
*
* @return string|null
*/
public function getMeasurementUnit(): ?string
{
return $this->measurement_unit;
}
/**
* Устанавливает единицу измерения количества
*
* @param string|null $measurement_unit
* @return $this
* @throws TooLongMeasurementUnitException
*/
public function setMeasurementUnit(?string $measurement_unit): self
{
$measurement_unit = trim((string)$measurement_unit);
if (mb_strlen($measurement_unit) > Constraints::MAX_LENGTH_MEASUREMENT_UNIT) {
throw new TooLongMeasurementUnitException($measurement_unit);
}
$this->measurement_unit = $measurement_unit ?: null;
return $this;
}
/**
* Возвращает установленный код товара
*
* @return string|null
*/
public function getCode(): ?string
{
return $this->code;
}
/**
* Возвращает шестнадцатиричное представление кода товара
*
* @return string|null
*/
public function getCodeHex(): ?string
{
return $this->codeHex;
}
/**
* Устанавливает код товара
*
* @param string|null $code
* @return Item
* @throws TooLongItemCodeException
*/
public function setCode(?string $code): self
{
$hex_string = null;
$code = trim((string)$code);
if (mb_strlen($code) > Constraints::MAX_LENGTH_ITEM_CODE) {
throw new TooLongItemCodeException($this->getName(), $code);
}
if (!empty($code)) {
$hex = bin2hex($code);
$hex_string = trim(preg_replace('/([\dA-Fa-f]{2})/', '$1 ', $hex));
}
$this->code = $code ?: null;
$this->codeHex = $hex_string ?: null;
return $this;
}
/**
* Возвращает признак способа оплаты
*
* @return PaymentMethod|null
*/
public function getPaymentMethod(): ?PaymentMethod
{
return $this->paymentMethod;
}
/**
* Устанавливает признак способа оплаты
*
* @param PaymentMethod|null $paymentMethod Признак способа оплаты
* @return $this
*/
public function setPaymentMethod(?PaymentMethod $paymentMethod): self
{
$this->paymentMethod = $paymentMethod;
return $this;
}
/**
* Возвращает признак предмета расчёта
*
* @return PaymentObject|null
*/
public function getPaymentObject(): ?PaymentObject
{
return $this->paymentObject;
}
/**
* Устанавливает признак предмета расчёта
*
* @param PaymentObject|null $paymentObject Признак предмета расчёта
* @return $this
*/
public function setPaymentObject(?PaymentObject $paymentObject): self
{
$this->paymentObject = $paymentObject;
return $this;
}
/**
* Возвращает ставку НДС
*
* @return Vat|null
*/
public function getVat(): ?Vat
{
return $this->vat;
}
/**
* Устанавливает ставку НДС
*
* @param Vat | VatType | null $vat Объект ставки, одно из значений VatTypes или null для удаления ставки
* @return $this
* @throws TooHighItemSumException
*/
public function setVat(Vat | VatType | null $vat): self
{
if (is_null($vat)) {
$this->vat = null;
} elseif ($vat instanceof Vat) {
$vat->setSum($this->getSum());
$this->vat = $vat;
} else {
$this->vat = new Vat($vat, $this->getSum());
}
return $this;
}
/**
* Возвращает установленный объект атрибутов агента
*
* @return AgentInfo|null
*/
public function getAgentInfo(): ?AgentInfo
{
return $this->agentInfo;
}
/**
* Устанавливает атрибуты агента
*
* @param AgentInfo|null $agentInfo
* @return Item
*/
public function setAgentInfo(?AgentInfo $agentInfo): self
{
$this->agentInfo = $agentInfo;
return $this;
}
/**
* Возвращает установленного поставщика
*
* @return Supplier|null
*/
public function getSupplier(): ?Supplier
{
return $this->supplier;
}
/**
* Устанавливает поставщика
*
* @param Supplier|null $supplier
* @return Item
*/
public function setSupplier(?Supplier $supplier): self
{
$this->supplier = $supplier;
return $this;
}
/**
* Возвращает дополнительный реквизит
*
* @return string|null
*/
public function getUserData(): ?string
{
return $this->userData;
}
/**
* Устанавливает дополнительный реквизит
*
* @param string|null $userData Дополнительный реквизит
* @return $this
* @throws TooLongUserdataException
*/
public function setUserData(?string $userData): self
{
$userData = trim((string)$userData);
if (mb_strlen($userData) > Constraints::MAX_LENGTH_USER_DATA) {
throw new TooLongUserdataException($userData);
}
$this->userData = $userData ?: null;
return $this;
}
/**
* Возвращает установленную сумму акциза
*
* @return float|null
*/
public function getExcise(): ?float
{
return $this->excise;
}
/**
* Устанавливает сумму акциза
*
* @param float|null $excise
* @return Item
* @throws NegativeItemExciseException
* @throws TooHighItemSumException
*/
public function setExcise(?float $excise): self
{
if ($excise < 0) {
throw new NegativeItemExciseException($this->getName(), $excise);
}
$this->excise = $excise;
$this->getVat()?->setSum($this->getSum());
return $this;
}
/**
* Возвращает установленный код страны происхождения товара
*
* @return string|null
* @see https://ru.wikipedia.org/wiki/Общероссийский_классификатор_стран_мира
* @see https://classifikators.ru/oksm
*/
public function getCountryCode(): ?string
{
return $this->countryCode;
}
/**
* Устанавливает код страны происхождения товара
*
* @param string|null $countryCode
* @return Item
* @throws InvalidOKSMCodeException
* @see https://classifikators.ru/oksm
* @see https://ru.wikipedia.org/wiki/Общероссийский_классификатор_стран_мира
*/
public function setCountryCode(?string $countryCode): self
{
$countryCode = trim((string)$countryCode);
if (preg_match(Constraints::PATTERN_OKSM_CODE, $countryCode) != 1) {
throw new InvalidOKSMCodeException($countryCode);
}
$this->countryCode = $countryCode ?: null;
return $this;
}
/**
* Возвращает установленный код таможенной декларации
*
* @return string|null
*/
public function getDeclarationNumber(): ?string
{
return $this->declarationNumber;
}
/**
* Устанавливает код таможенной декларации
*
* @param string|null $declarationNumber
* @return Item
* @throws InvalidDeclarationNumberException
*/
public function setDeclarationNumber(?string $declarationNumber): self
{
if (is_string($declarationNumber)) {
$declarationNumber = trim($declarationNumber);
$is_short = mb_strlen($declarationNumber) < Constraints::MIN_LENGTH_DECLARATION_NUMBER;
$is_long = mb_strlen($declarationNumber) > Constraints::MAX_LENGTH_DECLARATION_NUMBER;
if ($is_short || $is_long) {
throw new InvalidDeclarationNumberException($declarationNumber);
}
}
$this->declarationNumber = $declarationNumber;
return $this;
}
/**
* @inheritDoc
* @throws TooHighItemSumException
*/
public function jsonSerialize(): array
{
$json = [
'name' => $this->getName(),
'price' => $this->getPrice(),
'quantity' => $this->getQuantity(),
'sum' => $this->getSum(),
];
!is_null($this->getMeasurementUnit()) && $json['measurement_unit'] = $this->getMeasurementUnit();
!is_null($this->getCodeHex()) && $json['nomenclature_code'] = $this->getCodeHex();
!is_null($this->getPaymentMethod()) && $json['payment_method'] = $this->getPaymentMethod();
!is_null($this->getPaymentObject()) && $json['payment_object'] = $this->getPaymentObject();
!is_null($this->getDeclarationNumber()) && $json['declaration_number'] = $this->getDeclarationNumber();
$this->getVat()?->jsonSerialize() && $json['vat'] = $this->getVat()->jsonSerialize();
$this->getAgentInfo()?->jsonSerialize() && $json['agent_info'] = $this->getAgentInfo()->jsonSerialize();
$this->getSupplier()?->jsonSerialize() && $json['supplier_info'] = $this->getSupplier()->jsonSerialize();
!is_null($this->getUserData()) && $json['user_data'] = $this->getUserData();
!is_null($this->getExcise()) && $json['excise'] = $this->getExcise();
!is_null($this->getCountryCode()) && $json['country_code'] = $this->getCountryCode();
return $json;
}
}

View File

@ -0,0 +1,139 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\{
EmptyMonitorDataException,
NotEnoughMonitorDataException};
use DateTime;
use Exception;
/**
* Класс сущности ККТ, получаемой от монитора
*
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
* @property-read string|null serialNumber Заводской номер ККТ
* @property-read string|null registrationNumber Регистрационный номер машины (РНМ)
* @property-read string|null deviceNumber Номер автоматического устройства (внутренний идентификатор устройства)
* @property-read DateTime|string|null fiscalizationDate Дата активации (фискализации) ФН с указанием таймзоны
* @property-read DateTime|string|null fiscalStorageExpiration Дата замены ФН (Срок действия ФН), с указанием таймзоны
* @property-read int|null signedDocuments Количество подписанных документов в ФН
* @property-read float|null fiscalStoragePercentageUse Наполненость ФН в %
* @property-read string|null fiscalStorageINN ИНН компании (указанный в ФН)
* @property-read string|null fiscalStorageSerialNumber Заводской (серийный) номер ФН
* @property-read string|null fiscalStoragePaymentAddress Адрес расчёта, указанный в ФН
* @property-read string|null groupCode Код группы кассы
* @property-read DateTime|string|null timestamp Время и дата формирования данных, UTC
* @property-read bool|null isShiftOpened Признак открыта смена (true) или закрыта (false)
* @property-read int|null shiftNumber Номер смены (или "Номер закрытой смены", когда смена закрыта)
* @property-read int|null shiftReceipt Номер документа за смену (или "Кол-во чеков закрытой смены", когда смена
* закрыта)
* @property-read int|null unsentDocs Количество неотправленных документов. Указывается, если значение отлично от 0.
* @property-read DateTime|string|null firstUnsetDocTimestamp Дата первого неотправленного документа. Указывается, если
* есть неотправленные документы.
* @property-read int|null networkErrorCode Код ошибки сети
*/
final class Kkt extends Entity
{
/**
* Сопоставление кодов сетевых ошибок ККТ с их описаниями
*/
public const ERROR_CODES = [
0 => 'Нет ошибок',
1 => 'Отсутствует физический канал связи',
2 => 'Ошибка сетевых настроек или нет соединения с сервером ОФД',
3 => 'Разрыв соединения при передаче документа на сервер',
4 => 'Некорректный заголовок сессионного пакета',
5 => 'Превышен таймаут ожидания квитанции',
6 => 'Разрыв соединения при приёме квитанции',
7 => 'Превышен таймаут передачи документа на сервер',
8 => 'ОФД-процесс не иницилизирован',
];
/**
* @var string[] Список обязательных атрибутов
*/
private array $properties = [
'serialNumber',
'registrationNumber',
'deviceNumber',
'fiscalizationDate',
'fiscalStorageExpiration',
'signedDocuments',
'fiscalStoragePercentageUse',
'fiscalStorageINN',
'fiscalStorageSerialNumber',
'fiscalStoragePaymentAddress',
'groupCode',
'timestamp',
'isShiftOpened',
'shiftNumber',
'shiftReceipt',
//'unsentDocs',
//'firstUnsetDocTimestamp',
'networkErrorCode',
];
/**
* @var string[] Массив атрибутов, которые кастуются к DateTime
*/
private array $timestamps = [
'fiscalizationDate',
'fiscalStorageExpiration',
'firstUnsetDocTimestamp',
'timestamp',
];
/**
* Конструктор
*
* @throws EmptyMonitorDataException
* @throws NotEnoughMonitorDataException
*/
public function __construct(protected object $data)
{
if (empty((array)$data)) {
throw new EmptyMonitorDataException();
}
$diff = array_diff($this->properties, array_keys((array)$data));
if (count($diff) !== 0) {
throw new NotEnoughMonitorDataException($diff);
}
}
/**
* Эмулирует обращение к атрибутам
*
* @param string $name
* @return null
* @throws Exception
*/
public function __get(string $name)
{
if (empty($this->data?->$name)) {
return null;
}
if (in_array($name, $this->timestamps)) {
return new DateTime($this->data->$name);
}
return $this->data->$name;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
return (array)$this->data;
}
}

View File

@ -0,0 +1,133 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\{
InvalidInnLengthException,
InvalidPhoneException};
use AtolOnline\Traits\{
HasInn,
HasPhones};
use Illuminate\Support\Collection;
/**
* Класс, описывающий оператора перевода
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 28
*/
final class MoneyTransferOperator extends Entity
{
use HasInn;
use HasPhones;
/**
* @var string|null Наименование (1026)
*/
protected ?string $name = null;
/**
* @var string|null ИНН (1016)
*/
protected ?string $inn = null;
/**
* @var string|null Адрес (1005)
*/
protected ?string $address = null;
/**
* @var Collection Телефоны (1075)
*/
protected Collection $phones;
/**
* Конструктор
*
* @param string|null $name Наименование поставщика (1225)
* @param string|null $inn ИНН (1226)
* @param string|null $address Адрес (1005)
* @param array|Collection|null $phones Телефоны поставщика (1171)
* @throws InvalidInnLengthException
* @throws InvalidPhoneException
*/
public function __construct(
?string $name = null,
?string $inn = null,
?string $address = null,
array | Collection | null $phones = null,
) {
$this->setName($name);
$this->setInn($inn);
$this->setAddress($address);
$this->setPhones($phones);
}
/**
* Возвращает установленное наименование поставщика
*
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Устанавливает наименование поставщика
*
* @param string|null $name
* @return $this
*/
public function setName(?string $name): self
{
// критерии валидной строки не описаны ни в схеме, ни в документации
$this->name = trim((string)$name) ?: null;
return $this;
}
/**
* Возвращает установленный адрес места расчётов
*
* @return string|null
*/
public function getAddress(): ?string
{
return $this->address;
}
/**
* Устанавливает адрес места расчётов
*
* @param string|null $address
* @return $this
*/
public function setAddress(?string $address): self
{
// критерии валидной строки не описаны ни в схеме, ни в документации
$this->address = trim((string)$address) ?: null;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$json = [];
$this->getName() && $json['name'] = $this->getName();
$this->getInn() && $json['inn'] = $this->getInn();
$this->getAddress() && $json['address'] = $this->getAddress();
!$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
return $json;
}
}

View File

@ -0,0 +1,91 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Constraints;
use AtolOnline\Exceptions\{
InvalidPhoneException,
TooLongPayingAgentOperationException};
use AtolOnline\Traits\HasPhones;
use Illuminate\Support\Collection;
/**
* Класс, описывающий платёжного агента
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 19
*/
final class PayingAgent extends Entity
{
use HasPhones;
/**
* @var string|null Наименование операции (1044)
*/
protected ?string $operation = null;
/**
* Конструктор
*
* @param string|null $operation Наименование операции (1044)
* @param array|Collection|null $phones Телефоны платёжного агента (1073)
* @throws TooLongPayingAgentOperationException
* @throws InvalidPhoneException
*/
public function __construct(
?string $operation = null,
array | Collection | null $phones = null,
) {
!is_null($operation) && $this->setOperation($operation);
$this->setPhones($phones);
}
/**
* Устанавливает операцию
*
* @param string|null $operation
* @return $this
* @throws TooLongPayingAgentOperationException
*/
public function setOperation(?string $operation): self
{
if (!is_null($operation)) {
$operation = trim($operation);
if (mb_strlen($operation) > Constraints::MAX_LENGTH_PAYING_AGENT_OPERATION) {
throw new TooLongPayingAgentOperationException($operation);
}
}
$this->operation = $operation ?: null;
return $this;
}
/**
* Вoзвращает установленную операцию
*
* @return string|null
*/
public function getOperation(): ?string
{
return $this->operation;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$json = [];
$this->getOperation() && $json['operation'] = $this->getOperation();
!$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
return $json;
}
}

View File

@ -0,0 +1,108 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\{
Constraints,
Enums\PaymentType,};
use AtolOnline\Exceptions\{
NegativePaymentSumException,
TooHighPaymentSumException,};
use JetBrains\PhpStorm\{
ArrayShape,
Pure};
/**
* Класс, описывающий оплату
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30
*/
final class Payment extends Entity
{
/**
* Конструктор
*
* @param PaymentType $type Тип оплаты
* @param float $sum Сумма оплаты (1031, 1081, 1215, 1216, 1217)
* @throws NegativePaymentSumException
* @throws TooHighPaymentSumException
*/
public function __construct(
protected PaymentType $type,
protected float $sum,
) {
$this->setType($type)->setSum($sum);
}
/**
* Возвращает установленный тип оплаты
*
* @return PaymentType
*/
public function getType(): PaymentType
{
return $this->type;
}
/**
* Устанавливает тип оплаты
*
* @param PaymentType $type
* @return $this
*/
public function setType(PaymentType $type): self
{
$this->type = $type;
return $this;
}
/**
* Возвращает установленную сумму оплаты
*
* @return float
*/
public function getSum(): float
{
return $this->sum;
}
/**
* Устанавливает сумму оплаты
*
* @param float $sum
* @return $this
* @throws TooHighPaymentSumException
* @throws NegativePaymentSumException
*/
public function setSum(float $sum): self
{
$sum = round($sum, 2);
$sum > Constraints::MAX_COUNT_PAYMENT_SUM && throw new TooHighPaymentSumException($sum);
$sum < 0 && throw new NegativePaymentSumException($sum);
$this->sum = $sum;
return $this;
}
/**
* @inheritDoc
*/
#[Pure]
#[ArrayShape(['type' => 'int', 'sum' => 'float'])]
public function jsonSerialize(): array
{
return [
'type' => $this->getType(),
'sum' => $this->getSum(),
];
}
}

View File

@ -0,0 +1,481 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Api\AtolResponse;
use AtolOnline\Api\Fiscalizer;
use AtolOnline\Collections\Items;
use AtolOnline\Collections\Payments;
use AtolOnline\Collections\Vats;
use AtolOnline\Constraints;
use AtolOnline\Exceptions\AuthFailedException;
use AtolOnline\Exceptions\EmptyItemsException;
use AtolOnline\Exceptions\EmptyLoginException;
use AtolOnline\Exceptions\EmptyPasswordException;
use AtolOnline\Exceptions\InvalidEntityInCollectionException;
use AtolOnline\Exceptions\InvalidInnLengthException;
use AtolOnline\Exceptions\InvalidPaymentAddressException;
use AtolOnline\Exceptions\TooLongAddCheckPropException;
use AtolOnline\Exceptions\TooLongCashierException;
use AtolOnline\Exceptions\TooLongPaymentAddressException;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
/**
* Класс, описывающий документ прихода, расхода, возврата прихода, возврата расхода
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
final class Receipt extends Entity
{
/**
* Тип документа
*/
public const DOC_TYPE = 'receipt';
/**
* @var Client Покупатель
*/
protected Client $client;
/**
* @todo вынести в трейт?
* @var Company Продавец
*/
protected Company $company;
/**
* @var AgentInfo|null Агент
*/
protected ?AgentInfo $agent_info = null;
/**
* @var Supplier|null Поставщик
*/
protected ?Supplier $supplier = null;
/**
* @var Items Коллекция предметов расчёта
*/
protected Items $items;
/**
* @todo вынести в трейт?
* @var Payments Коллекция оплат
*/
protected Payments $payments;
/**
* @var Vats|null Коллекция ставок НДС
*/
protected ?Vats $vats = null;
/**
* @var float Итоговая сумма чека
*/
protected float $total = 0;
/**
* @todo вынести в трейт?
* @var string|null ФИО кассира
*/
protected ?string $cashier = null;
/**
* @var string|null Дополнительный реквизит
*/
protected ?string $add_check_props = null;
/**
* @var AdditionalUserProps|null Дополнительный реквизит пользователя
*/
protected ?AdditionalUserProps $add_user_props = null;
/**
* Конструктор
*
* @param Client $client
* @param Company $company
* @param Items $items
* @param Payments $payments
* @throws EmptyItemsException
* @throws InvalidEntityInCollectionException
*/
public function __construct(Client $client, Company $company, Items $items, Payments $payments)
{
$this->setClient($client)->setCompany($company)->setItems($items)->setPayments($payments);
}
/**
* Возвращает установленного покупателя
*
* @return Client
*/
public function getClient(): Client
{
return $this->client;
}
/**
* Устанаваливает покупателя
*
* @param Client $client
* @return $this
*/
public function setClient(Client $client): self
{
$this->client = $client;
return $this;
}
/**
* Возвращает установленного продавца
*
* @return Company
*/
public function getCompany(): Company
{
return $this->company;
}
/**
* Устанаваливает продавца
*
* @param Company $company
* @return $this
*/
public function setCompany(Company $company): self
{
$this->company = $company;
return $this;
}
/**
* Возвращает установленного агента
*
* @return AgentInfo|null
*/
public function getAgentInfo(): ?AgentInfo
{
return $this->agent_info;
}
/**
* Устанаваливает агента
*
* @param AgentInfo|null $agent_info
* @return $this
*/
public function setAgentInfo(?AgentInfo $agent_info): self
{
$this->agent_info = $agent_info;
return $this;
}
/**
* Поставщика
*
* @return Supplier|null
*/
public function getSupplier(): ?Supplier
{
return $this->supplier;
}
/**
* Поставщика
*
* @param Supplier|null $supplier
* @return $this
*/
public function setSupplier(?Supplier $supplier): self
{
$this->supplier = $supplier;
return $this;
}
/**
* Возвращает установленную коллекцию предметов расчёта
*
* @return Items
*/
public function getItems(): Items
{
return $this->items ?? new Items();
}
/**
* Устанаваливает коллекцию предметов расчёта
*
* @param Items $items
* @return $this
* @throws InvalidEntityInCollectionException
* @throws Exception
* @throws EmptyItemsException
*/
public function setItems(Items $items): self
{
$items->checkCount();
$items->checkItemsClasses();
$this->items = $items;
$this->getItems()->each(fn ($item) => $this->total += $item->getSum());
$this->total = round($this->total, 2);
return $this;
}
/**
* Возвращает установленную коллекцию оплат
*
* @return Payments
*/
public function getPayments(): Payments
{
return $this->payments;
}
/**
* Устанаваливает коллекцию оплат
*
* @param Payments $payments
* @return $this
* @throws InvalidEntityInCollectionException
*/
public function setPayments(Payments $payments): self
{
$payments->checkCount();
$payments->checkItemsClasses();
$this->payments = $payments;
return $this;
}
/**
* Возвращает установленную коллекцию ставок НДС
*
* @return Vats|null
*/
public function getVats(): ?Vats
{
return $this->vats ?? new Vats();
}
/**
* Устанаваливает коллекцию ставок НДС
*
* @param Vats|null $vats
* @return $this
* @throws Exception
*/
public function setVats(?Vats $vats): self
{
$vats->checkCount();
$vats->checkItemsClasses();
$this->vats = $vats;
/** @var Vat $vat */
$this->getVats()->each(fn ($vat) => $vat->setSum($this->getTotal()));
return $this;
}
/**
* Возвращает полную сумму чека
*
* @return float
*/
public function getTotal(): float
{
return $this->total;
}
/**
* Возвращает установленного кассира
*
* @return string|null
*/
public function getCashier(): ?string
{
return $this->cashier;
}
/**
* Устанаваливает кассира
*
* @param string|null $cashier
* @return $this
* @throws TooLongCashierException
*/
public function setCashier(?string $cashier): self
{
if (is_string($cashier)) {
$cashier = trim($cashier);
if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
throw new TooLongCashierException($cashier);
}
}
$this->cashier = $cashier ?: null;
return $this;
}
/**
* Возвращает установленный дополнительный реквизит чека
*
* @return string|null
*/
public function getAddCheckProps(): ?string
{
return $this->add_check_props;
}
/**
* Устанаваливает дополнительный реквизит чека
*
* @param string|null $add_check_props
* @return $this
* @throws TooLongAddCheckPropException
*/
public function setAddCheckProps(?string $add_check_props): self
{
if (is_string($add_check_props)) {
$add_check_props = trim($add_check_props);
if (mb_strlen($add_check_props) > Constraints::MAX_LENGTH_ADD_CHECK_PROP) {
throw new TooLongAddCheckPropException($add_check_props);
}
}
$this->add_check_props = $add_check_props ?: null;
return $this;
}
/**
* Возвращает установленный дополнительный реквизит пользователя
*
* @return AdditionalUserProps|null
*/
public function getAddUserProps(): ?AdditionalUserProps
{
return $this->add_user_props;
}
/**
* Устанаваливает дополнительный реквизит пользователя
*
* @param AdditionalUserProps|null $add_user_props
* @return $this
*/
public function setAddUserProps(?AdditionalUserProps $add_user_props): self
{
$this->add_user_props = $add_user_props;
return $this;
}
/**
* Регистрирует приход по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sell(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->sell($this, $external_id);
}
/**
* Регистрирует возврат прихода по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function sellRefund(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->sellRefund($this, $external_id);
}
/**
* Регистрирует расход по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buy(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->buy($this, $external_id);
}
/**
* Регистрирует возврат расхода по текущему документу
*
* @param Fiscalizer $fiscalizer Объект фискализатора
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
* @return AtolResponse|null
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidEntityInCollectionException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongPaymentAddressException
*/
public function buyRefund(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
{
return $fiscalizer->buyRefund($this, $external_id);
}
/**
* Возвращает массив для кодирования в json
*
* @throws Exception
*/
public function jsonSerialize(): array
{
$json = [
'client' => $this->getClient()->jsonSerialize(),
'company' => $this->getCompany()->jsonSerialize(),
'items' => $this->getItems()->jsonSerialize(),
'total' => $this->getTotal(),
'payments' => $this->getPayments()->jsonSerialize(),
];
$this->getAgentInfo()?->jsonSerialize() && $json['agent_info'] = $this->getAgentInfo()->jsonSerialize();
$this->getSupplier()?->jsonSerialize() && $json['supplier_info'] = $this->getSupplier()->jsonSerialize();
$this->getVats()?->isNotEmpty() && $json['vats'] = $this->getVats();
!is_null($this->getAddCheckProps()) && $json['additional_check_props'] = $this->getAddCheckProps();
!is_null($this->getCashier()) && $json['cashier'] = $this->getCashier();
$this->getAddUserProps()?->jsonSerialize() &&
$json['additional_user_props'] = $this->getAddUserProps()->jsonSerialize();
return $json;
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\InvalidPhoneException;
use AtolOnline\Traits\HasPhones;
use Illuminate\Support\Collection;
/**
* Класс, описывающий оператора по приёму платежей
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 19-20
*/
final class ReceivePaymentsOperator extends Entity
{
use HasPhones;
/**
* Конструктор
*
* @param array|Collection|null $phones Телефоны оператора по приёму платежей (1074)
* @throws InvalidPhoneException
*/
public function __construct(array | Collection | null $phones = null)
{
$this->setPhones($phones);
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$json = [];
!$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
return $json;
}
}

View File

@ -0,0 +1,91 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\{
InvalidInnLengthException,
InvalidPhoneException};
use AtolOnline\Traits\{
HasInn,
HasPhones};
use Illuminate\Support\Collection;
/**
* Класс, описывающий поставшика
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 29
*/
final class Supplier extends Entity
{
use HasPhones;
use HasInn;
/**
* @var string|null Наименование (1225)
*/
protected ?string $name = null;
/**
* Конструктор
*
* @param string|null $name Наименование поставщика (1225)
* @param string|null $inn ИНН (1226)
* @param array|Collection|null $phones Телефоны поставщика (1171)
* @throws InvalidInnLengthException
* @throws InvalidPhoneException
*/
public function __construct(
?string $name = null,
?string $inn = null,
array | Collection | null $phones = null,
) {
!is_null($name) && $this->setName($name);
!is_null($inn) && $this->setInn($inn);
$this->setPhones($phones);
}
/**
* Возвращает установленное наименование поставщика
*
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Устанавливает наименование поставщика
*
* @param string|null $name
* @return Supplier
*/
public function setName(?string $name): self
{
// критерии к длине строки не описаны ни в схеме, ни в документации
$this->name = trim($name) ?: null;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$json = [];
$this->getName() && $json['name'] = $this->getName();
$this->getInn() && $json['inn'] = $this->getInn();
!$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
return $json;
}
}

View File

@ -0,0 +1,135 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Entities;
use AtolOnline\Enums\VatType;
use AtolOnline\Helpers;
use JetBrains\PhpStorm\{
ArrayShape,
Pure};
/**
* Класс, описывающий ставку НДС
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 25, 31
*/
final class Vat extends Entity
{
/**
* Конструктор
*
* @param VatType $type Тип ставки НДС (1199, 1105, 1104, 1103, 1102, 1107, 1106)
* @param float $sum Исходная сумма в рублях, от которой нужно расчитать размер НДС
*/
public function __construct(
protected VatType $type,
protected float $sum,
) {
$this->setType($type)->setSum($sum);
}
/**
* Устанавливает тип ставки НДС
* Автоматически пересчитывает итоговый размер НДС от исходной суммы.
*
* @param VatType $type Тип ставки НДС
* @return $this
*/
public function setType(VatType $type): self
{
$this->type = $type;
return $this;
}
/**
* Возвращает тип ставки НДС
*
* @return VatType
*/
public function getType(): VatType
{
return $this->type;
}
/**
* Возвращает исходную сумму, от которой расчитывается размер налога
*
* @return float
*/
public function getSum(): float
{
return $this->sum;
}
/**
* Устанавливает исходную сумму, от которой будет расчитываться итоговый размер НДС.
* Автоматически пересчитывает итоговый размер НДС от исходной суммы.
*
* @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС
* @return $this
*/
public function setSum(float $rubles): self
{
$this->sum = round($rubles, 2);
return $this;
}
/**
* Возвращает расчитанный итоговый размер ставки НДС в рублях
*
* @return float
* @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
*/
#[Pure]
public function getCalculated(): float
{
return Helpers::toRub(
match ($this->getType()) {
VatType::VAT10 => Helpers::toKop($this->sum) * 10 / 100,
VatType::VAT18 => Helpers::toKop($this->sum) * 18 / 100,
VatType::VAT20 => Helpers::toKop($this->sum) * 20 / 100,
VatType::VAT110 => Helpers::toKop($this->sum) * 10 / 110,
VatType::VAT118 => Helpers::toKop($this->sum) * 18 / 118,
VatType::VAT120 => Helpers::toKop($this->sum) * 20 / 120,
default => 0,
}
);
}
/**
* Прибавляет указанную сумму к исходной
*
* @param float $rubles
* @return $this
*/
public function addSum(float $rubles): self
{
$this->sum += $rubles;
return $this;
}
/**
* @inheritDoc
*/
#[Pure]
#[ArrayShape(['type' => 'string', 'sum' => 'float'])]
public function jsonSerialize(): array
{
return [
'type' => $this->getType(),
'sum' => $this->getCalculated(),
];
}
}

View File

@ -0,0 +1,56 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Enums;
/**
* Константы, определяющие типы агента
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 18, 26
*/
enum AgentType: string
{
/**
* Банковский платёжный агент
*/
case BANK_PAYING_AGENT = 'bank_paying_agent';
/**
* Банковский платёжный субагент
*/
case BANK_PAYING_SUBAGENT = 'bank_paying_subagent';
/**
* Платёжный агент
*/
case PAYING_AGENT = 'payingAgent';
/**
* Платёжный субагент
*/
case PAYING_SUBAGENT = 'paying_subagent';
/**
* Поверенный
*/
case ATTRONEY = 'attorney';
/**
* Комиссионер
*/
case COMMISSION_AGENT = 'commission_agent';
/**
* Другой тип агента
*/
case ANOTHER = 'another';
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,76 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types=1);
namespace AtolOnline\Enums;
/**
* Константы, определяющие виды оплат
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35
*/
enum PaymentType: int
{
/**
* Расчёт наличными
*/
case CASH = 0;
/**
* Расчёт безналичными
*/
case ELECTRON = 1;
/**
* Предварительная оплата (зачёт аванса)
*/
case PREPAID = 2;
/**
* Предварительная оплата (кредит)
*/
case CREDIT = 3;
/**
* Иная форма оплаты (встречное предоставление)
*/
case OTHER = 4;
/**
* Расширенный типы оплаты (5)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
case ADD_5 = 5;
/**
* Расширенный типы оплаты (6)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
case ADD_6 = 6;
/**
* Расширенный типы оплаты (7)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
case ADD_7 = 7;
/**
* Расширенный типы оплаты (8)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
case ADD_8 = 8;
/**
* Расширенный типы оплаты (9)
* Для каждого фискального типа оплаты можно указать расширенный тип оплаты
*/
case ADD_9 = 9;
}

View File

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

Some files were not shown because too many files have changed in this diff Show More