☰ Меню · POS и кассы
Руководства

POS и кассы

Всё, что нужно кассе: найти гостя, провести чек, списать кэшбэк, обработать возврат и выпустить карты с номерами самой кассы.

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

1. Найдите гостя

GET /v1/members/{member} принимает то, что есть у кассы: номер карты, содержимое штрихкода, телефон в международном формате или наш id участника. Телефон кодируйте для URL (+ превращается в %2B).

curl
# by the number on the card (or the barcode the scanner read)
curl https://loyaltyfy.io/api/v1/members/2000111703779 \
  -H "Authorization: Bearer sk_test_..."

# by phone, with fresh add-to-wallet links
curl "https://loyaltyfy.io/api/v1/members/%2B37366791102?expand=wallet" \
  -H "Authorization: Bearer sk_test_..."

Покажите кассиру то, что важно для программы, из объекта участника:

program.mechanicЧто показать
cashbackcashback.balance и максимум к списанию: меньшее из баланса и redeem_max_percent от суммы чека.
stampsstamps.current из stamps.required и stamps.rewards_available, если гостя ждёт бесплатная позиция.
visit_discount, vipdiscount.percent и discount.tier: эту скидку к чеку касса применяет сама.

2. Что зашито в штрихкод карты

Формат штрихкода заведение выбирает в разделе Разработчикам → Штрихкод карты, чтобы он подходил сканеру кассы:

ФорматСодержимое штрихкодаКогда использовать
QR с номером картыcard_number, например 2000111703779Сканер читает QR, а касса ищет по номеру карты. Рекомендуем.
Штрихкод (Code 128) с номером картыТот же номер в виде линейного штрихкода, напечатан под полоскамиСтарые лазерные сканеры, которые не читают QR.
QR для кассы LoyaltyfyНаш внутренний серийный номер (UUID)По умолчанию. Его читает только кассовое приложение Loyaltyfy, но POS всё равно может найти гостя по телефону.

При смене формата карты, которые уже лежат в Wallet у гостей, обновляются сами, ничего переустанавливать не нужно. В любом формате передавайте отсканированное значение как есть в GET /v1/members/{member} или в POST /v1/scans: этот эндпоинт ещё и убирает префиксы сканера и переводы строк.

3. Выпускайте карты с номерами кассы

Если у кассы уже есть своя система карт, сохраните её номера. Регистрируйте гостя, передавая в card_number номер кассы: карта в Wallet покажет этот номер, а её штрихкод будет его содержать, так что сканер найдёт гостя и в собственной базе кассы. Номера уникальны в пределах заведения, на дубликат придёт 409 card_number_taken. Чтобы позже привязать номер кассы к существующему участнику, вызовите PATCH /v1/members/{member} с card_number.

curl
curl https://loyaltyfy.io/api/v1/members \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d1b2c4e-enrol-000123" \
  -d '{
    "phone": "+37366791102",
    "first_name": "Ion",
    "last_name": "Popescu",
    "card_number": "2000111703779",
    "language": "ro",
    "consent": true
  }'

В ответе есть wallet.apple_url и wallet.google_url. Напечатайте их QR-кодом на чеке, отправьте по SMS или покажите на дисплее покупателя: гость открывает ссылку, и карта добавляется в телефон.

4. Проведите чек

Отправляйте каждый закрытый чек один раз, после оплаты. amount это полная сумма чека до списания кэшбэка, redeem_amount это часть, которую гость оплатил кэшбэком. Правила программы (минимальный чек, лимит списания, уровни) применяются так же, как на экране кассира заведения.

curl
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHK-2026-000123",
    "member": "2000111703779",
    "amount": 25000,
    "currency": "MDL",
    "register_id": "POS-1",
    "closed_at": "2026-10-06T18:17:00+03:00",
    "line_items": [
      { "sku": "1001", "name": "Espresso", "quantity": 2, "unit_price": 4000, "category": "Coffee" },
      { "sku": "2005", "name": "Cheesecake", "quantity": 1, "unit_price": 17000, "category": "Desserts" }
    ]
  }'
Ответ · 201
{
  "object": "transaction",
  "id": "f8acbb16-0b3b-4afc-acbb-e5eabd4dd072",
  "external_id": "CHK-2026-000123",
  "status": "completed",
  "amount": 25000,
  "redeem_amount": 0,
  "cashback_earned": 1250,
  "stamps_earned": 0,
  "reward_unlocked": false,
  "net_amount": 25000,
  "currency": "MDL",
  "register_id": "POS-1",
  "closed_at": "2026-10-06T15:17:00+00:00",
  "livemode": false,
  "created_at": "2026-10-06T18:17:17.921+00:00",
  "voided_at": null,
  "member": {
    "object": "member",
    "id": "6dacfe0a-2500-44c6-a37c-05c660b093b4",
    "card_number": "2000111703779",
    "cashback": { "balance": 1250, "currency": "MDL", "earn_rate": 5, ... },
    ...
  }
}
external_id должен быть уникальным среди всех касс заведения. Если номера чеков начинаются заново на каждой кассе или в каждой смене, склеивайте их, например POS-3-000123 (касса 3, чек 123).

Кэшбэк начисляется на сумму, которую гость реально заплатил: amount − redeem_amount. В программах со штампами и визитами чек засчитывается как визит. Поля stamps_earned и reward_unlocked показывают результат, и касса может сказать: «Ещё один кофе, и следующий бесплатно».

5. Оплата кэшбэком

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

curl
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHK-2026-000124",
    "member": "2000111703779",
    "amount": 12000,
    "redeem_amount": 1250
  }'
Ответ · 402
{
  "error": {
    "type": "invalid_request_error",
    "code": "insufficient_balance",
    "message": "redeem_amount is more than the available balance.",
    "param": "redeem_amount",
    "available": 1250
  },
  "request_id": "req_6f4c30b8ca7d4423a9d4"
}
Давайте скидку кэшбэком только после нашего ответа 201 или 200. Отправляйте чек, когда сумма уже окончательная, но до приёма оплаты. Если ответа нет, повторите запрос с тем же external_id или закройте чек без кэшбэка. Иначе гость получит и скидку, и сохранит баланс.

6. Выдача награды за штампы

Если stamps.rewards_available больше нуля, у гостя есть бесплатная позиция. Выдайте её на кассе и зафиксируйте выдачу:

curl
curl -X POST https://loyaltyfy.io/api/v1/members/4285324286429/rewards/redeem \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: reward-4285324286429-0001"

7. Возвраты и ошибки

Отменяйте чек целиком по его external_id. Начисленный кэшбэк или штампы списываются обратно, списанный кэшбэк возвращается гостю. Частичный возврат: отмените чек и отправьте новый с исправленной суммой и новым external_id.

curl
curl -X POST https://loyaltyfy.io/api/v1/transactions/CHK-2026-000123/void \
  -H "Authorization: Bearer sk_test_..."

8. Если касса без связи

Складывайте чеки в локальную очередь вместе с external_id и closed_at и отправляйте, когда связь появится. Дубликаты игнорируются, поэтому всю очередь можно переотправлять без опаски. Для поиска гостя нужна сеть. Если её нет, пусть кассир проведёт чек без лояльности, а не останавливает продажу.

Чеки с сервера кассы

Некоторые POS не умеют вызывать API прямо на кассе, но могут отправлять закрытые чеки со своего сервера. Так тоже можно: сразу после закрытия чека отправьте с сервера тот же POST /v1/transactions с номером карты, который отсканировал кассир. Если кассе нужно показать результат позже, подпишитесь на transaction.completed.

Вопросы по интеграции: api@loyaltyfy.io. Отвечаем в течение одного рабочего дня.