
- MODX 3
- PHP 8.2
- miniShop3


Промо-коды доступны через REST API в составе MiniShop3. Все пути — с префиксом /api/v1/promo/.
API подключён к роутеру MiniShop3. Общая точка входа:
/assets/components/minishop3/api.php?route=/api/v1/promo/{action}С включённым переписыванием URL:
{site_url}api/v1/promo/{action}При установке в core/config/ms3.routes.d/web/50-ms3promocode.php создаётся файл-регистратор. Роутер MS3 загружает его и через require подключает реальные роуты пакета — core/components/ms3promocode/config/routes/web.php.
Авторизация, CORS и ограничение частоты запросов
К группе /api/v1/promo/* привязан только TokenMiddleware. CorsMiddleware и RateLimitMiddleware не наследуются от MS3 автоматически — к нашей группе они не прикреплены. Следствия:
Отслеживается в issue #3.
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api/v1/promo/apply | Применить код к корзине |
| POST | /api/v1/promo/remove | Снять применённый код |
| POST | /api/v1/promo/validate | Проверить код без записи |
| GET | /api/v1/promo/current | Получить текущий применённый код |
Применить код к черновику корзины покупателя.
{
"code": "SALE10"
}{
"success": true,
"message": null,
"data": {
"code": "SALE10",
"discount": 500.00,
"breakdown": {
"ms-prod-key-1": 300.00,
"ms-prod-key-2": 200.00
}
}
}| Поле | Описание |
|---|---|
code | Применённый код в верхнем регистре |
discount | Фактическая сумма скидки — после округления цен позиций и копеечной компенсации; может на копейку отличаться от математического расчёта |
breakdown | Карта product_key → сумма скидки на позицию |
Общий конверт ошибки MS3 Router:
{
"success": false,
"message": "Промо-код истёк",
"code": 400,
"errors": { "code": "expired" },
"error_code": "bad_request"
}| Поле | Описание |
|---|---|
success | Всегда false при ошибке |
message | Локализованное сообщение. Для missing_code, empty_cart, server_error — на английском (запасной вариант). Остальные ошибки — через лексиконы ms3promocode_err_* |
code | HTTP-статус ответа — всегда 400, даже для server_error (контроллер не отдаёт 500) |
errors.code | Машиночитаемая причина (см. таблицу ниже) |
error_code | Общий MS3-код ошибки — выводится из HTTP-статуса (400 → bad_request) |
Возможные значения errors.code:
| Код | Описание |
|---|---|
missing_code | В запросе отсутствует поле code |
empty_cart | Нет черновика корзины |
not_found | Код не существует или отключён (маскировка для покупателя) |
scheduled | Код ещё не активен |
expired | Срок действия истёк |
exhausted | Лимит применений исчерпан |
min_order | Сумма заказа меньше требуемого минимума |
not_applicable | Код не подходит к текущему составу корзины |
server_error | Внутренняя ошибка (HTTP всё равно 400) |
Маскировка отключённых кодов
Если в админке код отключён (флаг active = 0), Web API в /apply и /validate отдаёт errors.code: not_found — чтобы покупатель не узнал о временно отключённом коде. Менеджерский API (Mgr/Order/ApplyPromo) маскировку не применяет: админу нужна реальная причина.
Снять применённый код с черновика корзины. Идемпотентно — если кода нет, возвращает removed: false без ошибки.
Тело не требуется — любые поля игнорируются.
{
"success": true,
"message": null,
"data": {
"removed": true
}
}removed: false — код не был применён (снимать нечего). Такой же ответ, если черновика корзины ещё нет.
Проверить, можно ли применить код, без записи в БД. Удобно для предпросмотра перед применением.
{
"code": "SALE10"
}{
"success": true,
"data": {
"valid": true,
"preview": {
"code": "SALE10",
"discount_type": "percent",
"discount_value": 10,
"discount_amount": 500.00,
"breakdown": {
"ms-prod-key-1": 300.00,
"ms-prod-key-2": 200.00
}
}
}
}Если корзины ещё нет (покупатель не добавлял товары), validate проверяет только сам код:
{
"success": true,
"data": {
"valid": true,
"preview": {
"code": "SALE10",
"discount_type": "percent",
"discount_value": 10
}
}
}discount_amount и breakdown отсутствуют — посчитать их не на чём.
{
"success": true,
"data": {
"valid": false,
"reason": "expired",
"message": "Срок действия промо-кода истёк"
}
}Подсказка
success: true, потому что сам HTTP-запрос прошёл; валидность кода — в data.valid.
Получить состояние применённого к корзине кода.
Без тела.
{
"success": true,
"message": null,
"data": {
"current": {
"id": 12,
"code": "SALE10",
"discount_type": "percent",
"discount_value": 10,
"discount_amount": 500.00,
"breakdown": {
"ms-prod-key-1": 300.00,
"ms-prod-key-2": 200.00
},
"original_cart_cost": 5000.00,
"applied_at": "2026-04-21T12:34:56+00:00"
}
}
}discount_amount — та же величина, что discount в ответе /apply. applied_at — строка ISO 8601 в таймзоне сервера (date('c')).
Один и тот же ответ в двух случаях: черновик есть, но код не применён; черновика ещё нет.
{
"success": true,
"message": null,
"data": {
"current": null
}
}API использует MS3 TokenMiddleware. Токен ищется в таком порядке:
Authorization: Bearer <token>MS3TOKEN: <token>ms3_tokenПараметр ms3_token в URL не принимается — TokenMiddleware его явно вырезает (MS3 issue #576). В теле запроса токен тоже не ищется.
Для непубличных роутов MS3 при отсутствии токена автоматически выпускает анонимный токен и ставит его в cookie — для покупателя это незаметно.
Простейший путь — через готовый headless-клиент:
await window.ms3PromoCode.init();
const result = await window.ms3PromoCode.apply('SALE10');См. JS API.
$ch = curl_init('https://shop.example.com/api/v1/promo/apply');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['code' => 'SALE10']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $msToken,
],
]);
$response = json_decode(curl_exec($ch), true);Внимание
Нужен валидный $msToken — получите его через GET /api/v1/customer/token/get или возьмите из сессии MS3.