Перейти к содержанию

Карточка клиента

Карточка клиента — рабочее окно оператора по одному участнику: идентификационные данные, баланс и уровень, вкладки с операциями и историей, действия над клиентом. Открывается из каталога клиентов двойным кликом по строке или пунктом меню «Открыть карточку»; закрывается крестиком в правом верхнем углу — возврат в каталог.

Состав карточки — какие поля в каких блоках, какие разделы и вкладки справа — задаёт администратор в конфигураторе «Настройка профиля клиента», см. Настройка карточки клиента. Карточка собирается по активной версии этой настройки.

Где «живёт» клиент

Модуль лояльности не является мастером персональных данных: ФИО, контакты, город, часовой пояс, дата рождения и дата регистрации приходят из мастер-системы (MDM). Сегменты клиента приходят из системы кампаний (Batch Flow) и хранятся в лояльности составом сегментов. В лояльности хранится «технический» клиент — носитель внешнего идентификатора, его счета, баланс, уровень, теги, история и значения дополнительных полей карточки. Карточка объединяет эти источники. Режим работы с MDM задаётся настройкой loyalty.mdm.adapter; по умолчанию (local) мастер-данные ведутся в собственном хранилище модуля.

Идентификация

Клиент всегда адресуется внешним идентификатором (externalId) — сквозным идентификатором из контура платформы. Внутренних суррогатных идентификаторов в публичном API нет. Дополнительно у клиента есть собственный неизменяемый реферальный код, сгенерированный при создании (см. Реферальная программа).

Состав карточки

Шапка и действия

В шапке — ФИО клиента, пилюли уровня и статуса и панель действий.

Кнопка Роль Поведение
Редактировать LOYALTY_CUSTOMER_WRITE Включает инлайн-правку полей левой колонки; появляются «Сохранить» и «Отмена». Кнопки нет, если в настройке карточки нет редактируемых полей
Начислить LOYALTY_ACCRUAL_WRITE Ручное начисление — см. Ручные операции
Корректировка LOYALTY_REDEMPTION_WRITE Снятие баллов корректировкой с причиной из справочника — см. Ручные операции
Изменить уровень LOYALTY_TIER_WRITE Ручное назначение уровня и сброс к расчётному — см. Уровни
Заблокировать / Разблокировать LOYALTY_CUSTOMER_WRITE Смена статуса, после подтверждения в модалке
Архивировать, Удалить — В карточке пока неактивны («Скоро»); выполняются из каталога

Заблокированному клиенту ручные операции запрещены: кнопки «Начислить», «Корректировка» и «Изменить уровень» выключены с подсказкой «Клиент заблокирован».

Левая колонка: карточки с полями

Левая колонка состоит из блоков-«карточек» в порядке, заданном в настройке карточки клиента: заголовок блока — название карточки, внутри — её видимые поля в заданном порядке. По умолчанию блоков два — «Клиент» и «Баллы и уровень». Скрытые и архивные поля не выводятся, карточка без видимых полей не показывается. Если раскладку карточек прочитать не удалось, поля показываются одним блоком «Основные данные».

Преднастроенные (системные) поля берутся из данных клиента:

Поле схемы Подпись по умолчанию Значение
client_id ID клиента Внешний идентификатор клиента
full_name ФИО ФИО из профиля (редактируемое)
phone, email Телефон, Email Контакты из профиля (редактируемые)
birth_date Дата рождения Дата рождения из профиля; строки нет, если дата не заполнена. В карточке только просмотр
points_balance Баллы Баланс баллов
loyalty_level Уровень Действующий уровень
segments Сегменты Сегменты клиента — пилюлями с названиями; на пилюле подсказка «В сегменте с {дата}. Состав сегмента рассчитан {дата и время}». Нет сегментов — «Не входит ни в один сегмент». Только чтение
status Статус Статус клиента
registered_at Дата регистрации Дата регистрации

В первом блоке всегда выводится поле «Часовой пояс» — только чтение, например «UTC+7 — Asia/Novosibirsk». Под значением — подсказка, откуда взят пояс: «Пришёл в профиле клиента из внешней системы», «Определён по городу в профиле — {город}», «Определён по магазинам, где клиент обычно покупает» или «Город не определён — используется часовой пояс программы» со смещением пояса программы в скобках. Пояс берётся из первого доступного источника: пояс из профиля → город профиля по справочнику городов → самый частый город покупок (окно по умолчанию — 90 суток) → пояс программы. После сохранения профиля пояс пересчитывается сразу. В интерфейсе лояльности пояс не правится.

Остальные поля — дополнительные атрибуты, заведённые администратором:

  • введённые вручную — значения хранятся в лояльности и правятся здесь же;
  • из системы — атрибут привязан к колонке таблицы-источника, значение читается из неё по внешнему идентификатору клиента и в карточке не правится. Если источник недоступен или строки по клиенту нет, поле пустое, а остальная карточка открывается как обычно.

Значение показывается по типу поля: дата — датой, число — с разрядами, логическое — «Да» / «Нет». Атрибут с множественным выбором показывает значения отдельными пилюлями. Значение выпадающего списка показывается кодом записи справочника — и в карточке, и в списке выбора при правке.

Правка профиля

Кнопка «Редактировать» превращает редактируемые строки в поля ввода, «Сохранить» отправляет изменения: поля золотого профиля — в MDM через бэкенд лояльности, значения дополнительных атрибутов — в хранилище лояльности. Тост «Профиль сохранён».

  • ФИО вводится одной строкой и разбирается по пробелам: первое слово — фамилия, второе — имя, остальное — отчество.
  • Телефон и email проверяются и в форме, и на сервере (см. ниже).
  • Контрол правки атрибута задаётся типом поля: строка, число, дата, дата со временем, «Да/Нет». Поле со способом ввода «Выпадающий список» предлагает действующие записи своего справочника, с множественным выбором — несколько значений сразу. Записи читаются из реестра справочников, поэтому без роли LOYALTY_DICTIONARY_READ список пуст и значение вводится вручную. Пустое значение очищает поле.
  • Поля, которых нет в настройке карточки, правка не затрагивает.
  • Запись профиля заводит клиента в реестре лояльности, если его там ещё нет, — вместе со счетами во всех включённых валютах.

Дата со временем

Правка атрибута типа «Дата со временем» в карточке пока не работает: выбранное значение уходит на сервер не в формате ISO-8601, сервер отклоняет его (400 validation_failed), и сохранение профиля не проходит. Задавайте такое значение через API — в формате ISO-8601 с часовым поясом (см. REST API карточки).

Проверка значений на сервере (26.3.0)

Сервер принимает значение атрибута только в формате его типа (см. REST API карточки) и отклоняет запись нового значения в поле, которого нет в настройке, в преднастроенное поле, в поле с колонкой источника, в архивное и в скрытое поле. Очистить значение можно в любом из этих случаев.

Правая часть: разделы и вкладки

Правая часть карточки — переключатель разделов и вкладки внутри раздела. Разделы, их порядок и порядок вкладок в разделе задаёт настройка карточки клиента: вкладки продукта и таблицы данных, заведённые администратором, стоят там, куда их поставил администратор. По умолчанию разделов три:

Раздел Вкладка Содержимое
Баллы и уровень «Счета и балансы» Доступный и полный баланс, резерв, доступное по типам баллов, уровень, разбивка по субсчетам, счета — Баланс и операции
«Бонусные операции» История операций клиента с фильтрами — Баланс и операции
«Сроки сгорания» Партии баллов и их сроки, ближайшее сгорание — Сгорание баллов
«История уровней» Текущий уровень, пересмотр и переходы с источником — Уровни
Клиент «Статус клиента» Текущий статус (см. Статусы, архив и признак «Тест»)
«Достижения» Полученные бейджи — Достижения и бейджи
«Рефералы» Приглашённые клиенты — Реферальная программа
Активность и коммуникации «Участие в акциях» Срабатывания акций по покупкам клиента (см. ниже)

Вкладка таблицы данных, заведённой администратором, показывает строки таблицы-источника, связанные с клиентом по внешнему идентификатору: только видимые столбцы, в порядке и с подписями из настройки, с постраничным выводом и сортировкой по столбцу. Раздел без таблиц не показывается.

Данные вкладки запрашиваются при её открытии.

Вкладка «Участие в акциях»

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

Колонка Содержимое
Дата Момент операции; единственная сортируемая колонка, по умолчанию новые сверху
Акция Название версии акции; при роли LOYALTY_PROMOTION_READ — ссылка на карточку акции
Механика Механика акции; фильтр по механике — несколько значений по «ИЛИ» (при роли LOYALTY_PROMOTION_READ: без неё справочника механик нет, и фильтра тоже)
Бонус Начисленные баллы; «0» — акция сработала, начисления не было
ID транзакции Идентификатор операции

Колонки «Метки акции», «Сумма чека», «К оплате» и «Источник оплаты» появляются, только если их включает настройка развёртывания интерфейса (PROMOTION_PARTICIPATION); по умолчанию их нет.

Сторно начисления по акции (возврат покупки) участием не считается и во вкладку не попадает. Пустая вкладка — «Клиент пока не участвовал в акциях»; пустой результат фильтра — «Ничего не найдено» с кнопкой «Сбросить фильтры». Сортировка, фильтр и страница запоминаются в браузере.

Теги в карточке

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

Статусы, архив и признак «Тест»

Это три независимые характеристики клиента.

Статус Смысл
ACTIVE («Активен») Обычный участник
BLOCKED («Заблокирован») Операции с баллами запрещены; клиент остаётся в каталоге
MERGED Клиент слит в другого и выведен из операций
DELETED Клиент мягко удалён: скрыт из каталога, поиска и карточки
  • Архив — обратимое скрытие из основного каталога, статус не меняет.
  • Тест — признак тестового клиента: бейдж «Тест», исключение из отчётов и рассылок; контакт для него не обязателен.

Блокировка и разблокировка идемпотентны. Заблокировать или заархивировать удалённого клиента нельзя: для всех операций он выглядит как «не найден» — в том числе если удаление пришло одновременно с блокировкой. Разблокировка открывает клиенту счета во включённых валютах, которые не были открыты, пока он был заблокирован.

История смены статуса

Каждая фактическая смена статуса записывается в историю: блокировка, разблокировка и удаление (одиночные и массовые) — с автором из токена, слияние — ещё и с причиной из запроса. Повтор операции, не изменивший статус, записи не делает. История ведётся с момента выкладки версии 26.3.0: у клиентов, заблокированных раньше, деталей блокировки нет.

История доступна через API GET /v1/customers/{externalId}/status-history — страница строк {id, occurredAt, status, previousStatus, operator, operatorId, reason}, новые сверху. На вкладке «Статус клиента» интерфейс пока показывает только текущий статус и пустую таблицу с пояснением «История смены статуса появится после доработки бэкенда.», у заблокированного клиента — ещё и предупреждение, что причину, дату и автора блокировки интерфейс пока не показывает.

Удаление и обезличивание

Операция Что происходит
Мягкое удаление DELETE /v1/customers/{externalId} Статус → DELETED. Клиент скрыт из каталога, поиска и карточки и недоступен для операций (404), но счета, проводки и история сохраняются: двойная запись неизменяема. Идемпотентно, ответ 204
Обезличивание POST /v1/customers/{externalId}/anonymize Затирает выбранные поля ПДн профиля. Клиент остаётся в каталоге. Идемпотентно, поддерживает Idempotency-Key
«Удалить» в каталоге (bulk-delete) Обезличивание всех полей ПДн + мягкое удаление, одной транзакцией на клиента: полуобезличенных клиентов не остаётся

Обезличиваемые поля выбираются набором fields: PHONE, EMAIL, FULL_NAME, CITY, BIRTH_DATE, SEGMENTS. Текстовые поля заменяются заглушкой «[удалён]» (у ФИО фамилия — заглушка, имя и отчество очищаются), дата рождения обнуляется, по SEGMENTS клиент выводится из состава всех сегментов. Финансовая история не затрагивается — она нужна для сверок и отчётности.

Валидация и нормализация контактов

При сохранении профиля телефон и email приводятся к единому виду; невалидное значение отклоняется с 422, а не сохраняется молча.

Поле Правило
Телефон Принимаются +7…, 8…, 7… и 10 цифр номера с любыми разделителями. Результат — каноничная форма +7XXXXXXXXXX (по умолчанию) или 8XXXXXXXXXX. Неверная длина или чужой код страны — 422 invalid_phone
Email Обрезаются пробелы, значение приводится к нижнему регистру, проверяется формат. Неверный — 422 invalid_email

Код страны и каноничная форма настраиваются (loyalty.contact.phone.default-country, loyalty.contact.phone.canonical-form; значение формы — PLUS для +7XXXXXXXXXX или LOCAL для 8XXXXXXXXXX); значения по умолчанию — 7 и PLUS. Пустое поле означает «не менять». Те же проверки выполняет форма карточки и строгий режим импорта клиентов.

Даты: регистрация и заведение в реестре

  • Дата регистрации — из профиля клиента; если её нет, показывается дата заведения клиента в реестре лояльности.
  • Дата заведения в реестре (createdAt) проставляется при создании клиента и служит порядком по умолчанию в каталоге: новый клиент виден первым на любой странице. Сортировать по ней можно через API (sort=createdAt,desc).

Недоступность MDM

Сбой внешнего реестра клиентов (таймаут, обрыв связи, 5xx, просроченный сервисный токен) отдаётся как 503 mdm_unavailable, а не как «клиент не найден»: ответ не утверждает факт о клиенте, который никто не проверил. Отсутствие клиента по-прежнему 404 customer_not_found — в том числе когда MDM сам отвечает «не найден».

  • Интерфейс показывает «Клиент не найден» только на 404; на прочие ошибки — «Не удалось загрузить данные», и запрос можно повторить.
  • Чтение полной записи и сохранение карточки тоже отдают 503, а не «внутреннюю ошибку»; после записи сервер не выдаёт форму за сохранённый профиль, если перечитать запись не удалось.
  • Список клиентов при недоступном MDM не показывает страницу из одних идентификаторов, а сообщает об аварии.
  • Начисление по покупке продолжает работать: клиент отдаётся без ФИО и контактов, чтобы чужая авария не роняла операцию. Сегменты клиента от MDM не зависят — они хранятся в лояльности.

Изменение контракта (26.3.0)

Для потребителей карточки клиента сбой реестра теперь 503 mdm_unavailable вместо прежнего 404 customer_not_found.

Слияние клиентов «1 телефон = 1 профиль»

POST /v1/customers/{externalId}/merge сливает клиентов: в пути — выживший клиент, в теле sourceExternalId — источник, чьи данные переносятся, и необязательная причина reason для аудита.

Переносятся счета источника (вместе с историей проводок) и его журнал покупок и возвратов — по нему считается накопление уровня. Уровень выжившего после этого пересчитывается так же, как после покупки (см. Уровни). Ручное назначение уровня переносится, если у выжившего его нет; история переходов уровня остаётся у источника. Источник получает статус MERGED и ссылку на выжившего, в истории статуса источника появляется запись с причиной.

  • Повторное слияние источника в того же выжившего ничего не меняет и отвечает 200.
  • Источник, уже слитый в другого клиента, — 409 version_conflict.
  • Источник, мягко удалённый до или во время слияния, — 404 customer_not_found, перенос откатывается.

Пересечение по валюте

Счета есть у каждого клиента в каждой включённой валюте, поэтому конфликт возникает, только если операции в одной и той же валюте были и у источника, и у выжившего: слияние отклоняется — 422 merge_account_conflict, остаток переносится вручную через леджер. Счёт без операций место уступает и конфликта не даёт. Слияние клиента с самим собой — 422 merge_same_customer.

flowchart LR
    S["Клиент-источник"] -->|счета, покупки и возвраты| W["Выживший клиент"]
    S -->|статус → MERGED| X["Источник выведен<br/>из операций"]

REST API карточки

Метод и путь Роль Назначение
GET /v1/customers/{externalId} LOYALTY_CUSTOMER_READ Клиент по внешнему идентификатору
GET /v1/customers/{externalId}/card LOYALTY_PROFILE_READ и LOYALTY_MONITORING_READ Карточка: профиль MDM (с birthDate), статус, флаг archived, теги tags, лоялти-блок (в нём пояс клиента timezone {zone, source, resolvedAt}), значения атрибутов attributes
GET /v1/customers/{externalId}/profile LOYALTY_PROFILE_READ Профиль: идентичность MDM + сводка по баллам
PUT /v1/customers/{externalId}/profile LOYALTY_CUSTOMER_WRITE Сохранить профиль в MDM и завести клиента в реестре; null-поле — «не менять». Принимает и birthDate (YYYY-MM-DD; очистить дату можно только обезличиванием), city и timezone (идентификатор IANA, например Asia/Novosibirsk; пустая строка очищает; иначе 422 customer_timezone_invalid)
PUT /v1/customers/{externalId}/attributes LOYALTY_CUSTOMER_WRITE Значения атрибутов карточки: ключ поля → список значений; пустой список очищает
GET /v1/customers/{externalId}/segments LOYALTY_PROFILE_READ и LOYALTY_MONITORING_READ Сегменты клиента с датой входа
GET /v1/customers/{externalId}/status-history LOYALTY_PROFILE_READ и LOYALTY_MONITORING_READ История смены статуса
GET /v1/customers/{externalId}/profile-tables/{groupId}/rows LOYALTY_CUSTOMER_READ Строки таблицы данных, настроенной в конфигураторе
GET /v1/client-profile/schema, /structure LOYALTY_CUSTOMER_READ Поля активной настройки карточки и раскладка по карточкам, разделам и таблицам
GET /v1/client-profile/dictionaries/{key} LOYALTY_CUSTOMER_READ Записи справочника выпадающего списка активной настройки
POST /v1/customers/{externalId}/block, /unblock LOYALTY_CUSTOMER_WRITE Блокировка / разблокировка
POST /v1/customers/{externalId}/archive, /unarchive LOYALTY_CUSTOMER_WRITE Архивирование / возврат из архива (204)
DELETE /v1/customers/{externalId} LOYALTY_CUSTOMER_WRITE Мягкое удаление (204, идемпотентно)
POST /v1/customers/{externalId}/anonymize LOYALTY_CUSTOMER_WRITE или LOYALTY_INTEGRATION_WRITE Обезличивание выбранных полей ПДн
POST /v1/customers/{externalId}/merge LOYALTY_CUSTOMER_WRITE Слияние клиентов

Значение атрибута в PUT …/attributes передаётся строкой в формате типа поля:

Тип Формат Пример
STRING Любая строка Москва
NUMBER Десятичное число через точку, без экспоненты -12.5
DATE YYYY-MM-DD 2026-09-21
DATETIME ISO-8601 с часовым поясом 2026-09-21T10:00:00+03:00
BOOLEAN true или false true

Значение выпадающего списка — код записи справочника, по формату не проверяется; значение, уже сохранённое у клиента, принимается как есть. Ошибки записи:

Код HTTP Когда
validation_failed 400 Значение не в формате типа поля; ключ поля — в начале detail
unknown_attribute 422 Такого поля нет в настройке карточки
system_field_immutable 422 Поле преднастроенное или привязано к колонке источника — значение приходит из системы
field_archived 422 Поле в архиве
field_hidden 422 Поле скрыто с карточки
attribute_not_multiple 422 Больше одного значения у поля без множественного выбора

Изменение контракта (26.3.0)

attributes в карточке и тело PUT …/attributes — словарь «ключ → список строк» вместо «ключ → строка»: одиночное значение передаётся списком из одного элемента. Значение не в формате типа поля и запись в поле, которого нет в настройке (или которое заполняется системой, в архиве, скрыто), теперь отклоняются, а не сохраняются молча. Сегменты из профиля MDM (identity.segments) больше не отдаются — сегменты клиента читаются через GET …/segments.

Создание клиента, каталог и массовые операции описаны в разделе Каталог клиентов.

Доступ и персональные данные

Все эндпоинты работают по принципу deny-by-default: доступ только при наличии роли в токене (OAuth2/JWT из Keycloak).

Роль Доступ
LOYALTY_CUSTOMER_READ Каталог, клиент по идентификатору, раскладка карточки и строки настроенных таблиц
LOYALTY_CUSTOMER_WRITE Создание, правка профиля и атрибутов, блокировка, архив, удаление, обезличивание, слияние
LOYALTY_PROFILE_READ Профиль клиента (персональные данные)
LOYALTY_PROFILE_READ + LOYALTY_MONITORING_READ Карточка клиента (профиль вместе с историей операций), сегменты клиента, история смены статуса

Персональные данные

Карточка и профиль возвращают ПДн клиента. Обращения к ним, как и поиск по каталогу, фиксируются в журнале обращений к персональным данным; роли выдаются только тем, кому просмотр ПДн разрешён регламентом.