Внешняя система лояльности

Страница для разработчиков сервисов лояльности (UDS, Premium Bonus, свои системы баллов), которые хотят работать в заведениях на Mison. Заведение продаёт в Mison, а баллы гостей ведёт ваш сервис: касса показывает баланс из вашей системы, списание баллов резервирует у вас, продажи отправляет вам на начисление.

В отличие от остальных разделов API здесь Mison вызывает вас: вы реализуете у себя пять методов по этому контракту, заведение указывает в кабинете их адрес.

Подключение

Заведение в кабинете: Лояльность → Внешняя система лояльности. Указывает:

  • название — его увидит кассир («UDS»);
  • адрес API — базовый URL ваших методов, например https://api.example.com/mison/v1. Mison добавит к нему имя метода: …/mison/v1/customer;
  • секрет подписи — Mison генерирует его сам, заведение передаёт его вам.

Там же — кнопка проверки (метод ping), поиск гостя по телефону и журнал всех операций с ответами вашего сервиса.

Пока подключение включено, своя лояльность Mison на этом бренде не начисляет и не списывает баллы: нет начисления за чек, бонуса за регистрацию, кэшбэка по QR-коду на чеке.

Запросы

Все методы — POST с JSON-телом на {адрес API}/{метод}:

POST https://api.example.com/mison/v1/hold
Content-Type: application/json
X-Mison-Request-Id: 0b7c6f1e-2d4a-4b8e-9f3a-5c6d7e8f9a0b
X-Mison-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
  • X-Mison-Signature — HMAC-SHA256 сырого тела запроса секретом подписи, hex, с префиксом sha256=. Проверяйте её на каждом запросе — так же, как подпись вебхуков.
  • X-Mison-Request-Id — uuid операции, он же operation_id в теле. Повтор с тем же id не должен списывать или начислять второй раз — отдайте результат первого раза. Mison повторяет запросы при обрыве связи.

Отвечайте JSON. Код ответа решает, что будет дальше:

ОтветДля Mison
2xxОперация выполнена
4xxОтказ по существу: баллов не хватает, гость не найден, операция невозможна. Не повторяется. Текст из message увидит кассир
5xx, таймаутСбой. Синхронные методы — касса покажет «сервис не ответил», очередь — повторит
{ "message": "Недостаточно баллов: доступно 1 200" }

Таймаут — 5 секунд. customer и hold ждёт кассир у кассы, отвечайте быстро.

Как идёт продажа

  1. Кассир вводит телефон гостя или код из вашего приложения → customer. Касса показывает баланс и даёт оплатить баллами не больше него.
  2. Кассир закрывает чек с оплатой баллами → hold: зарезервируйте сумму. Отказали — чек этой оплатой не закроется, кассир выберет другую.
  3. Чек закрыт и дошёл до облака → commit: спишите резерв и начислите баллы за покупку. Отправляется очередью: не ждёт кассира и повторяется, пока вы не ответите.
  4. Чек удалили → cancel. Вернули часть товара → refund.

Оплата баллами в Mison — это оплата чека (как наличные или карта), а не скидка: в отчётах заведения баллы видны отдельным способом оплаты.

ping

Проверка из кабинета: адрес доступен, подпись проверяется.

{ "operation_id": "…", "message": "Проверка подключения Mison" }

Ответ — любой 2xx.

customer

Найти гостя. Приходит телефон (цифры, с кодом страны) или код из вашего приложения — кассир вводит его в то же поле, что и номер карты.

{
  "point_id": "c91fdd54-a539-44ab-8a8f-103838d31388",
  "phone": "998901234567"
}
{ "point_id": "c91fdd54-…", "code": "482913" }

Ответ:

{
  "customer_id": "u-58123",
  "name": "Азиз",
  "phone": "998901234567",
  "balance": 12500,
  "level": "Золото",
  "can_redeem": true
}
ПолеОписание
customer_idОбязательно. Ваш id гостя — его Mison передаст в hold, commit, refund
phoneОбязательно при поиске по коду: по телефону Mison находит или заводит своего клиента
balanceБаланс в баллах; 1 балл = 1 сум оплаты
can_redeemfalse — касса покажет баланс, но оплатить баллами не даст
levelУровень гостя — показывается кассиру

Гость не найден — 404. Отвечайте 200 с новым customer_id, если ваш сервис заводит гостя при первом визите.

hold

Зарезервировать баллы при закрытии чека.

{
  "operation_id": "6f1c2d5e-8a7b-4c3d-9e0f-1a2b3c4d5e6f",
  "customer_id": "u-58123",
  "amount": 5000,
  "point_id": "c91fdd54-…",
  "cheque": {
    "uuid": "a3f9c1e2-…",
    "total": 84000,
    "lines": [
      { "product_id": 1201, "name": "Плов", "quantity": 2, "price": 35000, "amount": 70000 }
    ]
  }
}

Ответ — id резерва:

{ "hold_id": "h-99812" }

Если правила сервиса не дают списать столько (например, не больше 50 % чека) — 400 с message: кассир увидит его и уменьшит сумму. Кассир может закрыть тот же чек повторно с другой суммой — тогда Mison отменит прежний резерв методом cancel.

commit

Чек закрыт. Спишите резерв и начислите баллы за покупку.

{
  "operation_id": "…",
  "customer": { "customer_id": "u-58123", "phone": "998901234567" },
  "hold_id": "h-99812",
  "redeemed": 5000,
  "cheque": {
    "uuid": "a3f9c1e2-…",
    "number": 1452,
    "point_id": "c91fdd54-…",
    "opened_at": "2026-10-25 19:04:11",
    "closed_at": "2026-10-25 20:31:40",
    "total": 84000,
    "goods_total": 70000,
    "discount": 0,
    "lines": [
      { "line_id": 501233, "product_id": 1201, "name": "Плов", "quantity": 2, "price": 35000, "amount": 70000 }
    ],
    "payments": [
      { "type": "points", "amount": 5000 },
      { "type": "card", "amount": 79000 }
    ]
  }
}
ПолеОписание
redeemedСколько баллов списать. Обычно равно резерву; hold_id может быть null, если касса старой версии закрыла чек без резерва — тогда спишите напрямую или откажите 4xx
cheque.totalИтог к оплате с обслуживанием и доставкой
cheque.goods_totalСумма товаров
cheque.linesТовары. Обслуживание и доставка строками не передаются — они в total
cheque.paymentscash, card, points, other. Начисляйте с той части, которую считаете базой — обычно без points
customer.customer_idМожет быть null, если гостя привязали к чеку после продажи (QR-код на чеке) — ищите по phone

Ответ — сколько начислено (Mison покажет это в чеке и отчётах):

{ "accrued": 3950, "balance": 11450 }

commit приходит и без оплаты баллами — когда гость просто указан в чеке: тогда redeemed = 0, hold_id = null.

cancel

Отмена ранее выполненной операции: чек удалён после закрытия (отмена commit — верните списанное, снимите начисленное) или резерв больше не нужен (отмена hold).

{
  "operation_id": "…",
  "ref_operation_id": "6f1c2d5e-…",
  "ref_operation": "commit",
  "hold_id": "h-99812"
}

ref_operation_id — operation_id отменяемой операции. Ответ — любой 2xx; если её у вас нет, тоже 2xx.

refund

Возврат части или всего чека. Деньги за возвращённые товары заведение отдаёт гостю целиком — включая долю, оплаченную баллами. Поэтому снимите начисление за возвращённое, а списанные баллы не возвращайте.

{
  "operation_id": "…",
  "ref_operation_id": "…",
  "customer": { "customer_id": "u-58123", "phone": "998901234567" },
  "amount": 35000,
  "lines": [
    { "product_id": 1201, "quantity": 1, "amount": 35000 }
  ]
}

ref_operation_id — operation_id исходного commit. Ответ — 2xx, по желанию { "revoked": 1975, "balance": 9475 }.

Повторы

commit, cancel и refund Mison повторяет через 1, 5, 30 минут, 2, 6 и 12 часов, пока не получит 2xx или 4xx. После этого операция помечается недоставленной, и заведение может отправить её снова из журнала в кабинете. Дедуплицируйте по operation_id.