API «Чат-Карты» для своей интеграции
Документ для разработчиков, которые подключают к сервису «Чат-Карта» учётную или кассовую систему: 1С:Розница или другую конфигурацию 1С, товароучётную программу, кассовое ПО или собственный бэкенд. Интеграция идёт только через это открытое API, готового расширения «для всех» нет: у каждой базы свои доработки.
С тарифа «Базовый» доступны примеры кода для 1С:Розница из папки
onec/ (выдаём с тарифа «Базовый»). Они реализуют ровно этот
API, их встраивают в свою конфигурацию как образец.
Условия подключения: самостоятельно по API — 0 ₽; интеграция под ключ — цена индивидуально; при оплате тарифа «Бизнес» или «Сеть / PRO» от 6 месяцев помощь с интеграцией бесплатна.
Машиночитаемые спецификации OpenAPI 3.1:
openapi-pos.yaml— методы сервиса, которые вызывает ваша система (/pos/*);openapi-accounting.yaml— методы, которые должна реализовать ваша система.
Содержание
- Как устроена интеграция
- Подключение
- Общие правила
- Коды на кассе
- Часть A.
Методы сервиса для вашей системы (
/pos/*) - Часть B. Методы, которые реализует ваша система
- Настройки программы лояльности
- Тарифы и функции
- Вебхуки
- Типовые сценарии на кассе
- Проверка интеграции
- Чек-лист перед запуском
Как устроена интеграция
Покупатель (MAX / Telegram) Ваша система (учёт, касса)
│ mini-app ▲ │
▼ │ │
┌──────────────────────────┐ Часть B: сервис → вы │ │ Часть A: вы → сервис
│ Чат-Карта │ ───────────────────────┘ │ /pos/*
│ app.chat-card.ru │ ◄──────────────────────────┘
└──────────────────────────┘
Ответственность разделена так:
| Что | Где хранится и кто отвечает |
|---|---|
| Клиенты, карты, баланс баллов, начисление и списание | Ваша система. Сервис баланс не хранит, только кэширует для показа |
| Привязка карты к аккаунту MAX или Telegram, согласие на рассылку | Сервис |
| Настройки программы (уровни, день рождения, автовозврат, сгорание, подарки, история, оценка визита, «Приведи друга», новый статус) | Сервис. Ваша система читает их и может менять |
| Рассылки, поздравления, напоминания о сгорании | Сервис отправляет через ботов. Баллы по этим сценариям начисляет ваша система по запросу сервиса |
| Одноразовые коды списания и подарков | Выдаёт сервис, касса гасит их через API |
| Покупки для статистики, RFM-сегментов, оценок визита и наград за друзей | Ваша система присылает после каждого чека |
| Статусы (уровни) карт | Ваша система. Сервис узнаёт о смене из card_status в
покупке или POST /pos/card-status |
| Оценки визитов, приглашения друзей, сегменты | Сервис. Ваша система может забрать их через
/pos/reviews, /pos/referrals,
/pos/segments или вебхуки |
Интеграция двусторонняя:
- Часть A. Ваша система ходит в сервис: гасит одноразовые коды, присылает покупки и статусы карт, синхронизирует точки и настройки, создаёт рассылки, забирает оценки визитов, приглашения и сегменты. Нужен ключ кассы.
- Часть B. Сервис ходит в вашу систему. Так он регистрирует клиента и выпускает карту, показывает баланс и историю, начисляет баллы по сценариям (день рождения, автовозврат, «Приведи друга», отзыв, новый статус), списывает баллы за купоны партнёров, узнаёт, у кого скоро сгорят баллы. Дополнительно сервис может сообщать о событиях вебхуками. Ваша система должна быть доступна из интернета по HTTPS.
Минимальная интеграция, при которой работает карта в приложении: из
части B нужны POST /register, GET /balance,
GET /welcome-bonus, GET /statuses; из части A
— POST /pos/redeem/consume. Остальные методы включают
дополнительные возможности, список в чек-листе.
Подключение
Организация регистрируется на chat-card.ru и в кабинете открывает раздел «Подключение». Этот документ опубликован на сайте: chat-card.ru/developers.
Адрес вашей системы для части B. Блок «1С:Розница» с полями «Адрес HTTP-сервиса», «Пользователь 1С» и «Пароль» подходит для любой системы, не только для 1С. Адрес — это база вашего HTTP-сервиса, например
https://erp.example.ru/hs/loyalty. Сервис добавляет к нему пути (/balance,/register, …). Требования:- только
https://с действующим сертификатом; - адрес должен быть виден из интернета, внутренние адреса (10.x, 192.168.x и т. п.) не принимаются;
- пароль хранится зашифрованным и после сохранения не показывается.
Кнопка «Проверить связь» делает
GET <адрес>/welcome-bonusс Basic-авторизацией и показывает результат.- только
Ключ кассы для части A. Там же нажмите «Выпустить ключ кассы» и получите строку вида
ck_…. Ваша система передаёт её в заголовкеX-Api-Key. Ключ определяет организацию: все данные и коды привязаны к ней. Новый ключ отменяет старый.Адрес сервиса:
https://app.chat-card.ru. Все методы части A начинаются с/pos/.
Пока адрес вашей системы не указан, сервис работает в демо-режиме со встроенной заглушкой. Так можно проверить mini-app и кабинет до готовности интеграции.
Общие правила
Формат.
- JSON в UTF-8 без BOM, заголовок
Content-Type: application/json. - Если ваша система по ошибке отдаёт BOM в начале тела, сервис его пропускает.
- Даты: ISO 8601. Дата и время — со смещением
(
2026-10-24T10:00:00+03:00), дата без времени —YYYY-MM-DD. - Суммы и баллы — числа (не строки), дробная часть через точку.
Телефон везде приводится к виду
79991234567: 11 цифр, первая 7, без
+.
Ошибки части A. Код HTTP и тело
{"error": "<код>"}. При ошибках проверки данных
добавляется errors: массив понятных текстов на русском, их
можно показать пользователю.
| HTTP | error |
Что значит |
|---|---|---|
| 400 | invalid, bad_request,
bad_purchase, too_many |
Неверные данные. Повтор с теми же данными не поможет |
| 403 | forbidden |
Нет ключа, ключ неверный или отозван, организация не активна (например, не оплачена) |
| 403 | plan |
Функция не входит в тариф организации, текст в message.
См. Тарифы и функции |
| 404 | not_found |
Объект не найден или принадлежит другой организации |
| 409 | already_sent |
Действие невозможно в текущем состоянии |
| 410 | token_invalid_or_expired |
Одноразовый код истёк, уже использован или выдан для другой организации |
| 5xx | — | Временная ошибка сервиса, повторите позже |
Повторы. При сетевой ошибке, 5xx и 429 повторяйте
запрос с паузой. При 400, 403, 404, 409, 410 не повторяйте.
POST /pos/purchases можно повторять сколько угодно, дублей
не будет. Погашение кодов (…/consume) повторять нельзя: код
уже погашен, повтор вернёт 410. Если ответ потерялся, попросите
покупателя показать новый код.
Таймауты. Сервис ждёт ответа вашей системы 5
секунд. Отвечайте быстро: тяжёлые расчёты (например, сгорание
по всей базе для /expiring) кэшируйте.
Коды на кассе
Сканер кассы может считать четыре вида кодов. Префикс говорит, что делать:
| Код | Вид | Откуда | Что делает касса |
|---|---|---|---|
Номер карты, например 2200000000015 (EAN-13 или ваш
формат) |
штрих-код | пластиковая карта | Находит карту у себя. Начислять можно, списывать нельзя |
LC + номер карты: LC2200000000015 |
Code-128 | экран карты в mini-app | Отрезает LC, находит карту. Начислять можно, списывать
нельзя. Покупка считается «картой из приложения» |
LR + 14 цифр: LR48201937465012 |
QR | кнопка «Списать баллы» в mini-app | Гасит код через POST /pos/redeem/consume и получает
номер карты. В этом чеке разрешено списание. Код живёт
60 секунд |
LG + 14 цифр: LG90318274650193 |
QR | вкладка «Подарки» в mini-app | Гасит код через POST /pos/gifts/consume, выдаёт подарок
и списывает его стоимость в баллах. Код живёт 15 минут |
Списание только по одноразовому коду защищает от оплаты баллами по чужому скриншоту карты: код одноразовый и быстро истекает. Для QR на кассе нужен 2D-сканер. Под QR код показан и цифрами для ручного ввода.
Регулярные выражения: ^LC\w{1,40}$,
^LR\d{14}$, ^LG\d{14}$.
Часть A. Методы
сервиса для вашей системы (/pos/*)
Базовый адрес https://app.chat-card.ru, заголовок
X-Api-Key: ck_… обязателен во всех запросах.
| Метод | Назначение |
|---|---|
GET /pos/plan |
Тариф, лимиты, включённые функции |
POST /pos/redeem/consume |
Погасить код списания LR… |
POST /pos/gifts/consume |
Погасить код подарка LG… |
GET /pos/gifts |
Каталог подарков |
POST /pos/purchases |
Сообщить о покупке или возврате |
POST /pos/card-status |
Сообщить новые статусы карт |
GET /pos/reviews |
Оценки визитов после покупки (PRO) |
GET /pos/referrals |
«Приведи друга»: приглашения и награды (PRO) |
GET /pos/segments |
RFM-сегменты карт (PRO) |
GET /pos/loyalty-settings |
Прочитать настройки программы |
PUT /pos/loyalty-settings |
Изменить настройки программы |
GET /pos/sources, PUT /pos/sources |
Точки регистрации (магазины, QR) |
POST /pos/campaigns |
Создать push-рассылку |
GET /pos/campaigns/{id} |
Статус и статистика рассылки |
POST /pos/campaigns/{id}/cancel |
Отменить рассылку |
GET /pos/plan
Тариф организации, его лимиты и включённые функции, а также текущее использование. Удобно показать в своей форме, что доступно, и не вызывать методы, которые ответят 403. Для дополнительного юрлица или базы действует тариф основной организации.
200
{
"plan": {
"id": "business", "name": "Бизнес", "tagline": "Оптимальный",
"price": { "1": 4990, "6": 4490, "12": 3990 },
"limits": { "cards": null, "designs": null, "sources": 5, "campaigns_per_month": null, "users": 5, "orgs": 1 },
"features": { "push_filters": true, "push_schedule": true, "segments": false, "history": true, "gifts": true,
"birthday": true, "winback": true, "review": false, "referral": false, "webhooks": true, "...": "..." }
},
"usage": { "cards": 812, "sources": 3, "campaigns": { "month": "2026-10", "used": 6, "limit": null } }
}null в limits и
campaigns.limit — без ограничения. Список функций — в
разделе Тарифы и функции.
POST /pos/redeem/consume
Касса отсканировала QR LR…. Сервис гасит код и
возвращает карту, по которой в этом чеке разрешено списание баллов.
POST /pos/redeem/consume
X-Api-Key: ck_...
Content-Type: application/json
{ "token": "LR48201937465012", "register_id": "kassa-2", "store_id": "kazan-mega" }
register_id и store_id необязательны,
сервис только записывает их в журнал.
200
{ "card_code": "2200000000015", "phone": "79991234567", "platform": "max", "user_id": "123456789" }410 token_invalid_or_expired: код истёк
(60 с), уже погашен, неверного формата или выдан другой организации.
Попросите покупателя нажать «Списать баллы» ещё раз.
Код одноразовый: после ответа 200 его больше нет. Держите разрешение на списание только в текущем чеке. Сумму списания и проверку остатка делает ваша система.
POST /pos/gifts/consume
Касса отсканировала QR LG…. Сервис гасит код и сообщает,
какой подарок выдать и сколько баллов списать.
{ "token": "LG90318274650193", "register_id": "kassa-2", "store_id": "kazan-mega" }200
{
"card_code": "2200000000015",
"gift": { "id": 3, "name": "Кофе в подарок", "points": 150, "sku": "A-1" }
}sku— артикул, который компания указала в кабинете (может бытьnull). Если он есть, товар можно добавить в чек по нулевой цене или провести выдачу своим документом.- Спишите
gift.pointsбаллов с картыcard_codeв своей системе. - Достаточность баллов сервис проверил при выдаче кода. Перед списанием проверьте остаток ещё раз: за 15 минут его могли потратить.
410 — код недействителен. Повторное сканирование
того же кода тоже вернёт 410. 403 plan —
подарки за баллы не входят в тариф (они доступны с тарифа «Бизнес»).
GET /pos/gifts
Каталог подарков организации, включая скрытые. Подходит, например, чтобы сопоставить подарки с товарами.
200
{ "gifts": [
{ "id": 3, "name": "Кофе в подарок", "description": "Любой напиток 0,3 л",
"points": 150, "sku": "A-1", "image_asset_id": 12, "image_url": "/media/12",
"active": true, "sort": 0, "kind": "gift", "partner": "", "coupon_code": "" },
{ "id": 7, "name": "Скидка 20% в кофейне «Зерно»", "description": "", "points": 200, "sku": "",
"image_asset_id": null, "image_url": null, "active": true, "sort": 1,
"kind": "coupon", "partner": "Кофейня «Зерно»", "coupon_code": "ZERNO20" }
] }image_url указан относительно
https://app.chat-card.ru.
kind: gift — подарок, который выдаёт касса
по коду LG…; coupon — купон партнёра (тариф
«Сеть / PRO»). Купон на кассу не приходит: покупатель обменивает баллы
на промокод coupon_code в приложении, а баллы сервис
списывает у вас через POST /bonus/writeoff.
coupon_code есть только в этом методе, в приложении
промокод показывается после списания.
POST /pos/purchases
Сообщите о покупке после проведения чека с картой лояльности. Из этих данных строится статистика в кабинете («покупки картой из приложения») и сценарий автовозврата: клиент считается давно не покупавшим по дате последней присланной покупки.
{
"receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d",
"card_code": "2200000000015",
"amount": 6490.00,
"scanned_code": "LC2200000000015",
"store_code": "kazan-mega",
"purchased_at": "2026-10-19T14:05:00+03:00"
}| Поле | Обязательно | Описание |
|---|---|---|
receipt_id |
да | Уникальный ID чека, до 100 символов [A-Za-z0-9_.:/-].
Повтор с тем же ID не создаёт дубль |
card_code |
да | Номер карты, до 40 символов; префикс LC можно не
отрезать |
amount |
да | Сумма чека в рублях. Для возврата — отрицательная |
scanned_code |
нет | Что отсканировал кассир. Если начинается с LC или
LR, покупка считается «картой из приложения» |
source |
нет | app или card, если
scanned_code не передаёте. По умолчанию
card |
store_code |
нет | Код точки из /pos/sources, до 40 символов |
purchased_at |
нет | Время чека, по умолчанию время запроса |
card_status |
нет | Статус карты после этого чека, как
name в GET /statuses, до 100 символов. Если
чек повысил статус, на тарифе «Сеть / PRO» покупатель сразу получит
поздравление. См. POST /pos/card-status |
Можно отправлять пачкой до 500 чеков:
{ "purchases": [ {...}, {...} ] }.
200 { "recorded": true, "created": 1 },
где created — сколько чеков записано впервые.
400 bad_purchase с полем
index — номер неверной записи в пачке. Пачка не
записывается целиком.
Отправляйте покупки из очереди в фоне: закрытие чека не должно ждать сервис.
Для каждого нового чека (не повтора
receipt_id) сервис дополнительно: обновляет статус карты по
card_status, планирует просьбу оценить визит
(review) и, если это первая покупка приглашённого друга,
начисляет баллы за «Приведи друга» (referral). Поэтому не
меняйте receipt_id при повторной отправке: новый ID — это
новый чек.
POST /pos/card-status
Сообщите статусы карт, если они меняются в вашей системе не только
при чеке: регламентный пересчёт уровней, ручная смена, перевыпуск. Можно
одной картой { "card_code": "...", "status": "..." } или
пачкой до 1000:
{ "cards": [ { "card_code": "2200000000015", "status": "Золотая" }, { "card_code": "2200000000022", "status": "Серебряная" } ] }status — как name в вашем
GET /statuses, до 100 символов. Учитываются только карты,
подключённые к приложению.
200 { "changed": 2, "upgraded": 1 } ·
400 bad_card с index или
too_many.
Что делает сервис при изменении статуса:
- отправляет вебхук
card.status_changed(тариф «Бизнес» и выше); - если статус повысился и на тарифе «Сеть / PRO»
включён раздел
status_upgrade, бот поздравляет покупателя и, если для статуса заданы баллы, вызывает у васPOST /bonusсreason: "status_upgrade". Один раз на карту и статус: обратное понижение и повторное повышение второй раз баллы не дадут.
Какой статус старше, сервис узнаёт по
status_upgrade.order (от младшего к старшему), а если он
пуст — по cashback_percent из GET /statuses.
Первый известный сервису статус карты поздравления не даёт: это не
повышение, а начальное значение.
Если статус меняется только при проведении чека, отдельный вызов не
нужен: передайте card_status в POST /pos/purchases.
GET /pos/reviews
Оценки визитов после покупки (тариф «Сеть / PRO», раздел настроек
review). Через delay_min минут после чека из
POST /pos/purchases бот просит оценить визит от 1 до 5.
Оценки 1–3 уходят владельцу в кабинет, на почту (с телефоном покупателя)
и вебхуком review.negative, в интернет они не попадают.
Покупателю с оценкой 5 бот предлагает оставить отзыв в Яндекс Картах или
2ГИС и начисляет public_bonus баллов через ваш
POST /bonus (reason: "review").
GET /pos/reviews?cursor=1760873472000_381&max_rating=3
| Параметр | Описание |
|---|---|
cursor |
Значение next_cursor из прошлого ответа; при первой
синхронизации пустое (cursor=). Оценки идут в порядке
времени оценки: покупатель может оценить визит через несколько дней
после покупки, поэтому синхронизация по номеру оценки теряла бы такие
оценки |
since_id |
Устаревший вариант: последние 500 оценок с id больше
этого. Для новых интеграций используйте cursor |
max_rating |
Только оценки не выше этой, например 3 — только
плохие |
200 — до 500 оценок по времени оценки,
next_cursor для следующего запроса и has_more
(есть ещё — запросите сразу):
{ "reviews": [
{ "id": 381, "rating": 2, "comment": "Долго ждали на кассе", "amount_rub": 6490, "purchased_at": "2026-10-19T11:05:00.000Z",
"can_rate": false, "public": null, "card_code": "2200000000015", "receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d",
"store_code": "kazan-mega", "rated_at": "2026-10-19T11:31:12.000Z", "public_target": null }
], "next_cursor": "1760873472000_381", "has_more": false }Для оценки 5 в public приходит
{ "bonus": 100, "targets": [{ "id": "yandex", "title": "Яндекс Карты", "url": "https://…" }], "done": true },
где done — покупатель перешёл по ссылке, а
public_target — куда именно (yandex или
2gis). 403 plan — функция не
входит в тариф.
GET /pos/referrals
«Приведи друга» (тариф «Сеть / PRO», раздел referral). У
каждого покупателя в приложении своя ссылка-приглашение. Засчитывается
только друг, которого ещё не было в вашей системе
(created: true в POST /register). После первой
покупки друга на кассе (POST /pos/purchases на сумму от
referral.min_amount) сервис вызывает ваш
POST /bonus с reason: "referral" дважды: для
друга (friend_points, по умолчанию 300) и для пригласившего
(referrer_points). Ключи:
referral-friend-<id> и
referral-referrer-<id>.
GET /pos/referrals или
GET /pos/referrals?referrer_card=2200000000015
200
{ "referrals": [
{ "id": 17, "referrer_card": "2200000000015", "friend_card": "2200000000046", "status": "rewarded",
"receipt_id": "0012-000351", "created_at": "2026-10-12T08:00:00.000Z", "rewarded_at": "2026-10-14T15:20:00.000Z" }
] }status: registered — друг подключил карту,
покупки ещё не было; rewarded — баллы начислены обоим;
failed — ваш POST /bonus не прошёл, причина в
журнале кабинета. 403 plan.
GET /pos/segments
RFM-сегменты карт (тариф «Сеть / PRO»). Сервис считает их по покупкам
из POST /pos/purchases для карт, подключённых к приложению:
R — дней с последней покупки, F — число покупок, M — сумма. Сегмент
можно записать в свойство клиента у себя или использовать в рассылке
(audience.segment).
200
{
"segments": [ { "id": "vip", "title": "VIP", "hint": "Топ-20% по сумме покупок, покупали за последние 45 дней" }, "..." ],
"cards": [ { "card_code": "2200000000015", "segment": "vip_leaving", "recency_days": 63, "frequency": 9, "monetary_rub": 54200 } ]
}id |
Сегмент |
|---|---|
vip |
Топ-20% по сумме (и от 2 покупок), покупали за последние 45 дней |
vip_leaving |
Топ-20% по сумме, не покупали от 45 до 180 дней |
loyal |
3 покупки и больше, последняя — не позже 90 дней назад |
active |
2 покупки, последняя — не позже 90 дней назад |
new |
Подключили карту или впервые купили за последние 30 дней |
one_time |
Купили один раз от 30 до 180 дней назад |
sleeping |
Не покупали от 90 до 180 дней |
lost |
Не покупали больше 180 дней |
no_purchases |
Подключили карту больше 30 дней назад, покупок нет |
recency_days — null, если покупок нет.
403 plan.
GET /pos/loyalty-settings
Настройки программы лояльности. Сервис — главный источник настроек:
их меняют в кабинете или через PUT. Ваша система читает их,
чтобы считать начисление по уровням, задержку и срок жизни баллов, бонус
за регистрацию. Читайте раз в 5–15 минут и кэшируйте. Сервис всегда
отдаёт все разделы, отсутствующие значения заполняет значениями по
умолчанию. Разделы функций, которых нет в тарифе, приходят с
enabled: false, а в поле plan_features — какие
функции включены в тариф (как plan.features в GET /pos/plan).
200 — объект с разделами, полное описание в разделе Настройки программы лояльности.
PUT /pos/loyalty-settings
Изменить настройки, например со своей формы настроек. Передавайте только разделы, которые меняете. Остальные останутся как были. Внутри раздела тоже можно передать часть полей.
{ "birthday": { "enabled": true, "days_before": 3, "points": [ { "status": "", "amount": 300 }, { "status": "Золотая", "amount": 1000 } ] } }200 — полные настройки после сохранения.
400 invalid,
errors: ["День рождения, за сколько дней: целое число от 0 до 30"]
— ничего не сохранено. Включить функцию не из тарифа нельзя:
errors: ["«Приведи друга»: доступно на тарифе «Сеть / PRO» и выше"].
Выключить её и поменять тексты можно. Поле plan_features в
запросе игнорируется.
GET /pos/sources
Точки регистрации: города и магазины, у каждой своя QR-ссылка на mini-app. Клиент, который оформил карту по ней, записывается на эту точку.
200
{ "sources": [ { "code": "kazan-mega", "city": "Казань", "name": "ТЦ Мега", "active": true, "origin": "1c" } ] }origin: 1c — точку прислала ваша система,
cabinet — её завели в кабинете.
PUT /pos/sources
Создать или обновить точки по коду. Точек, которых нет в запросе,
запрос не касается. Чтобы скрыть точку, передайте
active: false.
{ "sources": [ { "code": "kazan-mega", "city": "Казань", "name": "ТЦ Мега", "active": true } ] }code:[A-Za-z0-9_-], до 40 символов, попадает в ссылкуhttps://max.ru/<бот>?startapp=src_<code>;name: до 120 символов, обязательно;city: до 80 символов;- не больше 1000 точек за запрос.
200 { "saved": 1 } ·
400 invalid с index и
errors · 400 plan — активных
точек получится больше, чем разрешает тариф
(limits.sources), ничего не сохранено.
Код точки приходит вам при регистрации клиента
(source_code в POST /register).
POST /pos/campaigns
Создать push-рассылку в MAX и Telegram. Сервис сам отправит её в
send_at, держать вашу систему включённой не нужно. Получат
её только клиенты, которые дали согласие на уведомления и не
заблокировали бота.
{
"title": "Скидка 20% на осеннюю коллекцию",
"body": "Только до воскресенья в магазинах Казани.",
"button_text": "Открыть карту",
"image_url": "https://example.ru/autumn.jpg",
"send_at": "2026-10-24T10:00:00+03:00",
"ends_at": "2026-10-27T23:59:00+03:00",
"audience": {
"platforms": ["max", "telegram"],
"statuses": ["Золотая"],
"sources": ["kazan-mega"],
"client_ids": ["2200000000015", "79991234567", "LC2200000000016"]
}
}Рассылка по RFM-сегменту (тариф «Сеть / PRO»):
{ "title": "Скучаем по вам", "body": "Дарим двойные баллы до конца недели.", "audience": { "segment": "vip_leaving" } }| Поле | Ограничения |
|---|---|
title |
обязательно, до 100 символов |
body |
обязательно, до 3000 символов, с картинкой до 900 (ограничение мессенджеров) |
button_text |
до 40 символов |
image_url |
только https:// |
send_at |
когда отправить; если не указано, отправка сразу |
ends_at |
до какого времени акция видна в ленте mini-app |
audience.platforms |
max, telegram; пусто — оба |
audience.statuses |
статусы карт, как в GET /statuses; пусто — все |
audience.sources |
коды точек; неизвестный код — ошибка 400 |
audience.client_ids |
до 5000: номера карт (можно с LC), телефоны в любом
виде, ID пользователей мессенджера |
audience.segment |
RFM-сегмент из GET /pos/segments, например
vip_leaving. Список карт сегмента берётся в момент создания
и заменяет client_ids |
Условия внутри audience складываются через «И»: статус и
точка и список клиентов.
Ограничения тарифа (ответ 400 invalid,
текст в errors):
- рассылок в месяц (по Москве): «Старт» — 2, «Базовый» — 4, дальше без ограничений. Считаются запланированные и отправленные;
send_atпозже чем через 5 минут (отправка по расписанию) и фильтрыstatuses,sources,client_ids— с тарифа «Бизнес»;segment— тариф «Сеть / PRO». Если в сегменте сейчас никого нет — 400 «В выбранном сегменте сейчас нет покупателей».
201
{ "id": 42, "status": "scheduled", "send_at": "2026-10-24T07:00:00.000Z", "recipients": 318 },
где recipients — сколько человек получат рассылку на
текущий момент. 400 invalid с
errors.
Рассылка создаётся сразу запланированной. Изменить её нельзя, только отменить и создать новую.
GET /pos/campaigns/{id}
200
{ "id": 42, "status": "sent", "send_at": "…", "sent_at": "…", "recipients": 318,
"stats": { "pending": 0, "sent": 301, "failed": 2, "blocked": 15 } }Возможные status: scheduled (ждёт времени),
sending, sent, cancelled. В
stats: blocked — пользователь заблокировал
бота, failed — мессенджер не принял сообщение.
POST /pos/campaigns/{id}/cancel
200 { "id": 42, "status": "cancelled" }
· 409 already_sent · 404
— рассылка другой организации или не найдена.
Часть B. Методы, которые реализует ваша система
Сервис вызывает их по адресу из кабинета, например
https://erp.example.ru/hs/loyalty/balance?card=…, с
заголовком
Authorization: Basic <логин:пароль в base64> и
Accept: application/json. Таймаут 5 секунд.
| Метод | Когда вызывается | Обязателен |
|---|---|---|
POST /register |
Покупатель оформляет карту в mini-app | да |
GET /balance |
Открытие карты, обновление баланса (кэш 90 с), выдача кода подарка | да |
GET /welcome-bonus |
Экран регистрации (кэш 5 мин), кнопка «Проверить связь» | да |
GET /statuses |
Кабинет: оформление карт и фильтр рассылок по статусам | да |
POST /bonus |
День рождения, автовозврат, «Приведи друга», отзыв, новый статус | если включены эти сценарии |
POST /bonus/writeoff |
Купон партнёра за баллы | если есть купоны партнёров |
GET /expiring |
Напоминание о сгорании баллов | если включено напоминание |
GET /history |
Вкладка «История» в mini-app (кэш 60 с) | если включена история |
GET /card |
Поиск карты по телефону | желательно, зарезервирован |
Если метод не нужен, отвечайте 404: сервис покажет «недоступно», но работать продолжит.
Объект card в ответах:
{ "code": "2200000000015", "id": "b3a1…", "owner_name": "Ольга Иванова", "kind": "Бонусная карта", "status": "Серебряная", "birth_date": "1990-05-17" }| Поле | Описание |
|---|---|
code |
Номер карты: его покажет приложение в штрих-коде и пришлёт касса. До
40 символов [A-Za-z0-9_] |
id |
Внутренний ID карты в вашей системе, строка |
owner_name |
Имя владельца для карты в приложении. Первое слово подставляется в
{имя} в поздравлениях |
kind |
Название вида карты, показывается, если организация не загрузила логотип |
status |
Статус или уровень карты. Совпадает с name в
/statuses: по нему выбирается оформление и фильтруются
рассылки |
birth_date |
YYYY-MM-DD или null |
POST /register
Покупатель поделился номером телефона в мессенджере, сервис проверил подпись номера. Найдите клиента по телефону или создайте нового и выпустите карту. Метод должен быть идемпотентным: повторный вызов с тем же телефоном возвращает ту же карту.
{
"phone": "79991234567",
"first_name": "Ольга",
"last_name": "Иванова",
"channel": "max",
"channel_user_id": "123456789",
"source_code": "kazan-mega",
"source_name": "Казань, ТЦ Мега",
"birth_date": "1990-05-17",
"card_kind": "Золотая"
}source_code, source_name,
birth_date, card_kind приходят, только если
известны. card_kind — вид (статус) карты из вашего
GET /statuses, который магазин выбрал для новых клиентов:
выпустите новую карту этого вида. Клиенту, у которого карта уже есть,
вид не меняйте. Пусто — вид по вашим настройкам. Дата рождения проверена
сервисом: настоящая дата, возраст от 5 до 120 лет. Записывайте её,
только если у клиента в вашей системе дата ещё пуста.
200
{ "card": { ... }, "created": true, "welcome_bonus": { "amount": 200, "status": "accrued" } }created— клиент или карта созданы этим вызовом.welcome_bonus—nullили начисленный бонус за регистрацию.status:accrued(уже на счёте) илиpending(начислится позже). Покупатель увидит «+200 приветственных баллов».
Правила бонуса за регистрацию, которые должна соблюдать ваша система:
- параметры берите из
welcomeвGET /pos/loyalty-settings:enabled,amount,lifetime_days,existing_clients; - бонус даётся один раз на клиента, по телефону;
- только за первое подключение: кто уже подключился в
MAX, в Telegram бонус не получает, и наоборот. Для этого храните
channel, в которых клиент подключался; - если
existing_clients = false, бонус получают только клиенты, созданные этим вызовом.
GET /balance
GET /balance?card=2200000000015
200
{
"balance": 850,
"available": 850,
"status": "Серебряная",
"active": true,
"cashback_percent": 5,
"cashback_total": 12850,
"birth_date": "1990-05-17",
"expiring": { "amount": 300, "date": "2026-11-02" },
"pending": 120
}| Поле | Описание |
|---|---|
balance |
Всего баллов на карте |
available |
Сколько можно потратить сейчас. По нему проверяется, хватает ли на подарок |
status |
Текущий статус карты (может меняться в вашей системе) |
active |
false — карта заблокирована, приложение покажет
«обратитесь в магазин» |
cashback_percent |
Процент начисления для этой карты или null |
cashback_total |
Сколько баллов начислено за всё время или null |
birth_date |
Дата рождения, YYYY-MM-DD или null. Если
она появилась у вас позже, сервис сохранит её для поздравлений |
expiring |
Ближайшее сгорание {amount, date} или
null. В приложении: «300 баллов сгорят 2 ноября» |
pending |
Баллы за покупки, которые ещё не начислены из-за задержки, или 0 |
404 — карта не найдена.
Если ваша система недоступна, приложение покажет последний известный баланс с пометкой «обновляем».
GET /welcome-bonus
200
{ "enabled": true, "amount": 200, "existing_clients": false, "lifetime_days": 30 }
Сервис показывает эти условия на экране регистрации («Дарим 200
баллов за регистрацию»). Если организация сохранила раздел
welcome в кабинете, на экране показываются условия из
кабинета. Начисление в любом случае делает ваша система в
POST /register.
GET /statuses
200 — массив верхнего уровня:
[ { "name": "Серебряная", "cashback_percent": 5 }, { "name": "Золотая", "cashback_percent": 10 } ]POST /bonus
Начислить баллы по сценарию сервиса: подарок на день рождения, баллы для возврата давно не покупавшего клиента, награда за приглашённого друга, за отзыв на картах или за новый статус карты.
{
"card": "2200000000015",
"amount": 300,
"lifetime_days": 14,
"reason": "birthday",
"key": "birthday-2026-2200000000015",
"comment": "Подарок на день рождения"
}| Поле | Описание |
|---|---|
amount |
Сколько баллов начислить, больше 0 |
lifetime_days |
Через сколько дней баллы сгорают, 0 — без срока |
reason |
birthday — день рождения, winback —
автовозврат, referral — «Приведи друга» (другу и
пригласившему), review — отзыв на картах после оценки 5,
status_upgrade — новый статус карты. Неизвестную причину
лучше принимать как обычное начисление, а не отвечать 400 |
key |
Ключ идемпотентности, до 200 символов. Обязательно храните ключи: при повторе с тем же ключом не начисляйте второй раз |
comment |
Текст для истории операций |
200
{ "accrued": true, "balance": 1150 }, при повторе ключа —
{ "accrued": true, "duplicate": true, "balance": 1150 }.
404 — карты нет. 409
card_inactive — карта заблокирована.
Сервис повторяет неудачный вызов до 5 раз при следующих запусках
планировщика (раз в 15 минут), всегда с тем же key.
Сообщение клиенту уходит только после успешного начисления.
Ключи по сценариям: referral-friend-<id> и
referral-referrer-<id> (id из
GET /pos/referrals), review-<id>
(id из GET /pos/reviews),
status-<статус>-<карта>. Для «Приведи друга»,
отзыва и нового статуса сервис не повторяет вызов по расписанию: если
ваша система не ответила, награда помечается неудачной, ошибка видна в
журнале кабинета, и баллы можно начислить вручную.
POST /bonus/writeoff
Списать баллы без кассы. Сейчас сервис вызывает его для купонов партнёров (тариф «Сеть / PRO»): покупатель обменивает баллы на промокод партнёра прямо в приложении, на кассу он не приходит.
{
"card": "2200000000015",
"amount": 200,
"reason": "coupon",
"key": "coupon-7-2200000000015-29342811",
"comment": "Купон: Кофейня «Зерно» −20%"
}| Поле | Описание |
|---|---|
amount |
Сколько баллов списать, больше 0 |
reason |
Сейчас только coupon |
key |
Ключ идемпотентности, до 200 символов. Храните ключи, как в
POST /bonus: повтор с тем же ключом не списывает второй
раз |
comment |
Текст для истории операций (type: "redeem" в
GET /history) |
200
{ "written_off": true, "balance": 650 }, при повторе ключа
—
{ "written_off": true, "duplicate": true, "balance": 650 }.
409 { "error": "not_enough_points" } —
баллов не хватает: покупатель увидит «Недостаточно баллов».
409 card_inactive — карта заблокирована.
404 — карты нет.
Проверяйте остаток и списывайте под блокировкой: сервис проверил
баланс по GET /balance перед вызовом, но между проверкой и
списанием баллы могли потратить на кассе. Если метод не реализован
(404), купоны за баллы не выдаются, бесплатные купоны
(points: 0) работают без него.
GET /expiring
GET /expiring?days=7 — у каких карт сгорают баллы в
ближайшие days дней (от 1 до 365).
200 — массив верхнего уровня:
[ { "card": "2200000000015", "amount": 300, "date": "2026-11-02" } ]На каждую карту одна строка с ближайшей датой сгорания и суммой на эту дату. Достаточно отдавать карты, подключённые к приложению. Порог «сгорает от N баллов» применяет сервис. Напоминание отправляется один раз на каждую пару «карта + дата».
GET /history
GET /history?card=2200000000015&limit=50
(limit от 1 до 200).
200
{ "items": [
{ "date": "2026-10-19T09:12:00", "type": "bonus", "amount": 300, "comment": "Подарок на день рождения", "pending": false, "expires": "2026-11-02" },
{ "date": "2026-10-12T18:40:11", "type": "redeem", "amount": -500, "comment": "Чек 0012-000345", "pending": false, "expires": null },
{ "date": "2026-10-12T18:40:11", "type": "accrual", "amount": 324.5, "comment": "Покупка в ТЦ Мега", "pending": true, "expires": "2027-10-12" }
] }| Поле | Описание |
|---|---|
date |
Дата и время операции |
type |
accrual — за покупку, redeem — списание,
burn — сгорание, bonus — подарочные баллы
(регистрация, ДР, автовозврат), gift — подарок за
баллы |
amount |
Плюс — начисление, минус — списание |
comment |
Что увидит покупатель; если пусто, приложение подпишет операцию по
type |
pending |
Начислится позже (задержка начисления) |
expires |
До какой даты действуют эти баллы, или null |
Сортировка: новые сверху. 404 — карты нет.
GET /card
GET /card?phone=79991234567 → 200
{ "card": { ... } } или 404. Сейчас сервис
этот метод не вызывает, он зарезервирован для поиска карты без
регистрации.
Настройки программы лояльности
Объект из GET /pos/loyalty-settings со значениями по
умолчанию. Применяет их частично сервис, частично ваша система: это
указано в комментариях.
{
// Начисление за покупку. Применяет ВАША СИСТЕМА при проведении чека
"accrual": {
"enabled": false,
"levels": [ // до 50 строк
{ "status": "", "from_sum": 0, "percent": 3 }, // "" = для всех статусов
{ "status": "", "from_sum": 10000, "percent": 5 }, // чек от 10 000 ₽ — 5%
{ "status": "Золотая", "from_sum": 0, "percent": 7 }
],
"delay_days": 0, // 0..365: баллы становятся доступны через N дней (защита от возвратов)
"lifetime_days": 0 // 0..3650: баллы за покупки сгорают через N дней, 0 — не сгорают
},
// Бонус за регистрацию. Применяет ВАША СИСТЕМА в POST /register
"welcome": { "enabled": false, "amount": 0, "lifetime_days": 0, "existing_clients": false },
// День рождения. Применяет СЕРВИС: в нужный день вызывает POST /bonus и пишет клиенту
"birthday": {
"enabled": false,
"days_before": 0, // 0..30: 0 — в день рождения
"send_hour": 10, // 0..23, час по Москве
"lifetime_days": 14,
"points": [ { "status": "", "amount": 300 }, { "status": "Золотая", "amount": 1000 } ],
"text": "{имя}, с днём рождения! Дарим вам {баллы} баллов, ими можно оплатить покупку."
},
// Автовозврат. Применяет СЕРВИС по покупкам из POST /pos/purchases
"winback": {
"enabled": false, "after_days": 60, "repeat_days": 90, "send_hour": 12, "lifetime_days": 14,
"points": [], "text": "{имя}, давно вас не видели! Дарим {баллы} баллов на следующую покупку."
},
// Напоминание о сгорании. Применяет СЕРВИС по GET /expiring
"expiry": {
"enabled": false, "days_before": 7, "send_hour": 12, "min_amount": 1,
"text": "{имя}, {дата} сгорят {баллы} баллов. Успейте потратить их на покупку."
},
"gifts": { "enabled": false }, // вкладка «Подарки» в приложении
"history": { "enabled": false }, // вкладка «История» в приложении (GET /history)
"birth_date": { "ask": "off" }, // off | optional | required — спрашивать ли дату рождения при регистрации
"new_card": { "kind": "" }, // вид карты для клиента, которого нет в учётной системе ('' — по её настройкам)
// Оценка визита (PRO). Применяет СЕРВИС по покупкам из POST /pos/purchases
"review": {
"enabled": false,
"delay_min": 15, // 1..1440: через сколько минут после чека спросить
"repeat_days": 7, // 0..365: не чаще раза в N дней на карту
"min_amount": 0, // от суммы чека, ₽
"text": "Спасибо за покупку, {имя}! Оцените, пожалуйста, визит от 1 до 5.",
"public_bonus": 100, // баллы за переход к отзыву после оценки 5 (POST /bonus, reason review)
"public_lifetime_days": 30,
"yandex_url": "", "gis_url": "", // https://… страницы отзывов; пусто — кнопки нет
"notify_email": "" // куда слать оценки 1–3; пусто — email организации
},
// «Приведи друга» (PRO). Применяет СЕРВИС после первой покупки друга
"referral": {
"enabled": false, "friend_points": 300, "referrer_points": 300, "lifetime_days": 60, "min_amount": 0,
"friend_text": "{имя}, спасибо за первую покупку! Дарим {баллы} баллов от друга.",
"referrer_text": "{имя}, ваш друг совершил первую покупку. Дарим вам {баллы} баллов!"
},
// Поздравление с новым статусом (PRO). Применяет СЕРВИС по card_status
"status_upgrade": {
"enabled": false,
"points": [], // баллы по статусу, как в birthday; пусто — только поздравление
"lifetime_days": 30,
"order": [], // статусы от младшего к старшему; пусто — по cashback_percent из GET /statuses
"text": "{имя}, поздравляем! Ваша карта теперь «{статус}». Кэшбэк и привилегии уже действуют."
},
// Только в ответе GET: какие функции входят в тариф
"plan_features": { "birthday": true, "winback": true, "review": false, "...": "..." }
}
Уровни начисления. Для чека выбирайте строки со
статусом карты (или пустым статусом, если для статуса строк нет) и
берите строку с наибольшим from_sum, не превышающим сумму
чека. Начислите сумма × percent / 100, округление на ваше
усмотрение. Если accrual.enabled = true, отключите
собственные правила начисления, иначе баллы начислятся дважды. При
возврате уменьшайте начисление пропорционально.
Баллы по статусу (points в
birthday и winback): строка с точным статусом
карты, иначе строка с пустым статусом. Если подходящей строки нет, баллы
не начисляются, но сообщение уходит.
Подстановки в текстах: {имя} — первое
слово owner_name, {баллы} — количество,
{дата} — дата дня рождения или сгорания («2 ноября»),
{статус} — новый статус карты. Текст до 1000 символов.
Тарифы и функции
Что из API доступно организации, зависит от её тарифа. Узнать тариф и
включённые функции можно методом GET /pos/plan, а в
GET /pos/loyalty-settings они приходят в поле
plan_features.
Функция (features.*) |
Старт | Базовый | Бизнес | Сеть / PRO | Где в API |
|---|---|---|---|---|---|
Карт в базе (limits.cards) |
до 1 000 | ∞ | ∞ | ∞ | регистрация новых карт |
Точек регистрации (limits.sources) |
1 | 2 | 5 | ∞ | PUT /pos/sources |
Рассылок в месяц (limits.campaigns_per_month) |
2 | 4 | ∞ | ∞ | POST /pos/campaigns |
Доступов в кабинет (limits.users) / юрлиц или баз
(limits.orgs) |
1 / 1 | 2 / 1 | 5 / 1 | 20 / 3 | кабинет |
welcome — бонус за регистрацию |
✓ | ✓ | ✓ | ✓ | POST /register |
birthday — день рождения |
— | ✓ | ✓ | ✓ | POST /bonus birthday |
onec_examples — примеры кода для 1С |
— | ✓ | ✓ | ✓ | папка onec/ |
push_filters, push_schedule — фильтры и
расписание рассылок |
— | — | ✓ | ✓ | audience.*, send_at |
winback — возврат спящих |
— | — | ✓ | ✓ | POST /bonus winback |
history — история баллов |
— | — | ✓ | ✓ | GET /history |
gifts — подарки за баллы |
— | — | ✓ | ✓ | POST /pos/gifts/consume |
webhooks — вебхуки |
— | — | ✓ | ✓ | Вебхуки |
segments — RFM-сегменты |
— | — | — | ✓ | GET /pos/segments, audience.segment |
expiry — напоминание о сгорании |
— | — | — | ✓ | GET /expiring |
review — оценка после покупки |
— | — | — | ✓ | GET /pos/reviews, POST /bonus
review |
referral — «Приведи друга» |
— | — | — | ✓ | GET /pos/referrals, POST /bonus
referral |
status_upgrade — поздравление с новым статусом |
— | — | — | ✓ | POST /pos/card-status, card_status,
POST /bonus status_upgrade |
coupons — купоны партнёров |
— | — | — | ✓ | POST /bonus/writeoff |
receipts — детализация чеков в истории |
— | — | — | ✓ | по POST /pos/purchases |
app_code — код покупки из приложения
(LC) |
— | — | — | ✓ | касса |
white_label, roles,
analytics |
— | — | — | ✓ | кабинет и приложение |
Открытое API (части A и B) доступно на всех тарифах.
Ошибки тарифа.
- Метод целиком не входит в тариф (
/pos/reviews,/pos/referrals,/pos/segments,/pos/gifts/consume) — 403{ "error": "plan", "message": "Оценка после покупки: доступно на тарифе «Сеть / PRO» и выше" }. Не повторяйте запрос, отключите функцию у себя. - Запрос выходит за лимит или включает функцию не из тарифа
(
POST /pos/campaigns,PUT /pos/loyalty-settings) — 400invalidс текстом вerrors, например"На тарифе «Базовый» до 4 рассылок в месяц: в этом месяце уже 4". PUT /pos/sourcesсверх лимита точек — 400{ "error": "plan", "errors": ["На тарифе «Старт» до 1 точек регистрации, активных получится 2"] }.- После перехода на тариф ниже разделы настроек, которых в нём нет,
приходят с
enabled: false, а сервис перестаёт по ним вызывать вашу систему. Сохранённые значения не теряются. - Проверка идёт на стороне сервиса при каждом запросе, поэтому
интеграция, которую подключили на тарифе выше, после понижения не
обходит ограничения. Что перестаёт работать:
- рассылки с фильтрами или RFM-сегментом, поставленные в очередь до понижения, отменяются перед отправкой (запись в журнале событий);
- запросы оценки, отправленные до понижения, нельзя оценить, баллы за публичный отзыв не начисляются, вебхуки не отправляются;
- купоны партнёров пропадают из
GET /pos/giftsи из mini-app; - точки регистрации сверх лимита не привязывают карту (карта выдаётся без точки), доступы сотрудников сверх лимита и роль «Управляющий» без PRO не входят в кабинет, дополнительные юрлица сверх лимита закрыты для mini-app, кассы и кабинета. Работают первые по порядку создания; после возврата на тариф выше всё снова включается без перенастройки.
Вебхуки
Тариф «Бизнес» и выше. Сервис сообщает о событиях на ваш адрес сразу,
без опроса. Адрес и события настраиваются в кабинете: «Подключение →
Вебхуки» (только https://, адрес должен быть виден из
интернета). Секрет подписи whsec_… показывается один раз,
при первом сохранении и при перевыпуске. Кнопка «Проверить» отправляет
тестовое card.registered с "test": true.
POST https://erp.example.ru/hooks/chat-card
Content-Type: application/json
X-ChatCard-Event: review.negative
X-ChatCard-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
{ "event": "review.negative", "org_id": 12, "created_at": "2026-10-20T09:15:00.000Z",
"data": { "review_id": 381, "card_code": "2200000000015", "rating": 2, "comment": "Долго ждали на кассе",
"receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d", "store_code": "kazan-mega",
"phone": "79991234567", "owner_name": "Ольга Иванова" } }
| Событие | Когда | data |
|---|---|---|
card.registered |
Покупатель подключил карту в приложении | card_code, platform, created
(карта выпущена сейчас), source_code, referred
(пришёл по приглашению) |
card.status_changed |
Изменился статус карты | card_code, previous,
status |
review.created |
Покупатель оценил визит | review_id, card_code, rating,
comment, receipt_id,
store_code |
review.negative |
Оценка 1–3 (вместе с review.created) |
то же плюс phone, owner_name |
referral.registered |
Друг подключил карту по приглашению | referral_id, referrer_card,
friend_card |
referral.rewarded |
Первая покупка друга, баллы начислены | referral_id, referrer_card,
friend_card, receipt_id,
friend_points, referrer_points |
Правила доставки:
- ответьте 2xx в течение 5 секунд, тяжёлую обработку делайте после ответа;
- повторов нет: недоставленное событие записывается в
журнал кабинета. Для надёжной синхронизации оценок и приглашений
опрашивайте
GET /pos/reviews?cursor=…иGET /pos/referrals, а вебхук используйте как сигнал «пора забрать»; - переадресации (3xx) не выполняются.
Проверка подписи. X-ChatCard-Signature
= sha256= + HMAC-SHA256 от сырого тела
запроса (байты как пришли, до разбора JSON) с ключом — строкой
секрета whsec_… в UTF-8, в нижнем регистре hex. Сравнивайте
за постоянное время и отклоняйте запрос с неверной подписью.
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody — Buffer тела запроса, например express.raw({ type: 'application/json' })
export function verifyChatCard(rawBody, header, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(String(header || ''));
return a.length === b.length && timingSafeEqual(a, b);
}1С (HTTP-сервис, платформа 8.3.11 и новее; в платформе нет готовой
функции HMAC, поэтому она собрана из
ХешированиеДанных):
Функция ПодписьВебхукаВерна(Запрос, Секрет) Экспорт
Тело = Запрос.ПолучитьТелоКакДвоичныеДанные();
Ключ = ПолучитьДвоичныеДанныеИзСтроки(Секрет, КодировкаТекста.UTF8);
Ожидаемая = "sha256=" + НРег(ПолучитьHexСтрокуИзДвоичныхДанных(HMAC_SHA256(Ключ, Тело)));
Пришла = НРег(СокрЛП(Запрос.Заголовки.Получить("X-ChatCard-Signature")));
Возврат Ожидаемая = Пришла;
КонецФункции
Функция HMAC_SHA256(Знач Ключ, Данные)
РазмерБлока = 64;
Если Ключ.Размер() > РазмерБлока Тогда
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.Добавить(Ключ);
Ключ = Хеш.ХешСумма;
КонецЕсли;
БуферКлюча = Новый БуферДвоичныхДанных(РазмерБлока);
БуферКлюча.Записать(0, ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Ключ));
Внутр = Новый БуферДвоичныхДанных(РазмерБлока);
Внеш = Новый БуферДвоичныхДанных(РазмерБлока);
Для Индекс = 0 По РазмерБлока - 1 Цикл
Байт = БуферКлюча.Получить(Индекс);
Внутр.Установить(Индекс, ПобитовоеИсключительноеИли(Байт, 54)); // 0x36
Внеш.Установить(Индекс, ПобитовоеИсключительноеИли(Байт, 92)); // 0x5C
КонецЦикла;
ХешВнутр = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешВнутр.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Внутр));
ХешВнутр.Добавить(Данные);
ХешВнеш = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешВнеш.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Внеш));
ХешВнеш.Добавить(ХешВнутр.ХешСумма);
Возврат ХешВнеш.ХешСумма;
КонецФункции
Если имя заголовка в Запрос.Заголовки у вас приходит в
другом регистре, ищите его без учёта регистра. Секрет храните в
константе с доступом только для администратора.
Типовые сценарии на кассе
Чек с начислением.
- Кассир сканирует карту: пластик,
LC…илиLR…. - Для
LC…отрежьте префикс и найдите карту. ДляLR…вызовитеPOST /pos/redeem/consume. - Проведите чек, начислите баллы по своим правилам или по
accrual. - Положите покупку в очередь и отправьте
POST /pos/purchasesсscanned_codeв фоне.
Оплата баллами.
- Покупатель жмёт в приложении «Списать баллы» и показывает QR
LR…. - Касса вызывает
POST /pos/redeem/consumeи получаетcard_code. - Только в этом чеке разрешите оплату баллами по этой карте. Пластик и
LC…списание не разрешают.
Подарок за баллы.
- Покупатель выбирает подарок в приложении и показывает QR
LG…. - Касса вызывает
POST /pos/gifts/consume, получаетcard_codeиgift. - Проверьте остаток, спишите
gift.points, выдайте подарок (поsku, если указан). - При ошибке списания скажите кассиру и не выдавайте подарок. Код уже погашен, покупатель получит новый в приложении.
Возврат. Отправьте POST /pos/purchases
с отрицательной amount и новым receipt_id и
скорректируйте начисленные баллы у себя.
Статус карты повысился в чеке. Передайте новый
статус в card_status той же покупки. Если статусы
пересчитываются регламентным заданием, отправьте изменившиеся карты
пачкой в POST /pos/card-status.
Проверка интеграции
Заведите организацию в кабинете и подключите бота MAX или Telegram («Подключение»).
Укажите адрес вашей системы и нажмите «Проверить связь». Должен пройти
GET /welcome-bonus.Выпустите ключ кассы и проверьте часть A:
curl -s https://app.chat-card.ru/pos/loyalty-settings -H "X-Api-Key: ck_..." curl -s -X POST https://app.chat-card.ru/pos/purchases -H "X-Api-Key: ck_..." \ -H "Content-Type: application/json" \ -d '{"receipt_id":"test-1","card_code":"2200000000015","amount":100,"scanned_code":"LC2200000000015"}'Откройте mini-app по адресу из кабинета (
https://app.chat-card.ru/o/<имя>через бота), оформите карту. В вашей системе должен прийтиPOST /register, а в приложении появиться баланс изGET /balance.Нажмите «Списать баллы» и отсканируйте QR кассой:
POST /pos/redeem/consumeдолжен вернуть номер карты. Второе сканирование вернёт 410.Включите в кабинете «Программа лояльности → История» и проверьте вкладку «История» (
GET /history).Для дня рождения укажите у тестового клиента дату через
days_beforeдней и выставьтеsend_hourменьше текущего часа. Планировщик проверяет сценарии раз в 15 минут и вызоветPOST /bonus.
Чек-лист перед запуском
| Возможность | Часть B (реализуете вы) | Часть A (вызываете вы) |
|---|---|---|
| Карта в приложении, регистрация, баланс | /register, /balance,
/welcome-bonus, /statuses |
— |
| Списание баллов по QR | — | /pos/redeem/consume |
| Статистика покупок, автовозврат | — | /pos/purchases |
| Уровни начисления, задержка, срок жизни | применить accrual, отдавать pending и
expiring в /balance |
/pos/loyalty-settings |
| Бонус за регистрацию | правила в /register |
/pos/loyalty-settings |
| День рождения, автовозврат | /bonus, birth_date в card и
/balance |
/pos/purchases (для автовозврата) |
| Напоминание о сгорании | /expiring |
— |
| История баллов | /history |
— |
| Подарки за баллы | списание баллов | /pos/gifts/consume, /pos/gifts |
| Точки регистрации | принять source_code в /register |
/pos/sources |
| Push из вашей системы | — | /pos/campaigns… |
| Тариф и доступные функции | — | /pos/plan |
| Поздравление с новым статусом | /bonus (status_upgrade) |
card_status в /pos/purchases или
/pos/card-status |
| Оценка после покупки | /bonus (review) |
/pos/purchases, /pos/reviews |
| «Приведи друга» | created в /register, /bonus
(referral) |
/pos/purchases, /pos/referrals |
| RFM-сегменты | — | /pos/purchases, /pos/segments,
audience.segment |
| Купоны партнёров | /bonus/writeoff |
/pos/gifts |
| Вебхуки | приёмник https://… с проверкой подписи |
— |
Безопасность:
- храните ключ кассы как пароль и не передавайте его в браузер или на устройства покупателей;
- выделите для Basic-авторизации отдельного пользователя с правами только на этот HTTP-сервис;
- если адрес вашей системы открыт только для сервиса, разрешите
входящие запросы с IP сервера
app.chat-card.ru.
Вопросы по интеграции присылайте на info@chat-card.ru или через раздел «Поддержка» в кабинете.