☰ Меню · Правила и лимиты заведения
Руководства

Правила и лимиты заведения

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

Всё, что описано на этой странице, владелец заведения настраивает в разделе Разработчикам → Правила для касс. Правила действуют для всех ключей API заведения, а у отдельного ключа (одной кассы, одного киоска) можно задать свои значения для любого из них. Правила, по которым работает ваша интеграция, возвращает GET /v1/account в поле settings.

JSON
"settings": {
  "redemption": "hold",
  "hold_minutes": 30,
  "check_numbering": "per_register",
  "offline_max_hours": 72,
  "redeem_max_per_check": 50000,
  "redeem_max_per_day": null,
  "require_scan": true,
  "scan_window_minutes": 30,
  "allow_adjustments": false,
  "adjustment_max": 50000
}

Уровни кэшбэка

У программы с кэшбэком могут быть уровни, например: Базовый 5%, Серебро 7% со второго визита, Золото 10% от 1000.00 покупок. Уровень считается по визитам, по сумме покупок или по тому, что наступит раньше. Владелец задаёт уровни в программе лояльности. На стороне кассы ничего делать не нужно: каждый чек начисляет кэшбэк по уровню гостя после этого чека, а в каждом объекте участника видно, на каком уровне гость.

JSON
"cashback": {
  "balance": 10200,
  "held": 0,
  "available": 10200,
  "currency": "MDL",
  "earn_rate": 7,
  "level": "Silver",
  "level_basis": "any",
  "levels": [
    { "name": "Base", "earn_rate": 5, "min_visits": null, "min_spent": null },
    { "name": "Silver", "earn_rate": 7, "min_visits": 2, "min_spent": null },
    { "name": "Gold", "earn_rate": 10, "min_visits": null, "min_spent": 100000 }
  ],
  "next_level": { "name": "Gold", "earn_rate": 10, "visits_left": null, "spent_left": 80000 },
  ...
}

next_level.visits_left и next_level.spent_left можно сразу показывать гостю («Ещё 2 визита до уровня Серебро»). В display.receipt_lines эта строка уже есть на языке гостя.

Категории товаров

Один раз выгрузите товары с категориями, дальше отправляйте только изменения. Категории появятся в кабинете владельца, и для каждой можно задать: не начислять кэшбэк (табак, алкоголь), не принимать оплату кэшбэком (товары по акции) или начислять свой процент вместо уровня гостя (кофе 10%). Через API можно создавать и переименовывать товары и категории, но менять эти правила может только владелец.

curl
curl https://loyaltyfy.io/api/v1/catalog/products/bulk \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      { "sku": "4840001", "name": "Marlboro Gold", "category": "TOB", "category_name": "Tobacco", "price": 6500 },
      { "sku": "1001", "name": "Latte", "category": "COF", "category_name": "Coffee", "price": 4500 },
      { "sku": "2040", "name": "Zeama", "category": "KIT", "category_name": "Kitchen", "price": 6500 }
    ]
  }'

# { "object": "catalog_sync", "upserted": 3 }

Затем передавайте line_items с каждым чеком. В позиции может быть category или только sku: тогда категорию мы найдём в каталоге. Суммы позиций приводятся к сумме чека, поэтому скидки, сделанные на кассе, распределяются пропорционально. Позиции из категорий без правил начисляют кэшбэк по уровню гостя.

curl
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHK-2026-000130",
    "member": "2000111703779",
    "amount": 17500,
    "line_items": [
      { "sku": "4840001", "quantity": 1, "unit_price": 6500 },
      { "sku": "1001", "quantity": 1, "unit_price": 4500 },
      { "sku": "2040", "quantity": 1, "unit_price": 6500 }
    ]
  }'

# Tobacco earns nothing, Coffee has its own 10%, Kitchen earns at the guest's level (5%):
# 0 + 450 + 325 = "cashback_earned": 775

Оплата кэшбэком через резерв

В режиме В чеке (по умолчанию) касса передаёт redeem_amount вместе с закрытым чеком. В режиме Через резерв касса откладывает сумму, когда гость просит оплатить кэшбэком, а закрытый чек её подтверждает. Если чек так и не пришёл (оборвалась связь, продажу отменили), резерв снимается через время, заданное владельцем, и деньги остаются у гостя. Скидка никогда не выдаётся без списания с баланса.

  1. 1
    Создайте резерв

    POST /v1/holds с участником, суммой и суммой чека. Действуют те же ограничения, что и для чека. В ответе будет id резерва.

  2. 2
    Подтвердите

    POST /v1/transactions с hold_id. Не передавайте redeem_amount, чтобы списать весь резерв, или передайте меньшую сумму.

  3. 3
    Или снимите

    POST /v1/holds/{id}/release, если продажу отменили. Повторное снятие ничего не ломает.

curl
curl https://loyaltyfy.io/api/v1/holds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hold-R3-000131" \
  -d '{ "member": "2000111703779", "amount": 3000, "check_amount": 20000, "register_id": "R3" }'

# at payment: the check captures it
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "000131", "register_id": "R3", "member": "2000111703779",
        "amount": 20000, "hold_id": "5b0d6c1e-8f3a-4d7e-9a61-2c4b8e0f7d19" }'
Резервы учитываются везде: в cashback.available участника, в лимитах и в кассовом приложении самого заведения, поэтому одни и те же деньги нельзя потратить дважды.

Номера чеков по кассам

Если каждая касса нумерует чеки с 1, выберите У каждой кассы свои. Тогда register_id обязателен, а чек сохраняется как <register_id>:<external_id>, например R3:15. По этому составному id чек читают, отменяют и оформляют по нему возврат.

Чеки, отправленные позже

После сбоя касса досылает очередь. Если задан лимит, например 72 часа, чеки со closed_at старше этого срока отклоняются с check_too_old. Так касса, восстановленная из старой резервной копии, не завалит заведение устаревшими чеками. closed_at больше чем на 10 минут в будущем отклоняется всегда.

Лимиты списания

Сверх доли чека, заданной в программе, владелец может ограничить кэшбэк за один чек и на одного гостя в день (по всем кассам, по часовому поясу заведения). Если чек просит больше, списывается разрешённая сумма, а в ответе будет фактический redeem_amount. Резерв на большую сумму отклоняется с limited_by. Чтобы заранее показать гостю точную сумму, используйте расчёт чека.

Обязательный скан карты

С этим правилом чек, визит, резерв или награда по карте принимаются, только если тот же ключ незадолго до этого отправил эту карту в POST /v1/scans. Номер карты, введённый на кассе вручную, отклоняется с scan_required. Отправьте сырой вывод сканера в /v1/scans, дальше работайте как обычно.

Ручные корректировки

По умолчанию выключены. Если владелец их разрешил, ключ с правом members:adjust может начислить или снять кэшбэк с указанием причины, в пределах лимита на одну корректировку, заданного владельцем. Каждая корректировка попадает в историю участника и отправляет событие balance.adjusted.

curl
curl https://loyaltyfy.io/api/v1/members/2000111703779/adjustments \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "ADJ-2026-0009", "amount": 2000, "reason": "Goodwill after a complaint" }'

# a negative amount takes cashback away
Вопросы по интеграции: api@loyaltyfy.io. Отвечаем в течение одного рабочего дня.