
API заказа
Программный интерфейс для работы с заказами MiniShop3 из PHP-кода.
Заказ в MiniShop3 состоит из нескольких моделей:
- msOrder — основная модель заказа (стоимость, статус, доставка, оплата)
- msOrderAddress — адрес и контактные данные заказа
- msOrderProduct — позиции (товары) в заказе
- msOrderLog — журнал изменений заказа
Жизненный цикл заказа: черновик (draft) → оформление (submit) → смена статусов.
Контроллер Order (фасад)
Контроллер-фасад Order — основной способ работы с заказами из PHP: единая точка входа, за которой стоят специализированные сервисы.
Результат приходит в конверте
Методы контроллера, возвращающие array, отдают конверт ['success' => bool, 'message' => string, 'data' => array] — тот же Utils::success() / Utils::error(), что и в Web API. Нужные значения лежат в data, причём у get() они вложены ещё на уровень: поля заказа — в data['order'].
Значение напрямую отдают только: initialize(), initDraft(), remove() и hasPayment() — bool; getDraft() — объект msOrder или null; getUserId() — int.
$ms3 = $modx->services->get('ms3');
// Получить данные текущего заказа
$response = $ms3->order->get();
$order = $response['data']['order'];
// Добавить поле
$result = $ms3->order->add('email', 'user@example.com');
// Установить несколько полей
$result = $ms3->order->set([
'email' => 'user@example.com',
'phone' => '+79991234567',
'first_name' => 'Иван',
'delivery_id' => 1,
'payment_id' => 1,
'order_comment' => 'Позвоните перед доставкой',
]);
// Оформить заказ
$result = $ms3->order->submit();
// ['success' => true, 'data' => ['order_id' => 15, 'order_num' => '26/02-15', ...]]
// Очистить черновик
$ms3->order->clean();Инициализация
До первого вызова контроллеру нужен токен клиента:
$ms3->order->initialize($token);
$ms3->order->initDraft();В REST API и сниппетах это происходит автоматически.
Методы контроллера
| Метод | Описание |
|---|---|
initialize($token, $config) | Инициализация с токеном клиента |
initDraft() | Загрузка существующего черновика |
get() | Данные заказа (поля + адрес) |
add($key, $value) | Добавить/обновить поле |
set($fields) | Установить несколько полей |
remove($key) | Удалить поле (сбросить в null) |
validate($key, $value) | Валидация значения поля |
submit($data) | Оформить заказ |
clean() | Очистить черновик |
getCost($onlyCost) | Полная стоимость (корзина + доставка + оплата) |
getCartCost() | Стоимость корзины |
getDeliveryCost() | Стоимость доставки |
getPaymentCost() | Комиссия оплаты |
setCustomerAddress($hash) | Применить сохранённый адрес клиента |
cleanCustomerAddress() | Очистить адресные поля |
getDeliveryValidationRules($deliveryId) | Правила валидации доставки |
getDeliveryRequiresFields($deliveryId) | Обязательные поля доставки |
getDraft() | Получить объект черновика msOrder |
hasPayment($deliveryId, $paymentId) | Проверить, допустима ли пара «доставка — оплата» |
getUserId() | ID пользователя MODX для текущего заказа; если его нет — регистрирует |
Черновики (OrderDraftManager)
Черновик — объект msOrder в статусе draft. Создаётся при первом обращении клиента к корзине и хранит данные до оформления.
$draftManager = $modx->services->get('ms3_order_draft_manager');
// Получить или создать черновик
$draft = $draftManager->getOrCreateDraft($token, 'web');
// Получить существующий черновик (без создания)
$draft = $draftManager->getDraft($token, 'web');
// Получить черновик по ID клиента (для восстановления после логина)
$draft = $draftManager->getDraftByCustomer($customerId, 'web');
// Данные черновика как массив (заказ + адрес)
$data = $draftManager->toArray($draft);
// Адресные поля возвращаются с префиксом address_:
// ['email' => '...', 'address_city' => 'Москва', 'address_street' => '...']
// Обновить одно поле
$draftManager->updateField($draft, 'order_comment', 'Позвоните перед доставкой');
// Привязать клиента к черновику
$draftManager->attachCustomer($draft, $customerId);
// Пересчитать стоимость
$draftManager->recalculate($draft);
// Установить стоимость доставки
$draftManager->setDeliveryCost($draft, 350.00);
// Проверить, пуста ли корзина
if ($draftManager->isEmpty($draft)) {
// Нет товаров
}
// Очистить все поля
$draftManager->clean($draft);
// Удалить черновик полностью (с товарами и адресом)
$draftManager->deleteDraft($draft);Поля и валидация (OrderFieldManager)
Сервис управляет полями заказа и их валидацией, а при изменениях вызывает события.
$fieldManager = $modx->services->get('ms3_order_field_manager');
// Добавить поле (с валидацией и событиями)
$result = $fieldManager->add($draft, $orderData, 'email', 'user@example.com');
// ['success' => true, 'data' => [...]]
// Удалить поле
$fieldManager->remove($draft, $orderData, 'email');
// Валидация без сохранения
$result = $fieldManager->validate($orderData, 'phone', '+79991234567');
// ['success' => true] или ['success' => false, 'message' => 'Ошибка']Правила валидации
По умолчанию delivery_id и payment_id проверяются правилом required|numeric. Остальные правила приходят из настроек доставки.
// Получить правила валидации для доставки
$rules = $fieldManager->getDeliveryValidationRules($deliveryId);
// ['city' => 'required|min:2', 'street' => 'required', ...]
// Получить список обязательных полей
$required = $fieldManager->getDeliveryRequiredFields($deliveryId);
// ['city', 'street', 'building', 'phone']
// Добавить свои правила
$fieldManager->setValidationRules([
'company_name' => 'required|min:3',
]);Расчёт стоимости (OrderCostCalculator)
$calculator = $modx->services->get('ms3_order_cost_calculator');
// Стоимость корзины
$result = $calculator->getCartCost($draft, $token);
// ['cost' => 5000.00]
// Стоимость доставки
$result = $calculator->getDeliveryCost($draft, $orderData, $token);
// ['cost' => 300.00]
// Комиссия оплаты
$result = $calculator->getPaymentCost($draft, $orderData, $token);
// ['cost' => 150.00]
// Полная стоимость
$result = $calculator->getTotalCost($draft, $orderData, $token);
// ['cost' => 5450.00, 'cart_cost' => 5000.00, 'delivery_cost' => 300.00, 'payment_cost' => 150.00]Каждый метод вызывает пару событий msOnBefore... / msOn... — через них плагин меняет стоимость.
Пересчёт стоимости в админке — POST /api/mgr/orders/{id}/recalculate-cost
Появился в 1.11.0. Сервис ManagerOrderCostRecalculator пересчитывает cart_cost, weight, delivery_cost и итоговый cost по сохранённым позициям заказа и текущим delivery_id / payment_id. Другие поля заказа он не трогает.
Тело запроса:
{
"mode": "auto",
"manual_delivery_cost": 500.0
}Режимы (mode):
| Режим | Поведение |
|---|---|
auto (по умолчанию) | Пересчитывает только для DefaultDelivery / DefaultPayment (по полям price, weight_price, free_delivery_amount и процентам). Для своих обработчиков возвращает предупреждение delivery_manual_required / payment_manual_required, оставляет прежнюю delivery_cost и комиссию 0 — внешние API не вызывает. |
manual | Берёт переданный manual_delivery_cost. Комиссия оплаты считается автоматически, по полю. |
force_provider | Явно вызывает loadController() → getCost() и loadHandler() → getCost() в try/catch. При сбое — предупреждение delivery_provider_error / payment_provider_error, прежние значения остаются. |
Ответ повторяет данные заказа из GET /api/mgr/orders/{id} и добавляет:
{
"breakdown": {
"cart_cost": 5000.0,
"weight": 1.5,
"delivery_cost": 300.0,
"payment_cost": 150.0,
"cost": 5450.0
},
"warnings": ["delivery_manual_required"]
}Итог проходит через общий ограничитель OrderService::clampComputedTotal() — ниже нуля он не уйдёт. Скидки и наценки доставки и оплаты (отрицательные price, см. 1.11.0) считает MiniShop3\Utils\PriceAdjustment.
Пример вызова из JS (карточка заказа в админке):
const response = await fetch(`/api/mgr/orders/${orderId}/recalculate-cost`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'modAuth': MODx.modxConfig.auth, // или актуальный mgr-auth заголовок
},
body: JSON.stringify({
mode: 'manual',
manual_delivery_cost: 500,
}),
})
const json = await response.json()
// json.data.breakdown — cart_cost / delivery_cost / payment_cost / cost
// json.data.warnings — например delivery_manual_requiredЧерез HTTP (Manager API):
curl -X POST 'https://example.com/api/mgr/orders/42/recalculate-cost' \
-H 'Content-Type: application/json' \
-H 'Cookie: …' \
-d '{"mode":"auto"}'См. также API Router.
Оформление заказа (OrderSubmitHandler)
OrderSubmitHandler выполняет полный цикл оформления: валидация → создание клиента → расчёт стоимости → генерация номера → смена статуса → вызов оплаты.
$submitHandler = $modx->services->get('ms3_order_submit_handler');
$result = $submitHandler->submit($draft, $orderData, $token);
// Успех:
// [
// 'success' => true,
// 'data' => [
// 'order_id' => 15,
// 'order_num' => '26/02-15',
// 'redirect_url' => '/thank-you?msorder=15',
// ],
// 'message' => 'Заказ успешно оформлен'
// ]Порядок действий при оформлении
- Событие
msOnSubmitOrder - Проверка: корзина не пуста
- Валидация
delivery_id,payment_idи обязательных полей доставки - Поиск/создание клиента (
msCustomer) - Опционально: создание пользователя MODX (настройка
ms3_order_register_user_on_submit) - Расчёт стоимости (корзина + доставка + оплата)
- Генерация номера заказа
- Сохранение адреса клиента
- События
msOnBeforeCreateOrder/msOnCreateOrder - Смена статуса на
ms3_status_new(по умолчанию: 2) - Вызов
$msPayment->send()— редирект на оплату - Возврат URL страницы «Спасибо»
Генерация номера заказа
$num = $submitHandler->getNewOrderNum();
// "26/02-15" — формат настраивается:
// ms3_order_format_num — формат даты (по умолчанию 'ym')
// ms3_order_format_num_separator — разделитель (по умолчанию '/')Управление статусами (OrderStatusService)
$statusService = $modx->services->get('ms3_order_status');
// Сменить статус заказа
$result = $statusService->change($orderId, $newStatusId);
// true — успех
// string — текст ошибки из лексикона
// Сменить статус без уведомлений
$result = $statusService->change($orderId, $newStatusId, true);Ограничения статусов
| Свойство статуса | Поведение |
|---|---|
final = true | Нельзя сменить на другой статус |
fixed = true | Можно переключить только на статус с большей position |
Поверх этих двух правил работает список разрешённых переходов — системная настройка ms3_order_status_transitions. Пока она пуста, действуют только final и fixed. Если список задан, переход должен быть в нём и не нарушать final и fixed.
Формат — пары «из статуса : в статус», через запятую или массивом JSON:
2:3,3:4,2:5[[2,3],[3,4]]Если значение настройки разобрать не удалось, блокируется любая смена статуса — с ошибкой ms3_err_status_transitions_invalid. Опечатка в списке не игнорируется: она останавливает обработку заказов.
Уведомления
При смене статуса NotificationManager сам рассылает уведомления:
- Клиенту — email, телефон из
msCustomerилиmodUserProfile - Менеджерам — из настроек
ms3_email_manager,ms3_phone_manager,ms3_telegram_manager
Позиции заказа (msOrderProduct)
Позиции заказа хранит модель msOrderProduct (таблица ms3_order_products).
Поля msOrderProduct
| Поле | Тип | Описание |
|---|---|---|
product_id | integer | ID товара (msProduct) |
order_id | integer | ID заказа |
product_key | string | Уникальный ключ позиции (напр. 123_a1b2c3d4) |
name | string | Название товара |
count | integer | Количество |
price | float | Цена за единицу |
weight | float | Вес за единицу |
cost | float | Стоимость позиции (price × count) |
options | json | Выбранные опции |
properties | json | Дополнительные свойства |
Программная работа с позициями
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msOrderProduct;
$order = $modx->getObject(msOrder::class, $orderId);
// Получить все позиции заказа
$products = $order->getMany('Products');
foreach ($products as $product) {
echo $product->get('name') . ': ' . $product->get('count') . ' × ' . $product->get('price');
}
// Добавить позицию
$item = $modx->newObject(msOrderProduct::class);
$item->set('order_id', $orderId);
$item->set('product_id', $productId);
$item->set('product_key', $productId . '_' . md5(json_encode($options)));
$item->set('name', 'Товар');
$item->set('count', 2);
$item->set('price', 1500.00);
$item->set('cost', 3000.00);
$item->set('weight', 0.5);
$item->set('options', $options);
$item->save();
// Пересчитать итоги заказа после изменения позиций
$order->updateProducts();Пересчёт итогов
После добавления, удаления или изменения позиций вызывайте $order->updateProducts(). Метод пересчитывает cart_cost, weight и cost по всем msOrderProduct заказа.
Адрес заказа (msOrderAddress)
У каждого заказа один связанный объект msOrderAddress (таблица ms3_order_addresses).
Поля msOrderAddress
| Поле | Тип | Описание |
|---|---|---|
order_id | integer | ID заказа |
first_name | string | Имя |
last_name | string | Фамилия |
phone | string | Телефон |
email | string | |
country | string | Страна |
index | string | Почтовый индекс |
region | string | Регион |
city | string | Город |
metro | string | Станция метро |
street | string | Улица |
building | string | Дом |
entrance | string | Подъезд |
floor | string | Этаж |
room | string | Квартира/офис |
comment | string | Комментарий к адресу |
text_address | string | Полный адрес одной строкой |
properties | json | Дополнительные свойства |
Программная работа
use MiniShop3\Model\msOrder;
$order = $modx->getObject(msOrder::class, $orderId);
$address = $order->getOne('Address');
// Чтение
echo $address->get('city'); // "Москва"
echo $address->get('street'); // "Ленина"
// Обновление
$address->set('city', 'Санкт-Петербург');
$address->save();OrderAddressManager
Сервис работает с адресами черновика:
$addressManager = $modx->services->get('ms3_order_address_manager');
// Применить сохранённый адрес клиента к черновику
$result = $addressManager->setCustomerAddress($draft, $orderData, $addressHash);
// Очистить все адресные поля
$result = $addressManager->cleanCustomerAddress($draft, $orderData);
// Сохранить адрес заказа в адреса клиента
$savedAddress = $addressManager->saveToCustomerAddresses($customerId, $orderData);Журнал заказа (OrderLogService)
Журнал фиксирует изменения заказа: смену статуса, правку полей, работу с позициями.
$logService = $modx->services->get('ms3_order_log');
// Добавить запись в журнал
$logService->addEntry($orderId, 'status', [
'old' => 1,
'new' => 2,
]);
// Добавить запись о произвольном действии
$logService->addEntry($orderId, 'field', [
'key' => 'delivery_id',
'old_value' => 1,
'new_value' => 2,
], true); // visible = true (видна клиенту)
// Получить записи журнала
$entries = $logService->getEntries($orderId);
// Только видимые клиенту
$entries = $logService->getEntries($orderId, true);
// С лимитом
$entries = $logService->getEntries($orderId, false, 50);
// Проверить, логируется ли действие (настройка ms3_order_log_actions)
if ($logService->shouldLog('status')) {
// ...
}Типы действий
| Константа | Значение | Описание |
|---|---|---|
msOrderLog::ACTION_STATUS | status | Смена статуса |
msOrderLog::ACTION_PAYMENT | payment | Изменение оплаты |
msOrderLog::ACTION_PRODUCTS | products | Изменение позиций |
msOrderLog::ACTION_ADDRESS | address | Изменение адреса |
msOrderLog::ACTION_FIELD | field | Изменение поля заказа |
Настройка ms3_order_log_actions задаёт, какие действия попадают в журнал. По умолчанию — status,products,field,address; значение * записывает все действия.
Финализация из админки (OrderFinalizeService)
OrderFinalizeService оформляет заказы, которые менеджер создал в админке.
use MiniShop3\Services\Order\OrderOrigin;
$finalizeService = $modx->services->get('ms3_order_finalize');
$result = $finalizeService->finalize($orderId, [
'skip_validation' => false,
'skip_notifications' => false,
'create_customer' => true,
'force_create_customer' => false,
'origin' => OrderOrigin::MANAGER, // по умолчанию; для CRM — OrderOrigin::INTEGRATION
]);Финализация принимает ключи skip_* и работает с уже созданным черновиком. Платёжный сервис здесь не вызывается — в отличие от submit на фронтенде.
Параметр origin (OrderOrigin): manager (по умолчанию), storefront, integration. При manager дополнительно вызываются msOnBeforeMgrCreateOrder / msOnMgrCreateOrder. Ключ from_manager в событиях равен true только при origin=manager.
Программное создание заказа (ProgrammaticOrderService)
API без HTTP-сессии — для дополнений, cron и интеграций. Это не Web API: в routes/web.php отдельного эндпоинта нет.
| Ключ | ms3_programmatic_order |
| Класс | MiniShop3\Services\Order\ProgrammaticOrderService |
| Черновик | OrderDraftManager::createSessionlessDraft() (без PHP-сессии и токена корзины) |
| Финализация | OrderFinalizeService::finalize(..., origin=integration) |
| Идемпотентность | колонка ms3_orders.idempotency_key (unique, nullable) |
use MiniShop3\Services\Order\OrderOrigin;
$orders = $modx->services->get('ms3_programmatic_order');
$result = $orders->create([
'idempotency_key' => 'crm-invoice-10042', // обязательно
'products' => [
['product_id' => 15, 'count' => 2],
// или снимок без ресурса:
// ['name' => 'Услуга', 'price' => 500, 'count' => 1, 'weight' => 0, 'options' => []],
],
'customer_id' => 0,
'delivery_id' => 1,
'payment_id' => 1,
'address' => [
'first_name' => 'Иван',
'email' => 'user@example.com',
'phone' => '+79991234567',
],
'order_comment' => 'Из CRM',
'context' => 'web',
'origin' => OrderOrigin::INTEGRATION,
// 'delivery_cost' => 300, // → cost_mode=manual при finalize
// 'skip_notifications' => true,
// 'skip_validation' => false,
// 'properties' => ['source' => 'crm'],
]);
if (!$result['success']) {
// Частые message:
// ms3_order_err_idempotency_key_required
// ms3_order_err_products_required
// ms3_order_err_programmatic_create
// либо ошибки finalize / валидации
return $result;
}
// Успех: data = { order_id, uuid, num, status_id }
// Повтор того же idempotency_key:
// уже оформленный заказ → success + ms3_order_programmatic_idempotent
// черновик со статусом draft → повторная финализацияСобытия: msOnBeforeCreateOrder / msOnCreateOrder с origin=integration и без from_manager. События msOnBeforeMgrCreateOrder / msOnMgrCreateOrder не вызываются.
Отличие от POST /api/mgr/orders (менеджер создаёт пустой или частичный заказ в админке) и от POST /api/v1/order/submit на фронтенде (нужны токен и корзина).
Разрешение пользователей (OrderUserResolver)
Сервис находит или создаёт пользователя MODX по данным заказа.
$userResolver = $modx->services->get('ms3_order_user_resolver');
// Получить или создать MODX user_id
$userId = $userResolver->getUserId($orderData);
// Проверить существование пользователя
$user = $userResolver->checkUserExists([
'email' => 'user@example.com',
'phone' => '+79991234567',
]);
// Создать пользователя
$user = $userResolver->createUser([
'email' => 'user@example.com',
'first_name' => 'Иван',
'last_name' => 'Иванов',
'phone' => '+79991234567',
]);Настройка ms3_order_user_groups задаёт группы для новых пользователей. Формат — group_id:role_id, через запятую.
Поля msOrder
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
user_id | integer | 0 | ID пользователя MODX |
customer_id | integer | 0 | ID клиента (msCustomer) |
token | string | — | Токен сессии клиента |
uuid | string | — | Уникальный UUID заказа |
createdon | datetime | null | Дата создания |
updatedon | datetime | null | Дата обновления |
num | string | '' | Номер заказа |
cost | float | 0.0 | Итоговая стоимость |
cart_cost | float | 0.0 | Стоимость товаров |
delivery_cost | float | 0.0 | Стоимость доставки |
weight | float | 0.0 | Общий вес |
status_id | integer | 0 | ID текущего статуса |
delivery_id | integer | 0 | ID способа доставки |
payment_id | integer | 0 | ID способа оплаты |
context | string | 'web' | Контекст MODX |
order_comment | string | null | Комментарий к заказу |
idempotency_key | string | null | Ключ идемпотентности (программные заказы, unique) |
properties | json | null | Дополнительные свойства (в т.ч. origin для интеграций) |
Связи msOrder
| Связь | Модель | Тип | Описание |
|---|---|---|---|
Address | msOrderAddress | composite (one) | Адрес заказа |
Products | msOrderProduct | composite (many) | Позиции заказа |
Log | msOrderLog | composite (many) | Журнал изменений |
Customer | msCustomer | aggregate | Клиент |
Status | msOrderStatus | aggregate | Статус |
Delivery | msDelivery | aggregate | Способ доставки |
Payment | msPayment | aggregate | Способ оплаты |
User | modUser | aggregate | Пользователь MODX |
Composite vs Aggregate
Composite-связи удаляются каскадно при удалении заказа (адрес, позиции, журнал). Aggregate-связи — только ссылки, связанные объекты не удаляются.
События
| Событие | Когда вызывается |
|---|---|
msOnBeforeSaveOrder / msOnSaveOrder | Сохранение заказа |
msOnBeforeRemoveOrder / msOnRemoveOrder | Удаление заказа |
msOnBeforeGetOrderCost / msOnGetOrderCost | Сборка итога заказа |
msOnBeforeGetCartCost / msOnGetCartCost | Расчёт стоимости корзины |
msOnBeforeGetDeliveryCost / msOnGetDeliveryCost | Расчёт стоимости доставки |
msOnBeforeGetPaymentCost / msOnGetPaymentCost | Расчёт комиссии оплаты |
msOnBeforeAddToOrder / msOnAddToOrder | Добавление/изменение поля |
msOnBeforeRemoveFromOrder / msOnRemoveFromOrder | Удаление поля |
msOnBeforeValidateOrderValue / msOnValidateOrderValue | Валидация значения |
msOnErrorValidateOrderValue | Ошибка валидации |
msOnSubmitOrder | Начало оформления |
msOnBeforeCreateOrder / msOnCreateOrder | Создание заказа |
msOnBeforeChangeOrderStatus / msOnChangeOrderStatus | Смена статуса |
msOnBeforeGetOrderUser / msOnGetOrderUser | Поиск/создание пользователя |
msOnBeforeMgrCreateOrder / msOnMgrCreateOrder | Финализация из админки (только origin=manager) |
Manager REST API
Эндпоинты Vue-интерфейса заказов. Нужна сессия mgr — см. API Router.
| Метод | Путь | Описание |
|---|---|---|
GET | /api/mgr/orders | Список заказов |
GET | /api/mgr/orders/filters | Значения фильтров грида |
GET | /api/mgr/orders/stats | Агрегаты для фильтров и дашборда |
GET | /api/mgr/orders/{id} | Карточка заказа |
PUT | /api/mgr/orders/{id} | Обновление заказа |
DELETE | /api/mgr/orders/{id} | Удаление заказа |
DELETE | /api/mgr/orders/bulk | Массовое удаление |
POST | /api/mgr/orders | Создание заказа из админки |
POST | /api/mgr/orders/{id}/finalize | Финализация черновика |
POST | /api/mgr/orders/{id}/recalculate-cost | Пересчёт через ManagerOrderCostRecalculator |
GET | /api/mgr/orders/{id}/logs | Журнал заказа |
GET | /api/mgr/orders/{id}/products | Позиции заказа |
POST | /api/mgr/orders/{id}/products | Добавить позицию |
PUT | /api/mgr/orders/{id}/products/{product_id} | Изменить позицию |
DELETE | /api/mgr/orders/{id}/products/{product_id} | Удалить позицию |
GET | /api/mgr/orders/{id}/shipment | Отгрузка заказа |
PUT | /api/mgr/orders/{id}/shipment | Обновить отгрузку |
Создание и финализация из админки идут через OrderFinalizeService и вызывают события msOnBeforeMgrCreateOrder / msOnMgrCreateOrder.
Параметры событий — в разделе События.
