geekcodev / laravel-commercejson
CommerceJSON v1.0.8 integration package for Laravel 12
Requires
- php: ^8.4
- guzzlehttp/guzzle: ^7.9
- laravel/framework: ^v13.0
- spatie/laravel-data: ^4.0
- symfony/yaml: ^8.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^11.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^11.0
README
Пакет для интеграции с CommerceJSON API v1.0.8 в Laravel 12+. Предназначен для обмена данными с системами 1С и другими ERP-системами, поддерживающими стандарт CommerceJSON.
Оглавление
- Возможности
- Установка
- Использование
- Архитектура
- Тестирование
- Документация
- Конфигурация
- Синхронизация
- Обработка ошибок
- Очереди заданий
- Чеклист для production
- История версий
- Лицензия
- Поддержка
- Ссылки
Возможности
- HTTP-клиент с поддержкой повторных запросов, идемпотентности и пагинации
- 23 модели Eloquent с отношениями и приведением типов
- 24 миграции базы данных с оптимизированными индексами
- 49 Data-классов для валидации данных
- 6 сервисов для бизнес-логики
- 7 очередей заданий для асинхронных операций
- 7 Artisan-команд для работы через CLI
- 11 событий для интеграции
- Фабрики и сидеры для тестирования
- Полная документация
Установка
После установки пакета и публикации конфига, маршруты автоматически регистрируются в CommerceJsonServiceProvider:
// Автоматическая регистрация в boot() методе $this->loadRoutesFrom(__DIR__.'/routes/api.php');
Готовые REST API endpoints становятся доступны по префиксу /api/commercejson:
/api/commercejson/products— товары/api/commercejson/orders— заказы/api/commercejson/categories— категории/api/commercejson/offers— предложения/api/commercejson/counterparties— контрагенты
composer require geekcodev/laravel-commercejson
Публикация конфигурации и миграций
# Публикация конфигурации php artisan vendor:publish --tag=commercejson-config # Публикация миграций php artisan vendor:publish --tag=commercejson-migrations # Запуск миграций php artisan migrate
Переменные окружения
Добавьте в файл .env:
# CommerceJSON API COMMERCEJSON_BASE_URL=https://api.your-erp.com/v1 COMMERCEJSON_AUTH_TYPE=bearer COMMERCEJSON_AUTH_TOKEN=your-api-token COMMERCEJSON_TIMEOUT=30 COMMERCEJSON_RETRY_ATTEMPTS=3 # Очереди (рекомендуется для production) COMMERCEJSON_QUEUE_ENABLED=true COMMERCEJSON_QUEUE_CONNECTION=redis # Синхронизация COMMERCEJSON_SYNC_SCHEDULE=0 * * * * COMMERCEJSON_INCREMENTAL_SYNC=true # Логирование COMMERCEJSON_LOGGING=true COMMERCEJSON_LOG_CHANNEL=stack COMMERCEJSON_LOG_REQUESTS=false
Использование
Быстрый старт
use GeekCo\CommerceJson\Facades\CommerceJson; // Получить товары $products = CommerceJson::products()->getProducts( page: 1, limit: 100, categoryId: 'uuid-here' ); // Получить заказ $order = CommerceJson::orders()->getOrder($orderId); // Создать заказ $newOrder = CommerceJson::orders()->createOrder($orderData); // Импортировать предложения (цены и остатки) CommerceJson::offers()->importOffers($offerImportData);
Два способа работы с пакетом
1. REST API (Headless CMS) — рекомендуется для frontend
Пакет предоставляет готовые REST API endpoints. Используйте HTTP запросы из вашего frontend приложения:
// Frontend (React/Vue/Next.js) или мобильное приложение const response = await fetch('https://your-app.test/api/commercejson/products'); const products = await response.json(); // Или через axios const { data } = await axios.get('/api/commercejson/products'); // Создать заказ const { data: order } = await axios.post('/api/commercejson/orders', { number: 'ORD-001', status: 'new', items: [...] });
Преимущества:
- ✅ Готовые endpoints из коробки
- ✅ Не нужно писать контроллеры
- ✅ Идеально для React/Vue/Next.js frontend
- ✅ Мобильные приложения получают доступ к API
- ✅ CQRS архитектура внутри контроллеров
2. Сервисы — для кастомной бизнес-логики
Если вам нужна кастомная логика, используйте сервисы напрямую:
use GeekCo\CommerceJson\Services\ProductService; use GeekCo\CommerceJson\Services\OrderService; use GeekCo\CommerceJson\Http\Client\HttpClientInterface; class OrderController extends Controller { public function __construct( private HttpClientInterface $http, private OrderService $orderService ) {} public function index() { // Чтение через HTTP API $orders = $this->orderService->getOrders(page: 1, limit: 100); return view('orders.index', compact('orders')); } public function store(OrderCreateData $data) { // Создание через HTTP API + сохранение через CommandBus $order = $this->orderService->createOrder($data); return response()->json($order); } }
Тестирование
use GeekCo\CommerceJson\Tests\TestCase; use GeekCo\CommerceJson\Http\Client\HttpClientInterface; use Mockery; class ProductServiceTest extends TestCase { protected function setUp(): void { parent::setUp(); $this->mockHttp = Mockery::mock(HttpClientInterface::class); $this->productService = new ProductService($this->mockHttp, ...); } /** @test */ public function get_products_returns_product_list(): void { $this->mockHttp->shouldReceive('get') ->once() ->andReturn(new ResponseDto(200, [], $mockResponse, ...)); $products = $this->productService->getProducts(page: 1, limit: 100); $this->assertCount(10, $products->products); } }
Использование factory для Data-классов
// Создание тестовых данных $productData = ProductData::factory()->from([ 'id' => 'uuid-here', 'name' => 'Test Product', 'code' => 'TEST-001', 'is_active' => true, ]); // В тестах protected function createProductData(array $attributes = []): ProductData { return ProductData::factory()->from([ 'id' => $this->createTestUuid(), 'name' => 'Test Product', ...$attributes, ]); }
Console-команды
# Проверка соединения php artisan commercejson:handshake # Полная синхронизация php artisan commercejson:sync --full # Инкрементальная синхронизация (изменения за последний час) php artisan commercejson:sync --incremental # Импорт товаров php artisan commercejson:import-products --queue # Импорт заказов php artisan commercejson:import-orders --updated-after=2026-01-01T00:00:00Z # Экспорт заказов в ERP php artisan commercejson:export-orders --limit=50
Архитектура
Пакет использует архитектурные паттерны CQRS (Command Query Responsibility Segregation) и Repository для разделения операций чтения и записи, что обеспечивает:
- Чёткое разделение ответственности — команды для записи, запросы для чтения
- Тестируемость — легко мокировать зависимости через интерфейсы
- Масштабируемость — независимое масштабирование чтения/записи
- Поддерживаемость — понятная структура кода
- Headless CMS — готовые REST API контроллеры и роуты из коробки
Готовые REST API endpoints
Пакет автоматически регистрирует маршруты с префиксом /api/commercejson:
| Метод | URI | Контроллер | Описание |
|---|---|---|---|
| GET | /api/commercejson/products |
ProductController | Список товаров (paginated) |
| GET | /api/commercejson/products/{id} |
ProductController | Получить товар по ID |
| POST | /api/commercejson/products |
ProductController | Создать товар |
| PUT/PATCH | /api/commercejson/products/{id} |
ProductController | Обновить товар |
| DELETE | /api/commercejson/products/{id} |
ProductController | Удалить товар |
| GET | /api/commercejson/orders |
OrderController | Список заказов (paginated) |
| GET | /api/commercejson/orders/{id} |
OrderController | Получить заказ по ID |
| POST | /api/commercejson/orders |
OrderController | Создать заказ |
| PUT/PATCH | /api/commercejson/orders/{id} |
OrderController | Обновить заказ |
| DELETE | /api/commercejson/orders/{id} |
OrderController | Удалить заказ |
| GET | /api/commercejson/categories |
CategoryController | Список категорий (paginated) |
| GET | /api/commercejson/categories/{id} |
CategoryController | Получить категорию по ID |
| POST | /api/commercejson/categories |
CategoryController | Создать категорию |
| PUT/PATCH | /api/commercejson/categories/{id} |
CategoryController | Обновить категорию |
| DELETE | /api/commercejson/categories/{id} |
CategoryController | Удалить категорию |
| GET | /api/commercejson/offers |
OfferController | Список предложений (paginated) |
| GET | /api/commercejson/offers/{id} |
OfferController | Получить предложение по ID |
| POST | /api/commercejson/offers |
OfferController | Создать предложение |
| PUT/PATCH | /api/commercejson/offers/{id} |
OfferController | Обновить предложение |
| DELETE | /api/commercejson/offers/{id} |
OfferController | Удалить предложение |
| GET | /api/commercejson/counterparties |
CounterpartyController | Список контрагентов (paginated) |
| GET | /api/commercejson/counterparties/{id} |
CounterpartyController | Получить контрагента по ID |
| POST | /api/commercejson/counterparties |
CounterpartyController | Создать контрагента |
| PUT/PATCH | /api/commercejson/counterparties/{id} |
CounterpartyController | Обновить контрагента |
| DELETE | /api/commercejson/counterparties/{id} |
CounterpartyController | Удалить контрагента |
Пример запроса:
# Получить список товаров curl -X GET "https://your-app.test/api/commercejson/products?page=1&limit=20" \ -H "Accept: application/json" # Создать товар curl -X POST "https://your-app.test/api/commercejson/products" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"name": "Product 1", "code": "PROD-001", "is_active": true}'
Структура пакета
src/
├── CommerceJsonServiceProvider.php # Регистрация сервисов
├── config/
│ └── commercejson.php # Конфигурация
├── Http/Client/
│ ├── HttpClientInterface.php # Интерфейс HTTP клиента
│ ├── CommerceJsonHttpClient.php # Реализация HTTP клиента
│ ├── RetryStrategyInterface.php # Стратегия повторных попыток
│ ├── ExponentialBackoffStrategy.php # Экспоненциальная задержка
│ └── Dto/ # DTO для запросов/ответов
├── Commands/ # Команды (запись)
│ ├── CommandInterface.php
│ ├── CreateProductCommand.php
│ └── ...
├── Queries/ # Запросы (чтение)
│ ├── QueryInterface.php
│ ├── GetProductQuery.php
│ └── ...
├── Handlers/
│ ├── Commands/ # Обработчики команд
│ │ ├── HandlerInterface.php
│ │ ├── CreateProductCommandHandler.php
│ │ └── ...
│ └── Queries/ # Обработчики запросов
│ ├── QueryHandlerInterface.php
│ ├── GetProductQueryHandler.php
│ └── ...
├── Bus/
│ ├── CommandBusInterface.php # Шина команд
│ └── QueryBusInterface.php # Шина запросов
├── Repositories/ # Репозитории
│ ├── BaseRepository.php
│ ├── ProductRepository.php
│ └── ...
├── Services/ # Сервисы (бизнес-логика)
│ ├── ServiceInterface.php
│ ├── ProductService.php
│ ├── OrderService.php
│ ├── OfferService.php
│ ├── ClassifierService.php
│ ├── WarehouseService.php
│ └── CounterpartyService.php
├── Http/Controllers/ # API контроллеры
│ ├── ProductController.php
│ ├── OrderController.php
│ └── ...
├── Exchange/
│ ├── Import/ # Импорт данных
│ │ ├── ImporterInterface.php
│ │ ├── ProductImporter.php
│ │ └── ...
│ └── Export/ # Экспорт данных
│ ├── ExporterInterface.php
│ └── OrderExporter.php
├── Jobs/ # Очереди заданий
│ ├── Import/
│ ├── Export/
│ └── Sync/
├── Console/Commands/ # Artisan команды
├── Events/ # События
├── Exceptions/ # Исключения
│ ├── SyncException.php
│ └── Http/Client/Exceptions/ # HTTP исключения
├── Models/ # Eloquent модели (23)
├── Data/ # Data-класси (49 DTO)
├── Enums/ # Перечисления (11)
├── Facades/
│ └── CommerceJson.php
└── database/
├── migrations/ # Миграции (24)
├── factories/ # Фабрики (17)
└── seeders/ # Сидеры (7)
Компоненты архитектуры
1. HTTP Client Layer
use GeekCo\CommerceJson\Http\Client\HttpClientInterface; // Внедрение зависимости public function __construct( private HttpClientInterface $http ) {} // Использование $response = $this->http->get('/catalog/products', ['page' => 1]); $data = $response->data; // Массив данных
2. Command/Query Bus
use GeekCo\CommerceJson\Bus\CommandBusInterface; use GeekCo\CommerceJson\Bus\QueryBusInterface; use GeekCo\CommerceJson\Commands\CreateProductCommand; use GeekCo\CommerceJson\Queries\GetProductQuery; // Команда (запись) $command = new CreateProductCommand($productData); $product = $this->commandBus->dispatch($command); // Запрос (чтение) $query = new GetProductQuery($id); $product = $this->queryBus->ask($query);
3. Services (бизнес-логика)
use GeekCo\CommerceJson\Services\ProductService; public function __construct( private ProductService $productService ) {} // Чтение через HTTP API $products = $this->productService->getProducts(page: 1, limit: 100); // Запись через CommandBus $product = $this->productService->syncProduct($productData);
4. Controllers (API endpoints)
use GeekCo\CommerceJson\Http\Controllers\ProductController; // GET /api/commercejson/products public function index(Request $request): JsonResponse { $query = new GetProductsQuery(perPage: 15); $products = $this->queryBus->ask($query); return response()->json(ProductData::collect($products->items())); } // POST /api/commercejson/products public function store(Request $request): JsonResponse { $command = new CreateProductCommand(ProductData::from($request->all())); $product = $this->commandBus->dispatch($command); return response()->json(ProductData::from($product), 201); }
5. Exceptions
use GeekCo\CommerceJson\Http\Client\Exceptions\AuthenticationException; use GeekCo\CommerceJson\Http\Client\Exceptions\ValidationException; use GeekCo\CommerceJson\Http\Client\Exceptions\BusinessException; use GeekCo\CommerceJson\Http\Client\Exceptions\RateLimitException; try { $this->http->post('/orders', $data); } catch (AuthenticationException $e) { // 401, 403 } catch (ValidationException $e) { // 400 - $e->errorsAsString() } catch (BusinessException $e) { // 422, 429 }
Таблицы базы данных
24 таблицы:
categories— категории товаров (иерархия)price_types— типы цен (розница, опт, дилер)warehouses— складыproperty_definitions— свойства товаровproperty_values— значения свойствcounterparties— контрагентыcontacts— контактная информацияbank_accounts— банковские счетаrepresentatives— представителиproducts— каталог товаровproduct_variants— варианты товаровproduct_images— изображения товаровoffers— торговые предложенияoffer_prices— цены предложенийstocks— остатки на складахorders— заказы клиентовorder_items— позиции заказовorder_item_taxes— налоги позицийstatus_history_entries— история статусовcustom_attributes— пользовательские атрибутыsignatories— подписанты документовproduct_analogues— аналоги товаровproduct_components— комплектующиеorder_linked_documents— связанные документы
Тестирование
Документация по генерации большого объёма данных для нагрузочного тестирования: TESTING.md.
# Запуск всех тестов composer test # Unit-тесты php vendor/bin/phpunit --testsuite=Unit # Feature-тесты php vendor/bin/phpunit --testsuite=Feature # С покрытием (HTML отчёт) composer test:coverage # С покрытием (текстовый отчёт) composer test:coverage-text
После генерации HTML отчёта, откройте coverage/index.html в браузере.
Покрытие кода
Минимальные требования к покрытию:
- Services: 85%
- Models: 80%
- Jobs: 80%
- Console Commands: 75%
- Общее: 80%
См. подробную документацию в COVERAGE.md.
Документация
Доступные методы
ProductService
// Получить товары с пагинацией $products = $productService->getProducts( page: 1, limit: 100, categoryId: 'uuid', isActive: true, updatedAfter: new DateTime('2026-01-01') ); // Получить товар по ID $product = $productService->getProduct($id); // Импортировать товары $result = $productService->importProducts($productsData); // Деактивировать товар $productService->deactivateProduct($id);
OrderService
// Получить заказы $orders = $orderService->getOrders( page: 1, limit: 50, status: 'new', updatedAfter: new DateTime('2026-01-01') ); // Создать заказ $order = $orderService->createOrder($orderCreateData); // Обновить статус заказа $orderService->updateOrderStatus($orderId, 'confirmed'); // Экспортировать новые заказы $exported = $orderService->exportOrders(limit: 50);
События
use GeekCo\CommerceJson\Events\ProductsImported; use GeekCo\CommerceJson\Events\OrderImported; use GeekCo\CommerceJson\Events\SyncCompleted; Event::listen(ProductsImported::class, function ($event) { Log::info("Импортировано {$event->importedCount} товаров"); }); Event::listen(OrderImported::class, function ($event) { // Обработка нового заказа }); Event::listen(SyncCompleted::class, function ($event) { Log::info("Синхронизация завершена за {$event->durationSeconds}с"); });
Конфигурация
// config/commercejson.php return [ 'base_url' => env('COMMERCEJSON_BASE_URL'), 'auth' => [ 'type' => env('COMMERCEJSON_AUTH_TYPE', 'bearer'), 'token' => env('COMMERCEJSON_AUTH_TOKEN'), ], 'timeout' => env('COMMERCEJSON_TIMEOUT', 30), 'retry_attempts' => env('COMMERCEJSON_RETRY_ATTEMPTS', 3), 'exchange' => [ 'mode' => env('COMMERCEJSON_EXCHANGE_MODE', 'auto'), 'batch_size' => [ 'products' => 100, 'offers' => 200, 'orders' => 50, ], 'queue' => [ 'enabled' => env('COMMERCEJSON_QUEUE_ENABLED', true), 'connection' => env('COMMERCEJSON_QUEUE_CONNECTION', 'redis'), ], ], 'sync' => [ 'schedule' => env('COMMERCEJSON_SYNC_SCHEDULE', '0 * * * *'), 'incremental' => [ 'enabled' => env('COMMERCEJSON_INCREMENTAL_SYNC', true), ], ], 'logging' => [ 'enabled' => env('COMMERCEJSON_LOGGING', true), 'channel' => env('COMMERCEJSON_LOG_CHANNEL', 'stack'), ], ];
Синхронизация
Полная синхронизация
# Синхронизация всех данных
php artisan commercejson:sync --full
Инкрементальная синхронизация
# Синхронизация изменений за последний час php artisan commercejson:sync --incremental # Синхронизация изменений с указанной даты php artisan commercejson:sync --incremental --since=2026-01-01T00:00:00Z
Планирование синхронизации
Добавьте в app/Console/Kernel.php:
protected function schedule(Schedule $schedule): void { // Ежечасная инкрементальная синхронизация $schedule->command('commercejson:sync --incremental') ->hourly(); // Еженедельная полная синхронизация $schedule->command('commercejson:sync --full') ->weeklyOn(0, '2:00'); }
Обработка ошибок
HTTP исключения
use GeekCo\CommerceJson\Http\Client\Exceptions\AuthenticationException; use GeekCo\CommerceJson\Http\Client\Exceptions\ValidationException; use GeekCo\CommerceJson\Http\Client\Exceptions\BusinessException; use GeekCo\CommerceJson\Http\Client\Exceptions\RateLimitException; use GeekCo\CommerceJson\Http\Client\Exceptions\ConnectionException; try { $order = CommerceJson::orders()->createOrder($data); } catch (AuthenticationException $e) { // 401, 403 - Неверные учётные данные Log::error('Ошибка авторизации: ' . $e->getMessage()); } catch (ValidationException $e) { // 400 - Ошибка валидации Log::error('Валидация: ' . $e->errorsAsString()); foreach ($e->errors() as $error) { Log::error($error); } } catch (BusinessException $e) { // 422, 429 - Бизнес-ошибка или rate limit Log::error('Бизнес-ошибка [' . $e->getCode() . ']: ' . $e->getMessage()); } catch (RateLimitException $e) { // 429 - Превышен лимит запросов $retryAfter = $e->retryAfter(); // секунд до повторной попытки $retryAt = $e->retryAt(); // DateTime для повторной попытки Log::warning("Превышен лимит запросов. Повтор через {$retryAfter}с"); } catch (ConnectionException $e) { // Ошибка соединения Log::error('Ошибка соединения: ' . $e->getMessage()); }
Бизнес исключения
use GeekCo\CommerceJson\Exceptions\SyncException; try { CommerceJson::syncFull(); } catch (SyncException $e) { Log::error('Синхронизация [' . $e->getSyncType() . ']: ' . $e->getMessage()); $lastSync = $e->getLastSyncTime(); // DateTime последней успешной синхронизации }
Очереди заданий
use GeekCo\CommerceJson\Jobs\Import\ImportProductsJob; use GeekCo\CommerceJson\Jobs\Import\ImportOrdersJob; use GeekCo\CommerceJson\Jobs\Sync\SyncFullJob; // Импорт товаров асинхронно ImportProductsJob::dispatch( page: 1, limit: 100, updatedAfter: now()->subHour() ); // Цепочка заданий ImportProductsJob::dispatch()->chain([ new ImportOrdersJob(), new \GeekCo\CommerceJson\Jobs\Export\ExportOrdersJob(), ]); // Полная синхронизация SyncFullJob::dispatch();
Чеклист для production
- Опубликовать конфигурацию:
php artisan vendor:publish --tag=commercejson-config - Опубликовать миграции:
php artisan vendor:publish --tag=commercejson-migrations - Запустить миграции:
php artisan migrate - Настроить worker очередей для асинхронных операций
- Настроить планирование синхронизации в Kernel.php
- Настроить мониторинг неудачных заданий
- Настроить канал логирования
- Проверить соединение:
php artisan commercejson:handshake - Запустить начальную полную синхронизацию:
php artisan commercejson:sync --full
История версий
1.0.0 (2026-04-24)
- Начальный выпуск
- Поддержка CommerceJSON v1.0.8
- 24 миграции
- 23 модели
- 49 Data-классов
- 6 сервисов
- 7 очередей заданий
- 7 console-команд
- 11 событий
- Полное тестовое покрытие
Лицензия
Пакет распространяется под лицензией MIT.
Поддержка
- Email: geekco@yandex.ru
- Issues: GitHub Issues
- Документация: Wiki