Управление промокодами

Промокод уменьшает стоимость заказа в клиентском приложении Gigma. Вы задаёте код, размер скидки, период, лимиты и аудиторию, а витрина передаёт код при расчёте корзины и создании заказа.

Для управления используйте методы /api/discounts. Методы /api/promotions относятся к старому справочнику промоакций и не создают промокоды.

Промокод может относится к конкретному Application. Если application_id равен null, код работает во всех приложениях того же проекта.

Как это работает

  1. Сотрудник создаёт промокод. Без поля status объект получает статус draft.
  2. Сотрудник активирует код.
  3. Витрина передаёт promo_code в предварительный расчёт заказа.
  4. Gigma считает скидку после товарных скидок.
  5. При создании заказа Gigma повторно проверяет код с учётом клиента, приложения, дат и лимитов.
  6. Применение попадает в журнал.
Сотрудник → POST /api/discounts → промокод draft → activate
                                                     │
Клиент → precalculate → создание заказа ─────────────┴→ журнал применений

Авторизация

ЗадачаАвторизация
Управлять промокодами, статистикой и журналомBearer сотрудника
Предварительно рассчитать корзинуApp Token
Создать заказ с промокодомApp Token + Bearer клиента

Административные методы /api/discounts работают внутри проекта сотрудника. Чужой application_id или branch_id не проходит валидацию, а чтение или изменение промокода другого проекта возвращает 404.

Код уникален во всём проекте среди неархивированных промокодов, а не только внутри одного приложения.

branch_id используется как атрибут и фильтр административного списка. В текущей клиентской проверке он не ограничивает применение кода по филиалу.

Быстрый старт

Шаг 1. Создайте промокод

POST /api/discounts HTTP/1.1
Host: api.gigma.ru
Authorization: Bearer <employee_token>
Accept: application/json
Content-Type: application/json

{
  "name": "10% на первый заказ",
  "code": "WELCOME10",
  "discount_type": "percent",
  "discount_value": 10,
  "application_id": 17,
  "min_order_amount": 500,
  "max_discount_amount": 1000,
  "total_usage_limit": 1000,
  "per_client_usage_limit": 1,
  "audience": "new_clients",
  "starts_at": "2026-08-20T00:00:00+03:00",
  "ends_at": "2026-09-30T23:59:59+03:00"
}

Gigma удаляет пробелы по краям кода и переводит латинские буквы в верхний регистр. Допустимы A–Z, цифры и дефис, длина — до 64 символов.

Шаг 2. Активируйте код

POST /api/discounts/42/activate HTTP/1.1
Host: api.gigma.ru
Authorization: Bearer <employee_token>
Accept: application/json

Шаг 3. Рассчитайте корзину

POST /api/counterparty/orders/precalculate HTTP/1.1
Host: api.gigma.ru
Token: <application_token>
Accept: application/json
Content-Type: application/json

{
  "products": [
    { "id": 34780, "quantity": 2 }
  ],
  "promo_code": "WELCOME10"
}

Успешный расчёт возвращает promo_discount_amount, final_price и applied_discount. Предварительный расчёт не резервирует лимит и не заменяет окончательную проверку.

Шаг 4. Создайте заказ

Передайте тот же promo_code в POST /api/counterparty/orders вместе с обычными полями заказа. На этом шаге Gigma повторно проверяет код от имени авторизованного клиента и записывает применение.

Полный контракт находится в разделе «Заказы и оплата».

Настройки промокода

Размер скидки

  • discount_type: "percent" — процент от суммы после товарных скидок; discount_value от 0.01 до 100.
  • discount_type: "fixed" — фиксированная сумма; discount_value не меньше 0.01.
  • max_discount_amount ограничивает скидку сверху.
  • Итоговая скидка не превышает сумму заказа.

Период и статусы

starts_at и ends_at необязательны. Для объекта со статусом active поле effective_status рассчитывается по датам:

Состояниеeffective_status
дата начала ещё не наступилаscheduled
дата окончания прошлаexpired
промокод действует сейчасactive
draft, paused, archivedсовпадает с status

Исчерпанный лимит не меняет effective_status: код остаётся active, но заказ получает ошибку лимита.

Лимиты

  • min_order_amount — минимальная сумма после товарных скидок.
  • total_usage_limit — общий лимит применений.
  • per_client_usage_limit — лимит на одного клиента.
  • total_used_count — текущий счётчик.
  • usage_percent — доля использования общего лимита; null, если лимит не задан.

Аудитория

ЗначениеПоведение
allдоступно всем
new_clientsдоступно клиенту без ранее успешных заказов
partnersзначение сохраняется, но применение сейчас отклоняется кодом 2009
customзначение сохраняется, но применение сейчас отклоняется кодом 2010

Правила, зависящие от клиента, могут быть неполными в публичном precalculate. Окончательное решение принимает POST /api/counterparty/orders с Bearer клиента.

Административные методы

Получение списка промокодов

Метод
GET
URL
https://api.gigma.ru/api/discounts
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Возвращает промокоды текущего проекта, недавно изменённые — первыми. Без фильтра status архивные записи не возвращаются.

Параметры запроса

  • query (string, необязательно, до 255 символов) — поиск по названию или коду.
  • status (string, необязательно) — all, active, scheduled, expired, paused, draft или archived.
  • audience (string, необязательно) — all, new_clients, partners или custom.
  • period (string, необязательно) — месяц YYYY-MM; возвращаются коды, действовавшие хотя бы один день месяца.
  • application_id (integer, необязательно) — приложение текущего проекта.
  • branch_id (integer, необязательно) — филиал текущего проекта.
  • page (integer, необязательно) — номер страницы, начиная с 1.
  • per_page (integer, необязательно) — от 1 до 200, по умолчанию 50.

Пример запроса

GET /api/discounts?status=active&application_id=17&period=2026-08&page=1&per_page=50 HTTP/1.1
Host: api.gigma.ru
Authorization: Bearer <employee_token>
Accept: application/json

Ответ

{
  "discounts": [
    {
      "id": 42,
      "project_id": 10,
      "application_id": 17,
      "branch_id": null,
      "name": "10% на первый заказ",
      "code": "WELCOME10",
      "description": null,
      "status": "active",
      "effective_status": "active",
      "discount_type": "percent",
      "discount_value": 10,
      "min_order_amount": 500,
      "max_discount_amount": 1000,
      "total_usage_limit": 1000,
      "total_used_count": 24,
      "per_client_usage_limit": 1,
      "usage_percent": 2.4,
      "audience": "new_clients",
      "starts_at": "2026-08-20T00:00:00.000000Z",
      "ends_at": "2026-09-30T20:59:59.000000Z",
      "url": null,
      "paused_at": null,
      "archived_at": null,
      "share_path": "?ref=WELCOME10",
      "magic_link": "https://shop.example.com/?ref=WELCOME10",
      "created_by": 14,
      "updated_by": 14,
      "created_at": "2026-08-20T09:00:00.000000Z",
      "updated_at": "2026-08-20T09:10:00.000000Z"
    }
  ],
  "discountsCount": 1,
  "pagination": {
    "current_page": 1,
    "per_page": 50,
    "last_page": 1,
    "total": 1
  }
}

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 422 — фильтр имеет неверный формат либо связанный объект относится к другому проекту.

Создание промокода

Метод
POST
URL
https://api.gigma.ru/api/discounts
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}; Content-Type: application/json
Успешный ответ
201

Если status не передан, используется draft; если не передана audience, используется all; min_order_amount по умолчанию равен 0.

Параметры запроса

  • name (string, обязательно, до 255 символов) — внутреннее название.
  • code (string, обязательно, до 64 символов) — латинские буквы, цифры и дефис.
  • discount_type (string, обязательно) — percent или fixed.
  • discount_value (number, обязательно) — размер скидки.
  • status (string, необязательно) — draft, active, paused или archived.
  • application_id (integer|null, необязательно) — ограничить код одним приложением.
  • branch_id (integer|null, необязательно) — атрибут филиала.
  • min_order_amount (number|null, необязательно) — минимальная сумма, не меньше 0.
  • max_discount_amount (number|null, необязательно) — верхняя граница скидки, не меньше 0.
  • total_usage_limit (integer|null, необязательно) — общий лимит, не меньше 1.
  • per_client_usage_limit (integer|null, необязательно) — лимит на клиента, не меньше 1.
  • audience (string, необязательно) — all, new_clients, partners или custom.
  • starts_at (string|null, необязательно) — начало действия.
  • ends_at (string|null, необязательно) — окончание действия, не раньше starts_at.
  • url (string|null, необязательно, до 2048 символов) — собственная ссылка.
  • description (string|null, необязательно) — описание.

Пример запроса

{
  "name": "500 ₽ на заказ от 5000 ₽",
  "code": "SAVE-500",
  "discount_type": "fixed",
  "discount_value": 500,
  "application_id": 17,
  "min_order_amount": 5000,
  "total_usage_limit": 300,
  "per_client_usage_limit": 1,
  "audience": "all"
}

Ответ

При успешном создании API возвращает HTTP 201 с объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 422 — код занят, формат неверен, процент больше 100 либо связанный объект принадлежит другому проекту.

Получение промокода

Метод
GET
URL
https://api.gigma.ru/api/discounts/{discount}
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Возвращает промокод и до 20 последних применений в latest_usages.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Ответ

{
  "discount": {
    "id": 42,
    "application_id": 17,
    "name": "10% на первый заказ",
    "code": "WELCOME10",
    "status": "active",
    "effective_status": "active",
    "discount_type": "percent",
    "discount_value": 10,
    "total_used_count": 24,
    "latest_usages": [
      {
        "id": 301,
        "order_id": 9001,
        "counterparty_id": 701,
        "code": "WELCOME10",
        "original_amount": 2500,
        "product_discount_amount": 0,
        "discount_amount": 250,
        "final_amount": 2250,
        "status": "confirmed",
        "used_at": "2026-08-20T10:00:00.000000Z"
      }
    ]
  }
}

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.

Частичное изменение промокода

Метод
PATCH
URL
https://api.gigma.ru/api/discounts/{discount}
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}; Content-Type: application/json
Успешный ответ
200

Изменяет только переданные поля.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Параметры запроса

Поля те же, что при создании, но все необязательны. При одновременном изменении процентного типа и значения передавайте оба поля.

Пример запроса

{
  "discount_type": "percent",
  "discount_value": 15,
  "ends_at": "2026-10-31T23:59:59+03:00"
}

Ответ

HTTP 200 с обновлённым объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.
  • 422 — данные не прошли валидацию или код конфликтует с другой неархивированной записью.

Изменение промокода через PUT

Метод
PUT
URL
https://api.gigma.ru/api/discounts/{discount}
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}; Content-Type: application/json
Успешный ответ
200

Поддерживается для совместимости и использует тот же частичный контракт, что PATCH.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Параметры запроса

Те же поля, что в PATCH /api/discounts/{discount}.

Ответ

HTTP 200 с обновлённым объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.
  • 422 — данные не прошли валидацию.

Постановка промокода на паузу

Метод
POST
URL
https://api.gigma.ru/api/discounts/{discount}/pause
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Меняет status на paused и заполняет paused_at.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Ответ

HTTP 200 с обновлённым объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.

Активация промокода

Метод
POST
URL
https://api.gigma.ru/api/discounts/{discount}/activate
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Меняет status на active, очищает paused_at и archived_at.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Ответ

HTTP 200 с обновлённым объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.
  • 422 — другой неархивированный промокод проекта уже использует тот же код.

Архивирование промокода

Метод
POST
URL
https://api.gigma.ru/api/discounts/{discount}/archive
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Меняет status на archived и заполняет archived_at. Журнал сохраняется, а код можно повторно использовать в новой записи.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Ответ

HTTP 200 с обновлённым объектом discount.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.

Удаление промокода

Метод
DELETE
URL
https://api.gigma.ru/api/discounts/{discount}
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
204

Метод не удаляет запись физически: он архивирует промокод, чтобы сохранить журнал.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Ответ

Успешный ответ — HTTP 204 No Content без тела.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.

Получение статистики промокодов

Метод
GET
URL
https://api.gigma.ru/api/discounts/stats
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Возвращает статистику проекта за выбранный месяц. В использования и экономию входят только записи confirmed.

Параметры запроса

  • period (string, необязательно) — месяц YYYY-MM, по умолчанию текущий.
  • application_id (integer, необязательно) — ограничить статистику одним приложением.

Ответ

{
  "period": "2026-08",
  "active_count": 4,
  "active_delta": 1,
  "usage_count_for_period": 127,
  "usage_delta_percent": 18.69,
  "saved_amount_for_period": 45890.5,
  "saved_delta_percent": 12.3
}
Описание полей ответа
  • active_count — количество кодов, активных сейчас с учётом дат.
  • active_delta — сколько кодов со статусом active создано в выбранном месяце.
  • usage_count_for_period — подтверждённые применения.
  • usage_delta_percent — изменение к предыдущему месяцу; может быть null.
  • saved_amount_for_period — подтверждённая сумма скидок.
  • saved_delta_percent — изменение к предыдущему месяцу; может быть null.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 422 — неверный период или приложение принадлежит другому проекту.

Получение применений промокода

Метод
GET
URL
https://api.gigma.ru/api/tables/discounts/{discount}/usages
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Возвращает табличную историю применений.

Параметры пути

  • discount (integer, обязательно) — идентификатор промокода.

Параметры запроса

  • page (integer, необязательно) — номер страницы.
  • per_page (integer, необязательно) — от 1 до 100, по умолчанию 10.
  • status (string, необязательно) — reserved, confirmed, canceled, all или список через запятую.
  • from (string, необязательно) — начало периода YYYY-MM-DD.
  • to (string, необязательно) — конец периода YYYY-MM-DD.
  • counterparty_id (integer, необязательно) — клиент.
  • order_id (integer, необязательно) — заказ.
  • application_id (integer, необязательно) — приложение.
  • query (string, необязательно, до 128 символов) — код, ID заказа или имя клиента.
  • sort_by (string, необязательно) — used_at, final_amount, discount_amount или original_amount.
  • sort_dir (string, необязательно) — asc или desc.

Ответ

{
  "columns": [],
  "discount_usages": [
    {
      "id": 301,
      "used_at": "2026-08-20T10:00:00.000000Z",
      "order": { "icon": null, "value": "ORD-9001", "link": "..." },
      "counterparty": { "icon": null, "value": "Иван Петров", "link": "..." },
      "application": { "icon": null, "value": "Интернет-магазин", "link": "..." },
      "code": "WELCOME10",
      "original_amount": 2500,
      "product_discount_amount": 0,
      "discount_amount": 250,
      "final_amount": 2250,
      "status": "confirmed"
    }
  ],
  "pagination": {
    "total": 1,
    "per_page": 10,
    "current_page": 1,
    "last_page": 1,
    "from": 1,
    "to": 1
  }
}

reserved — заказ с оплатой картой создан, но платёж не подтверждён. После оплаты запись становится confirmed. При ошибке платежа применение получает canceled, а общий счётчик уменьшается.

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — промокод не найден либо относится к другому проекту.
  • 422 — фильтры не прошли валидацию.

Применение на витрине

Предварительный расчёт

POST /api/counterparty/orders/precalculate принимает App Token и необязательное поле promo_code.

{
  "products": [
    { "id": 34780, "quantity": 2 }
  ],
  "promo_code": "welcome10"
}

Успешный ответ содержит:

{
  "original_price": 2500,
  "product_discount_amount": 0,
  "promo_discount_amount": 250,
  "total_discount_amount": 250,
  "final_price": 2250,
  "applied_discount": {
    "id": 42,
    "code": "WELCOME10",
    "name": "10% на первый заказ",
    "type": "percent",
    "value": 10
  },
  "discount_error": null
}

Если код нельзя применить, публичный расчёт не раскрывает точную причину:

{
  "applied_discount": null,
  "discount_error": {
    "reason": "Промокод не применим.",
    "code": 2099
  }
}

Создание заказа

POST /api/counterparty/orders принимает App Token, Bearer клиента и поле promo_code. Код повторно проверяется в транзакции, поэтому успешный предварительный расчёт не гарантирует создание заказа: лимит мог закончиться, а аудитория — зависеть от клиента.

Успешный заказ возвращает promo_code, original_price, product_discount_amount, promo_discount_amount, total_discount_amount и final_price.

Если промокод обнулил заказ с оплатой банковской картой, Gigma не создаёт платёж на 0 ₽: заказ сразу переводится в оплаченный статус, а применение подтверждается.

Ошибки применения

Создание заказа возвращает HTTP 422 с message и числовым code:

КодПричина
2001промокод не найден
2002статус не active
2003период ещё не начался
2004срок закончился
2005сумма меньше min_order_amount
2006общий лимит исчерпан
2007клиент исчерпал свой лимит
2008код только для новых клиентов
2009аудитория partners не настроена
2010аудитория custom не поддерживается
2011код относится к другому приложению

При ошибке платёжной системы после создания заказа применение отменяется, а ответ 502 сообщает, что промокод не был списан.

Ссылки промокода

  • share_path имеет вид ?ref=<CODE>.
  • magic_link равен url, если оно задано.
  • Если url не задан, Gigma собирает ссылку из настроенного frontend URL и share_path; без frontend URL поле равно null.

Получение ref из адресной строки и подстановка его в форму — ответственность frontend. API применяет код только после передачи promo_code.

Старый справочник промоакций

/api/promotions — read-only справочник и не является API управления промокодами. Он сохранён для совместимости старых административных экранов. Пошаговый сценарий находится в разделе «Промоакции».

GET /api/promotions/{promotion} работает без общей проверки проекта. Не используйте его как источник промокодов и не перебирайте идентификаторы.

Получение справочника промоакций

Метод
GET
URL
https://api.gigma.ru/api/promotions
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Параметры запроса

  • query (string, необязательно) — поиск по названию.

Ответ

{
  "promotions": [
    {
      "id": 1,
      "name": "Промоакция",
      "avatar": "https://api.gigma.ru/storage/uploads/default.svg",
      "created_at": "2024-07-27T18:13:40.000000Z"
    }
  ],
  "promotionsCount": 1
}

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.

Получение элемента справочника промоакций

Метод
GET
URL
https://api.gigma.ru/api/promotions/{promotion}
Авторизация
Bearer сотрудника
Headers
Authorization: Bearer {token}
Успешный ответ
200

Параметры пути

  • promotion (integer, обязательно) — идентификатор записи.

Ответ

{
  "promotion": {
    "id": 1,
    "name": "Промоакция",
    "avatar": "https://api.gigma.ru/storage/uploads/default.svg",
    "created_at": "2024-07-27T18:13:40.000000Z"
  }
}

Возможные ошибки

  • 401 — Bearer сотрудника отсутствует или недействителен.
  • 404 — запись не найдена.

Что дальше