Конфигурация¶
Конфигурация модуля Loyalty разделена на три уровня:
- Переменные окружения — инфраструктурные параметры развёртывания (подключение к БД, OIDC-провайдер, CORS, брокер сообщений, Redis, внешние интеграции). Задаются на уровне контейнера/деплоя.
- Свойства приложения (
application.yml, префиксloyalty.*, плюсcustomer.default.*) — бизнес-параметры и пороги подсистем: валюта баллов, системные счета, лимиты, расписания фоновых задач. Каждое свойство переопределяется переменной окружения по правилам Spring Boot (relaxed binding) — конкретная переменная указана в таблицах ниже. - Операционная конфигурация в БД (
system_config) — узкий набор параметров, для которых значение, заданное администратором через/v1/admin/system-config, имеет приоритет над переменной окружения и действует без передеплоя (см. раздел «Операционная конфигурация в БД» ниже). Это исключение, а не общее правило — большинство параметров модуля такого переопределения не имеет.
Где искать конкретный параметр
Если параметр не встречается ни в одной таблице ниже — это либо строго env-only переменная инфраструктуры, либо расписание/лимит из смежного раздела руководства («Наблюдаемость» — метрики и оповещения; «Роли» — доступ; «Установка и развёртывание» — параметры чарта и ресурсы пода).
Обязательные переменные окружения¶
Минимальный набор, необходимый для запуска модуля в реальном контуре.
| Переменная | Назначение | Обязательна | Значение по умолчанию |
|---|---|---|---|
LOYALTY_DB_URL |
JDBC-URL PostgreSQL | да (есть fallback) | jdbc:postgresql://…/billing |
LOYALTY_DB_USERNAME |
Пользователь БД | да (есть fallback) | loyalty |
LOYALTY_DB_PASSWORD |
Пароль БД | да | — (значения по умолчанию нет) |
LOYALTY_OIDC_ISSUER_URI |
OIDC issuer (realm Keycloak) | да | — (без него приложение не стартует) |
LOYALTY_OIDC_CLIENT_ID |
Keycloak-клиент, в resource_access которого лежат роли LOYALTY_* |
нет | frontend |
Пароль БД и issuer обязательны
Свойства LOYALTY_DB_PASSWORD и LOYALTY_OIDC_ISSUER_URI не имеют значения по умолчанию. Без пароля не поднимется пул соединений, без issuer-URI не инициализируется OAuth2 resource server — приложение завершится с ошибкой при старте.
База данных¶
URL, пользователь и пароль подключения к PostgreSQL. Пароль передаётся только через окружение (в реальном контуре — из секрета Kubernetes), в репозитории и конфиге не хранится. Схема версионируется и применяется самим приложением на старте через Liquibase (SPRING_LIQUIBASE_CONTEXTS, по умолчанию prod; демо-стенд задаёт demo, чтобы применить дополнительно сидовые changeset'ы). Размер пула соединений — LOYALTY_DB_POOL_SIZE (по умолчанию 30; денежная операция удерживает два соединения — своё и второе под распределённую блокировку), минимальный простой — LOYALTY_DB_POOL_MIN_IDLE (5), таймаут ожидания — LOYALTY_DB_POOL_TIMEOUT (5000 мс). Размер пула фоновых задач (@Scheduled) — LOYALTY_SCHEDULER_POOL_SIZE (по умолчанию 4).
OIDC / Keycloak¶
Модуль работает как OAuth2 resource server и валидирует JWT по issuer-uri. В едином контуре разработки используется общий realm с другими продуктами платформы (Core UI, Promo), чтобы один вход в Core UI давал токен, валидный для встроенного UI Loyalty.
Размер заголовка
Платформенные токены (омни-пользователь с ролями всех продуктов в одном JWT) велики, поэтому в конфигурации повышен лимит заголовка HTTP-запроса (server.max-http-request-header-size = 64KB). При самостоятельном развёртывании за прокси убедитесь, что аналогичный лимит выставлен и на прокси.
LOYALTY_DB_URL=jdbc:postgresql://loyalty-db:5432/billing
LOYALTY_DB_USERNAME=loyalty
LOYALTY_DB_PASSWORD=<из секрета>
LOYALTY_OIDC_ISSUER_URI=https://<keycloak>/auth/realms/<realm>
LOYALTY_OIDC_CLIENT_ID=frontend
Операционная конфигурация: валюта и системные счета¶
Бизнес-параметры балльного леджера задаются свойствами приложения. Они определяют валюту баллов и набор системных счетов двойной бухгалтерии, на которых отражаются начисления, списания, сгорание и корректировки.
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.points.currency |
— (правится в БД, см. «Операционная конфигурация в БД» ниже) | Код валюты, представляющей баллы (единый источник истины — в том числе для бюджета акций и денежной оценки обязательств) | PTS |
loyalty.accrual.source-account |
LOYALTY_ACCRUAL_SOURCE_ACCOUNT |
Системный счёт-источник начислений (кредит) | MRK |
loyalty.redemption.sink-account |
LOYALTY_REDEMPTION_SINK_ACCOUNT |
Системный счёт-приёмник списанных баллов | RDM |
loyalty.expiry.breakage-account |
LOYALTY_EXPIRY_BREAKAGE_ACCOUNT |
Системный счёт, на который списываются сгоревшие баллы | BRK |
loyalty.correction.sink-account |
LOYALTY_CORRECTION_SINK_ACCOUNT |
Системный контрсчёт ручных корректировок (снятие оператором, отдельно от sink-account трат) |
CORR |
customer.default.chart-of-accounts |
— | План счетов клиентского счёта по коду валюты; для валюты без записи — 2110 у балльной и 2120 у денежной |
PTS: 2110, RUB: 2120 |
loyalty.currency.exchange.mapping |
— | Сопоставление валют → счета пополнения/депозита для операций обмена | PTS → MRK, RUB → RUB_REVENUE |
Валюта баллов и номера системных счетов accrual, redemption, expiry и correction дополнительно переопределяются в БД (см. «Операционная конфигурация в БД» ниже) — значение из БД имеет приоритет над переменной окружения.
Пример из конфигурации приложения (значения по умолчанию):
loyalty:
points:
currency: PTS
accrual:
source-account: MRK
redemption:
sink-account: RDM
expiry:
breakage-account: BRK
correction:
sink-account: CORR
customer:
default:
chart-of-accounts:
PTS: 2110
RUB: 2120
Где физически живут системные счета
Перечисленные свойства — это «эталон»: миграции БД создают валюту баллов, планы счетов 2110 и 2120 и системные счета MRK, RDM, BRK (источник начислений, списание, сгорание) и CORR (корректировки), если их ещё нет. Изменение свойства влияет на то, какой счёт использует приложение при последующих операциях; уже созданные записи в БД при этом не удаляются.
Счета списания и сгорания по умолчанию разведены
Три системных счёта — источник начислений, списание и сгорание — по умолчанию разные: по номеру счёта-получателя отчёт по субсчёту делит ушедшие баллы на «списано» и «сгорело». Прежние проводки контура остаются на MRK, переопределения в БД не трогаются. Если на контуре счёт списания и счёт сгорания совпадают (заданы одним номером), сгорание от списания не отделяется: итоги субсчёта отдают признак expirySeparable: false, а сгоревшее учитывается как списанное.
Просмотр текущей операционной конфигурации¶
Действующие системные счета, валюты и единицы измерения доступны оператору только для чтения через админ-фасад справочников. Источником истины остаются соответствующие таблицы леджера.
| Эндпоинт | Назначение | Роль |
|---|---|---|
GET /v1/admin/dictionaries |
Реестр справочников (что доступно/редактируемо) | LOYALTY_DICTIONARY_READ |
GET /v1/admin/dictionaries/system-account/entries |
Системные счета (план счетов) | LOYALTY_DICTIONARY_READ |
GET /v1/admin/dictionaries/currency/entries |
Валюты, включая валюту баллов | LOYALTY_DICTIONARY_READ |
GET /v1/admin/dictionaries/unit/entries |
Единицы измерения | LOYALTY_DICTIONARY_READ |
Операционная конфигурация в БД¶
Узкий набор ключей может быть переопределён администратором через GET/PUT /v1/admin/system-config (роли LOYALTY_DICTIONARY_READ/WRITE) без передеплоя — значение из БД в этом случае имеет приоритет над переменной окружения (а для валюты баллов и срока резерва — над жёстко заданным значением, своей переменной окружения у них нет). Значения кэшируются в памяти реплики на срок loyalty.reference-cache.ttl (по умолчанию 30s, 0s выключает кэш); правка на своей реплике действует сразу, соседние увидят её не позже чем через TTL.
Ключ system_config |
Дефолт, пока ключ не задан в БД | Назначение |
|---|---|---|
points.currency |
PTS (жёстко в конфигурации, своей переменной окружения нет) |
Код валюты, представляющей баллы |
accrual.source-account |
LOYALTY_ACCRUAL_SOURCE_ACCOUNT (MRK) |
Системный счёт-источник начислений |
redemption.sink-account |
LOYALTY_REDEMPTION_SINK_ACCOUNT (RDM) |
Системный счёт-приёмник списанных баллов |
expiry.breakage-account |
LOYALTY_EXPIRY_BREAKAGE_ACCOUNT (BRK) |
Системный счёт, на который списываются сгоревшие баллы |
correction.sink-account |
LOYALTY_CORRECTION_SINK_ACCOUNT (CORR) |
Системный контрсчёт ручных корректировок |
redemption.consume-order |
LOYALTY_REDEMPTION_CONSUME_ORDER (FEFO) |
Порядок списания лотов: FIFO | LIFO | FEFO (минимизирует breakage) |
redemption.hold-ttl |
PT30M (жёстко в конфигурации, своей переменной окружения нет) |
Срок жизни резерва баллов (двухфазное списание), если вызывающий его не задал |
program.timezone |
LOYALTY_PROGRAM_TIMEZONE (Europe/Moscow) |
Часовой пояс программы (IANA) — см. «Часовой пояс программы» ниже |
Прочие свойства приложения (все параметры ниже, кроме перечисленных в этой таблице) такого переопределения не имеют — их меняют только через переменную окружения и передеплой.
Часовой пояс программы¶
Пояс программы (program.timezone) — единый пояс контура. В нём интерфейс вводит и показывает даты, а сервер режет сутки, недели и месяцы: календарный лимит участий «в день/неделю/месяц/год», итоги субсчёта за даты, дневные разрезы отчётов и мониторинга, подстраховка учётной даты (см. «Бизнес-день»). В нём же подписываются даты в текстах клиенту и именах выгружаемых файлов, по нему трактуются расписания бизнес-заданий — сгорания баллов, предупреждений о сгорании и выгрузок во внешние системы.
- Допустимо только имя зоны IANA (
Europe/Moscow,Asia/Yekaterinburg).PUT /v1/admin/system-config/program.timezoneсо значением видаMoscowилиUTC+3отклоняется422 program_timezone_invalid; непригодныйLOYALTY_PROGRAM_TIMEZONEостанавливает старт приложения. - Значение из БД действует без перезапуска, но зона расписаний сгорания и предупреждений берётся на старте — после смены пояса в админке их нужно перезапустить.
- Текущий пояс отдаёт
GET /v1/program/timezone({zone}) любому аутентифицированному пользователю. - Пояс среды исполнения — всегда UTC (
TZ=UTCи-Duser.timezone=UTCв образе и чартах): партиции леджера, печати журнала аудита и технические расписания от пояса программы не зависят. Технические расписания без пометки «в поясе программы» (например,LOYALTY_AUDIT_SEAL_VERIFY_CRON,LOYALTY_TIER_REVIEW_CRON) трактуются в UTC. - Прошлое не пересчитывается: проставленные учётные даты и сроки лотов остаются как есть.
Лимиты списания по разрезам (с 26.0.0)¶
Поверх глобальной политики погашения можно задавать лимиты списания по разрезам (per-scope). Разрез задаёт, к какой категории операций применяется ограничение:
Разрез (scope) |
Применение лимита |
|---|---|
TIER |
Лимит для клиентов конкретного уровня (тира) |
SEGMENT |
Лимит для клиентов сегмента — по текущему составу сегментов, залитому из внешней системы (см. «Интеграции» → «Сегменты клиентов из внешней системы»); если клиент входит в несколько сегментов с лимитами, берётся самое строгое ограничение |
CURRENCY |
Лимит по валюте баллов |
Действует правило «частное бьёт общее», порядок проверки: TIER → SEGMENT → CURRENCY → правила списания активной конфигурации программы → глобальная политика погашения. Применяется первый найденный лимит. При выходе суммы списания за границы лимита операция отклоняется ответом 422 (redemption_below_min или redemption_above_max).
В интерфейсе лимиты настраиваются в разделе «Настройка ПЛ» → «Лимиты по разрезам». Управление лимитами требует роль LOYALTY_DICTIONARY_WRITE.
Общая база — правила конфигурации или глобальная политика
Разрезы не отменяют общих правил, а уточняют их для конкретных уровней, сегментов и валют. Если частного лимита нет, действуют правила списания активной конфигурации программы; если в конфигурации они не заданы — глобальная политика погашения, как и раньше.
Теги клиентов и метки акций¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.tags.max-bulk-targets |
LOYALTY_TAGS_MAX_BULK_TARGETS |
Предел клиентов в одном пакете присвоения/снятия тега | 10000 |
loyalty.tags.max-batch-ids |
LOYALTY_TAGS_MAX_BATCH_IDS |
Предел ids в query-параметре пакетного чтения (ограничено длиной строки запроса) |
200 |
loyalty.tags.max-lookup-values |
LOYALTY_TAGS_MAX_LOOKUP_VALUES |
Предел значений в POST /v1/tags/lookup (тело запроса); он же — предел числа тегов в ручных операциях «Добавить теги» и «Снять теги» |
1000 |
loyalty.promotion-labels.max-bulk-targets |
LOYALTY_PROMOTION_LABELS_MAX_BULK_TARGETS |
То же для меток акций | 10000 |
loyalty.promotion-labels.max-batch-ids |
LOYALTY_PROMOTION_LABELS_MAX_BATCH_IDS |
То же; он же — предел списка меток в массовом проставлении и снятии меток у акций (больше — 400 bulk_size_exceeded) |
200 |
loyalty.promotion-labels.max-lookup-values |
LOYALTY_PROMOTION_LABELS_MAX_LOOKUP_VALUES |
То же | 1000 |
Ручные операции¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.manual-operations.max-rows |
LOYALTY_MANUAL_OPERATIONS_MAX_ROWS |
Предел строк файла/списка ID на экране «Ручные операции» | 1000000 |
loyalty.manual-operations.bulk-max-ids |
LOYALTY_MANUAL_OPERATIONS_BULK_MAX_IDS |
Предел ID у JSON-ручек (bulk-accruals/bulk-corrections/tier-bulk) и массовых статус-действий каталога; он же размер чанка, на которые режется файл при исполнении |
1000 |
loyalty.manual-operations.scheduler.enabled |
LOYALTY_MANUAL_OPERATIONS_SCHEDULER_ENABLED |
Шедулер отложенного начисления/корректировки — без него принятый отложенный пакет не исполнится никогда | true |
loyalty.manual-operations.scheduler.tick |
LOYALTY_MANUAL_OPERATIONS_SCHEDULER_TICK |
Как часто искать просроченные пакеты | PT30S |
loyalty.manual-operations.scheduler.max-ahead |
LOYALTY_MANUAL_OPERATIONS_SCHEDULE_MAX_AHEAD |
Предельная отсрочка выполнения пакета | P366D |
loyalty.manual-operations.events.tick |
LOYALTY_MANUAL_OPERATIONS_EVENTS_TICK |
Шаг опроса счётчиков для потока прогресса пакета (SSE) | PT1S |
loyalty.manual-operations.events.heartbeat |
LOYALTY_MANUAL_OPERATIONS_EVENTS_HEARTBEAT |
Период комментария-пульса SSE (чтобы прокси не закрыл тихое соединение) | PT15S |
loyalty.manual-operations.resume.enabled |
LOYALTY_MANUAL_OPERATIONS_RESUME_ENABLED |
Подхват брошенных пакетов после падения реплики посреди выполнения | true |
loyalty.manual-operations.resume.tick |
LOYALTY_MANUAL_OPERATIONS_RESUME_TICK |
Как часто искать брошенные пакеты | PT1M |
loyalty.manual-operations.resume.stale-after |
LOYALTY_MANUAL_OPERATIONS_RESUME_STALE_AFTER |
Пауза без признака жизни, после которой пакет считается брошенным | PT5M |
loyalty.manual-operations.kafka.enabled |
LOYALTY_MANUAL_OPERATIONS_KAFKA_ENABLED |
Транспорт чанков пакета через Kafka вместо очереди в памяти приложения | false |
loyalty.manual-operations.kafka.topic |
LOYALTY_MANUAL_OPERATIONS_KAFKA_TOPIC |
Топик чанков | loyalty.manual-operations.chunks |
loyalty.manual-operations.kafka.group-id |
LOYALTY_MANUAL_OPERATIONS_KAFKA_GROUP |
Группа потребителя | loyalty-billing-manual-operations |
loyalty.manual-operations.kafka.concurrency |
LOYALTY_MANUAL_OPERATIONS_KAFKA_CONCURRENCY |
Потоков потребителя (не больше числа партиций топика) | 1 |
loyalty.manual-operations.kafka.retry-attempts |
LOYALTY_MANUAL_OPERATIONS_KAFKA_RETRY_ATTEMPTS |
Повторов чанка при системном сбое, затем чанк закрывается прерванным | 3 |
loyalty.manual-operations.kafka.retry-delay |
LOYALTY_MANUAL_OPERATIONS_KAFKA_RETRY_DELAY |
Пауза между повторами | PT2S |
loyalty.manual-operations.kafka.publish-timeout |
LOYALTY_MANUAL_OPERATIONS_KAFKA_PUBLISH_TIMEOUT |
Ожидание подтверждения брокера при публикации чанка на приёме | PT10S |
Причина «Прочее» — отдельное право
У всех трёх ручных операций (начисление, корректировка, перевод уровня) причина «Прочее» доступна только с ролью LOYALTY_OPERATION_REASON_OTHER_WRITE поверх права на саму операцию. Справочник причин операций требует LOYALTY_DICTIONARY_READ — без него форма любой из трёх операций сообщает «Справочник причин недоступен».
Ingress: отключите буферизацию ответа для потока прогресса
GET /v1/customers/operations/batches/{batchId}/events — поток событий SSE. Если ingress
перед подом буферизует ответ (значение по умолчанию у большинства контроллеров), оператор не
видит хода выполнения пакета до его завершения. Для пути /v1/customers/operations/batches/*/events
отключите буферизацию (например, nginx.ingress.kubernetes.io/proxy-buffering: "off").
Рекомендуется подключение к Kafka
Без Kafka (LOYALTY_MANUAL_OPERATIONS_KAFKA_ENABLED=false, значение по умолчанию) пакеты
ручных операций выполняются через очередь в памяти приложения в один поток. При перезапуске пода
неисполненные чанки пакета пропадают вместе с процессом; их заново публикует подхват брошенных
пакетов (loyalty.manual-operations.resume.*, включён по умолчанию) — на старте приложения и затем
раз в resume.tick, если пакет не подаёт признаков жизни дольше resume.stale-after. Уже
проведённые строки повторно не проводятся, но выполнение пакета прерывается и продолжается с
задержкой. Для контуров, где используются ручные операции, включите транспорт через Kafka:
LOYALTY_MANUAL_OPERATIONS_KAFKA_ENABLED=true, LOYALTY_KAFKA_BOOTSTRAP и SASL-учётные данные.
Сгорание баллов: предупреждения и сборщик просроченных резервов¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.expiry.cron |
LOYALTY_EXPIRY_CRON |
Плановое сгорание баллов (тот же прогон, что и ручной POST /v1/maintenance/points/expire); расписание — в поясе программы |
выключено (в примерах чарта — 0 30 0 * * *: баллы со сроком «до 23:59:59» сгорают в ту же ночь) |
loyalty.expiry.batch-size |
LOYALTY_EXPIRY_BATCH_SIZE |
Сколько просроченных лотов сжигает один прогон; остаток разбирается следующим прогоном | 5000 |
loyalty.expiry.notify-before |
LOYALTY_EXPIRY_NOTIFY_BEFORE |
Шаги предупреждения о сгорании — один или несколько сроков до сгорания через запятую (ISO-8601, например P30D,P7D,P1D); по каждому лоту и шагу — одно предупреждение. Непригодное значение (ноль, отрицательное, повтор) заменяется умолчанием |
P7D (в примерах чарта — P7D,P1D) |
loyalty.expiry.notify-batch-size |
LOYALTY_EXPIRY_NOTIFY_BATCH_SIZE |
Сколько лотов забирает один прогон предупреждений | 5000 |
loyalty.expiry.notify-cron |
LOYALTY_EXPIRY_NOTIFY_CRON |
Расписание прогона предупреждений о скором сгорании — в поясе программы | выключено (в примерах чарта — 0 0 9 * * *, 09:00 по поясу программы) |
loyalty.redemption.hold-expiry.cron |
LOYALTY_REDEMPTION_HOLD_EXPIRY_CRON |
Сборщик просроченных резервов баллов (hold без commit/cancel по истечении срока) |
выключено (на активных контурах включают на раз в минуту) |
Прогон сгорания выполняется один на кластер (ручной вызов во время идущего прогона получает 409 points_expiry_in_progress), пачками в порядке «срок, лот», каждый счёт — в своей транзакции. Пробный прогон без изменений — POST /v1/maintenance/points/expire?dryRun=true; ручной запуск прогона предупреждений — POST /v1/maintenance/points/expiry-notice (роль LOYALTY_MAINTENANCE_WRITE, 409 points_expiry_notice_in_progress, если прогон уже идёт). Шаг, окно которого прошло к моменту начисления, пропускается: при единственном шаге P7D лот, начисленный меньше чем за неделю до срока, предупреждения не получит — поэтому для контура рекомендуется P7D,P1D. Предупреждения и сообщение о факте сгорания ставятся в очередь исходящих уведомлений (см. «Уведомления клиентам: очередь исходящих» ниже).
Первое включение сгорания на контуре
Рекомендуемый порядок: включить предупреждения (LOYALTY_EXPIRY_NOTIFY_CRON) → выждать самый длинный
шаг LOYALTY_EXPIRY_NOTIFY_BEFORE → выполнить пробный прогон dryRun=true и оценить объём → задать
LOYALTY_EXPIRY_CRON. Подробнее о поведении сгорания для оператора — в разделе
«Сгорание баллов».
Акции: автоведение по периоду¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.promotion.lifecycle.enabled |
LOYALTY_PROMOTION_LIFECYCLE_ENABLED |
Автоматический перевод акции в «Активна» по дате начала и в «Завершена» по дате окончания периода | false (включён на dev/qa; на prod включается вместе с выкаткой обновлённого каталога акций) |
loyalty.promotion.lifecycle.interval |
LOYALTY_PROMOTION_LIFECYCLE_INTERVAL |
Как часто проверять акции на автопереход | PT1M |
Акции: согласование запуска и статистика переменных покупки¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.promotion.approval.enabled |
LOYALTY_PROMOTION_APPROVAL_ENABLED |
Согласование запуска акции по принципу «четырёх глаз»: запуск только согласованной акции, содержательная правка сбрасывает согласование. Выключенное — действия согласования отвечают 409 approval_disabled, запуск идёт без согласования |
false (в примерах чарта включено на dev и qa, выключено на prod) |
loyalty.purchase-attributes.stats-retention |
LOYALTY_PURCHASEATTRIBUTES_STATSRETENTION |
Сколько хранится статистика переменных покупки для формы акции (запас сверх окна «7 дней», которое читает форма) | P30D |
loyalty.purchase-attributes.cleanup-cron |
LOYALTY_PURCHASEATTRIBUTES_CLEANUPCRON |
Расписание уборки этой статистики | выключено |
Право запуска акции — отдельная роль при любом значении настройки
Запуск акции (POST /v1/promotions/{id}/activate, POST /v1/promotions/bulk-activate) требует роль
LOYALTY_PROMOTION_ACTIVATE независимо от того, включено ли согласование; решение по согласованию —
роль LOYALTY_PROMOTION_APPROVE. Перед выкаткой версии с этими ролями повторно импортируйте роли
Keycloak и выдайте LOYALTY_PROMOTION_ACTIVATE тем, кто раньше запускал акции правом
LOYALTY_PROMOTION_WRITE, — иначе запуск ответит 403 access_denied. Экрана для переключения
настройки в интерфейсе нет — это настройка развёртывания.
Флаг согласования задаётся и веб-интерфейсу
API значение LOYALTY_PROMOTION_APPROVAL_ENABLED не отдаёт, поэтому веб-интерфейс узнаёт о
согласовании из своего runtime-конфига — переменной PROMOTION_APPROVAL_ENABLED пода loyalty-ui
(см. «Runtime-конфиг веб-интерфейса» ниже). Задавайте обе переменные одинаково: в примерах значений
чартов loyalty-ui они согласованы с бэкендом (включено на dev и qa, выключено на prod).
Асинхронный приём команд (intake) и брокер сообщений¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.intake.scheduler-enabled |
LOYALTY_INTAKE_SCHEDULER_ENABLED |
Фоновый разбор очереди приёма | true |
loyalty.intake.tick |
LOYALTY_INTAKE_TICK |
Как часто воркер разбирает очередь | PT1S |
loyalty.intake.batch-size |
LOYALTY_INTAKE_BATCH_SIZE |
Команд за один тик | 50 |
loyalty.intake.lease |
LOYALTY_INTAKE_LEASE |
Срок аренды обработчика (истёк — команда снова доступна) | PT5M |
loyalty.intake.max-attempts |
LOYALTY_INTAKE_MAX_ATTEMPTS |
Число попыток исполнения до откладывания в разбор (PARKED) |
10 |
loyalty.intake.backoff / .max-backoff |
LOYALTY_INTAKE_BACKOFF / LOYALTY_INTAKE_MAX_BACKOFF |
Нарастающая пауза между попытками | PT2S / PT10M |
loyalty.intake.done-ttl |
LOYALTY_INTAKE_DONE_TTL |
Срок хранения исполненных команд — он же окно дедупликации приёма | P7D |
loyalty.intake.reaper-tick |
LOYALTY_INTAKE_REAPER_TICK |
Как часто искать зависшую аренду (реплика упала между захватом и коммитом) | PT1M |
loyalty.intake.max-schedule |
LOYALTY_INTAKE_MAX_SCHEDULE |
Предельная отсрочка исполнения команды | P366D |
loyalty.intake.retention-tick |
LOYALTY_INTAKE_RETENTION_TICK |
Как часто чистить исполненные команды старше done-ttl |
PT1H |
loyalty.intake.kafka.enabled |
LOYALTY_INTAKE_KAFKA_ENABLED |
Приём команд через Kafka-потребитель (поверх той же очереди, что и REST-приём) | false |
loyalty.intake.kafka.topic |
LOYALTY_INTAKE_KAFKA_TOPIC |
Топик команд | loyalty.intake.commands |
loyalty.intake.kafka.group-id |
LOYALTY_INTAKE_KAFKA_GROUP |
Группа потребителя | loyalty-billing-intake |
loyalty.intake.kafka.concurrency |
LOYALTY_INTAKE_KAFKA_CONCURRENCY |
Потоков потребителя | 1 |
loyalty.intake.kafka.retry-attempts / .retry-delay |
LOYALTY_INTAKE_KAFKA_RETRY_ATTEMPTS / LOYALTY_INTAKE_KAFKA_RETRY_DELAY |
Повтор при сбое обработки сообщения | 3 / PT2S |
Общее подключение к брокеру (используется всеми Kafka-транспортами модуля: intake.kafka, manual-operations.kafka, а также отправкой событий смены уровня tier-events и уведомлений notifications — см. разделы ниже):
| Переменная | Назначение | Значение по умолчанию |
|---|---|---|
LOYALTY_KAFKA_BOOTSTRAP |
Адрес брокера | localhost:9092 |
LOYALTY_KAFKA_SECURITY_PROTOCOL |
Протокол безопасности (платформенный брокер — SASL_PLAINTEXT) |
PLAINTEXT |
LOYALTY_KAFKA_SASL_MECHANISM |
Механизм SASL | SCRAM-SHA-512 |
LOYALTY_KAFKA_SASL_JAAS_CONFIG |
Строка JAAS учётной записи брокера (секрет) | — |
Оба Kafka-транспорта выключены по умолчанию
На контуре без брокера приложение стартует и работает как раньше: команды принимаются только синхронным REST-путём, а чанки пакетов ручных операций исполняются очередью внутри самого приложения. Включайте транспорт только после того, как на брокере заведены топик, ACL на топик и на группу потребителя, и получены учётные данные SASL — без права на группу поломка самая тихая из возможных: сообщения публикуются, читать их некому, а пакет/команда «вечно висит в очереди», выглядя штатно работающей системой.
В примерах значений чарта values.dev.yaml, values.qa.yaml и values.prod.yaml оба транспорта
включены (LOYALTY_INTAKE_KAFKA_ENABLED=true, LOYALTY_MANUAL_OPERATIONS_KAFKA_ENABLED=true), а строка
JAAS приходит из k8s-секрета loyalty-kafka (ключ sasl-jaas-config). Значение по умолчанию в самом
приложении по-прежнему false — включение и откат выполняются через значения чарта контура. Если один
брокер обслуживает несколько контуров, разведите на них топики
(LOYALTY_INTAKE_KAFKA_TOPIC, LOYALTY_MANUAL_OPERATIONS_KAFKA_TOPIC, LOYALTY_TIER_EVENTS_TOPIC):
иначе группы потребителей контуров станут общими и брокер будет делить партиции между их подами
произвольно, а события смены уровня разных контуров смешаются в одном топике.
Кеш баланса (Redis)¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.balance-cache.enabled |
LOYALTY_BALANCE_CACHE_ENABLED |
Включение cache-aside кеша баланса в Redis | false |
loyalty.balance-cache.ttl |
LOYALTY_BALANCE_CACHE_TTL |
Срок жизни ключа — больше окна, за которое незавершённое изменение точно закончится | 30s |
loyalty.balance-cache.recheck |
LOYALTY_BALANCE_CACHE_RECHECK |
Как часто сверяться с БД, пока по клиенту есть незавершённые изменения | 1s |
loyalty.balance-cache.recent-keys |
LOYALTY_BALANCE_CACHE_RECENT_KEYS |
Предел памяти локального ограничителя сверок | 100000 |
Подключение к Redis задаётся стандартными свойствами Spring Data Redis (spring.data.redis.* / SPRING_DATA_REDIS_*). Отказ Redis (недоступность, таймаут) не прерывает операцию — модуль откатывается на чтение баланса из БД.
Кеш справочников¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.reference-cache.ttl |
LOYALTY_REFERENCE_CACHE_TTL |
Срок жизни в памяти реплики для плана счетов, валют, субсчетов, БД-переопределений system_config, а также разрешения кода торговой точки из покупки в точку, город и регион и артикула в категории товара каталога; 0s выключает кэш |
30s |
Кэш не согласован между репликами: правку справочника соседняя реплика увидит не позже чем через TTL; на своей реплике изменение действует сразу (сброс кэша при записи). Исключение — разрешение кода торговой точки и артикула: оно сбросом при записи не обновляется, и правка точки, города или товара доходит до условий акций не позже чем через TTL на каждой реплике.
Rate limiting (ограничение частоты)¶
Ограничение частоты запросов покрывает весь интеграционный периметр одним фильтром (алгоритм GCRA), а не выборочные эндпоинты. Правила задаются списком loyalty.rate-limit.rules — выигрывает первое совпавшее правило, порядок значим.
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.rate-limit.enabled |
LOYALTY_RATE_LIMIT_ENABLED |
Аварийный выключатель всего ограничителя | true |
loyalty.rate-limit.trust-forwarded-for |
LOYALTY_RATE_LIMIT_TRUST_XFF |
Брать адрес клиента из X-Forwarded-For (выключайте, если под выставлен наружу напрямую — иначе заголовок подделывается) |
true |
loyalty.rate-limit.max-keys-per-rule |
LOYALTY_RATE_LIMIT_MAX_KEYS |
Предел ключей на правило в памяти реплики (fail-open при переполнении — не должно отбивать трафик) | 100000 |
loyalty.rate-limit.idle-eviction |
— | Через сколько простоя ключ вычищается | PT10M |
loyalty.rate-limit.sweep-interval |
— | Как часто идёт очистка простаивающих ключей | PT5M |
Готовые правила (капасити задаётся как target / N, где N — число реплик, так как состояние ограничителя in-memory на реплику):
| Правило | Путь | Capacity/окно | Режим | Переменная capacity |
|---|---|---|---|---|
pos-card-resolve |
GET /v1/integration/pos/cards/** |
60/мин | ENFORCE |
LOYALTY_RATE_LIMIT_POS_RESOLVE |
checkscan-overview |
GET /v1/integration/checkscan/customers/** |
60/мин | ENFORCE |
LOYALTY_RATE_LIMIT_CHECKSCAN |
integration-write |
POST/PUT/PATCH/DELETE /v1/integration/** |
600/мин | LOYALTY_RATE_LIMIT_INTEGRATION_MODE (деф. OBSERVE) |
LOYALTY_RATE_LIMIT_INTEGRATION_WRITE |
integration-read |
GET /v1/integration/** |
300/мин | LOYALTY_RATE_LIMIT_INTEGRATION_MODE (деф. OBSERVE) |
LOYALTY_RATE_LIMIT_INTEGRATION_READ |
events |
POST /v1/events/** |
1200/мин | LOYALTY_RATE_LIMIT_EVENTS_MODE (деф. OBSERVE) |
LOYALTY_RATE_LIMIT_EVENTS |
bulk |
Массовые/пакетные операции (импорт акций, tier-bulk, сгорание, bulk-accruals/bulk-corrections, приём пакета файлом) |
10/мин | LOYALTY_RATE_LIMIT_BULK_MODE (деф. OBSERVE) |
LOYALTY_RATE_LIMIT_BULK |
b2c |
/v1/b2c/** (ключ — субъект клиентского токена) |
120/мин | LOYALTY_RATE_LIMIT_B2C_MODE (деф. OBSERVE) |
LOYALTY_RATE_LIMIT_B2C |
Режим OBSERVE перед ENFORCE
Новые правила заводятся в режиме OBSERVE: он считает метрику loyalty.ratelimit{outcome=observed} и пишет предупреждение в лог, но не отбивает запрос. Порог обкатывается на реальном трафике контура, затем переводится в ENFORCE соответствующей env-переменной режима. Правила pos-card-resolve и checkscan-overview — исторические, сразу в ENFORCE, их коды ответов (resolve_rate_limited, checkscan_overview_rate_limited) — часть контракта интеграций.
Загружаемые файлы¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
spring.servlet.multipart.max-file-size |
LOYALTY_IMPORT_MAX_FILE_SIZE |
Предел размера одного загружаемого файла | 20MB |
spring.servlet.multipart.max-request-size |
LOYALTY_IMPORT_MAX_REQUEST_SIZE |
Предел размера всего multipart-запроса | 21MB |
loyalty.import.max-rows |
LOYALTY_IMPORT_MAX_ROWS |
Предел строк файла импорта клиентов | 10000 |
loyalty.import.max-field-length |
LOYALTY_IMPORT_MAX_FIELD_LENGTH |
Предел длины значения поля | 512 |
loyalty.import.max-problems |
LOYALTY_IMPORT_MAX_PROBLEMS |
Предел размера отчёта об ошибках (счётчики в ответе всегда полные) | 200 |
loyalty.import.scan.enabled |
LOYALTY_IMPORT_SCAN_ENABLED |
Антивирусная проверка загружаемых файлов (fail-closed: включённая проверка без доступного сканера отказывает, а не пропускает файл) | false |
Загружаемый файл проверяется до разбора содержимого: сигнатура первых байт (не архив, не исполняемый файл), кодировка строго UTF-8 (без замены некорректных байт), затем — правила разбора CSV. У ручных операций свой отдельный предел строк — loyalty.manual-operations.max-rows (раздел выше), а не loyalty.import.max-rows: список ID — не импорт профилей клиентов.
CORS¶
| Переменная | Назначение | Значение по умолчанию |
|---|---|---|
LOYALTY_CORS_ALLOWED_ORIGINS |
Origin, которым разрешены cross-origin запросы к основному API (через запятую) | — (не задано = запрещено) |
LOYALTY_B2C_CORS_ALLOWED_ORIGINS |
То же для клиентского контура /v1/b2c/**; пусто — действует список выше |
— |
Значения читаются типизированной настройкой (loyalty.security.cors-allowed-origins / .b2c-cors-allowed-origins), а не напрямую из окружения — видны в /actuator/configprops и подставляются в тестах.
Служебный предпросмотр B2C-кабинета сотрудником (роль LOYALTY_PROFILE_READ) открывает не произвольного клиента, а только одного, заданного настройкой loyalty.b2c.preview-external-id (LOYALTY_B2C_PREVIEW_EXTERNAL_ID, по умолчанию PLT-1002).
Идемпотентность: журнал с ограничением хранения¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.idempotency.retention |
LOYALTY_IDEMPOTENCY_RETENTION |
Окно повтора: сколько хранится строка журнала идемпотентности (полное тело ответа денежной операции) | P7D |
loyalty.idempotency.cleanup-cron |
LOYALTY_IDEMPOTENCY_CLEANUP_CRON |
Расписание уборки журнала старше окна повтора; пусто (-) — уборка выключена |
выключено |
Запрет двойной проводки от журнала не зависит
Журнал идемпотентности — оптимизация (экономит повторную работу, возвращает прежний ответ на повторный вызов с тем же Idempotency-Key). Запрет двойной проводки после истечения окна держит бэкстоп ядра леджера (уникальность ссылки на проводку), поэтому уборка журнала безопасна даже без включённого расписания. На проде расписание стоит включать (например, раз в сутки в непиковый час) — иначе журнал растёт без ограничения.
SIEM: передача событий аудита¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.siem.enabled |
LOYALTY_SIEM_ENABLED |
Включение потока событий аудита во внешний SIEM | false |
loyalty.siem.host / .port |
LOYALTY_SIEM_HOST / LOYALTY_SIEM_PORT |
Адрес приёмника | — / 514 |
loyalty.siem.transport |
LOYALTY_SIEM_TRANSPORT |
TCP (гарантия доставки) или UDP |
TCP |
loyalty.siem.format |
LOYALTY_SIEM_FORMAT |
CEF (понимают все распространённые SIEM без парсера под продукт) или JSON |
CEF |
loyalty.siem.tick |
LOYALTY_SIEM_TICK |
Период отправки — «близко к реальному времени» | PT10S |
loyalty.siem.batch-size |
LOYALTY_SIEM_BATCH_SIZE |
Записей журнала за один тик | 500 |
loyalty.siem.timeout |
LOYALTY_SIEM_TIMEOUT |
Ожидание приёмника; истёк — попытка неудачна, курсор не двигается | PT5S |
loyalty.siem.lag |
LOYALTY_SIEM_LAG |
Отступ верхней границы выборки от текущего момента (транзакция становится видимой позже своей отметки времени) | PT5S |
loyalty.siem.facility |
LOYALTY_SIEM_FACILITY |
Категория источника syslog | 13 |
loyalty.siem.product |
LOYALTY_SIEM_PRODUCT |
Имя продукта в заголовке события | loyalty-billing |
Буфер — сам журнал аудита
Поток не заводит отдельную очередь: буфером служит сам (durable) журнал аудита, хранится только курсор — докуда отдано. Пока приёмник недоступен, поток стоит и продолжается с того же места после починки; обрыв на полуслове даёт повтор последней пачки (SIEM дедуплицирует события, повтор безопаснее пропуска). Выключено по умолчанию: адрес приёмника знает только контур заказчика.
Аудит: защита журнала от подделки¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.audit.seal.enabled |
LOYALTY_AUDIT_SEAL_ENABLED |
Периодические печати журнала (цепочка отпечатков, делающая подделку записи обнаружимой) | false |
loyalty.audit.seal.tick |
LOYALTY_AUDIT_SEAL_TICK |
Как часто ставится печать. Вместе с lag задаёт окно lag + 2 × tick, старше которого непокрытая запись попадает в uncovered |
PT5M |
loyalty.audit.seal.lag |
LOYALTY_AUDIT_SEAL_LAG |
Не печатать записи моложе этого возраста (верхняя граница печати обязана быть заведомо закоммиченной) | PT1M |
loyalty.audit.seal.key |
LOYALTY_AUDIT_SEAL_KEY |
Секрет HMAC для отпечатка. Обязателен, если enabled=true — без него старт падает |
— |
loyalty.audit.seal.verify-cron |
LOYALTY_AUDIT_SEAL_VERIFY_CRON |
Ночная проверка целостности всей цепочки печатей; она же обновляет показатели loyalty.audit.seal.broken и loyalty.audit.seal.uncovered. Время — UTC |
0 25 3 * * * |
Включать только с секретом
Отпечаток — HMAC с секретом из окружения, а не голый хеш: журнал и цепочка печатей лежат в той же базе, которую защищают, и без секрета отпечаток пересчитывается общедоступным алгоритмом без всякой защиты. Включённая подсистема без LOYALTY_AUDIT_SEAL_KEY не поднимется — приложение остановится с понятной ошибкой при старте, а не отчитается «журнал цел», будучи неспособной это установить. Смена ключа обесценивает ранее поставленные печати — это долгоживущий секрет контура, а не предмет ротации.
Уровни: пересмотр и событие смены уровня¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.tier-review.cron |
LOYALTY_TIER_REVIEW_CRON |
Пересмотр уровней в личную дату клиента: понижение и продление периода происходят только здесь, здесь же снимается ручное назначение уровня. Расписание — в поясе среды исполнения (UTC) | выключено (в примерах чарта задано только для dev — 0 20 3 * * *; для qa и prod не задано) |
loyalty.tier-review.batch-size |
LOYALTY_TIER_REVIEW_BATCH_SIZE |
Клиентов в пачке захвата; каждый клиент — своя транзакция | 500 |
loyalty.tier-review.max-duration |
LOYALTY_TIER_REVIEW_MAX_DURATION |
Предельная длительность одного запуска; остаток ждёт следующего | PT1H |
loyalty.tier-events.enabled |
LOYALTY_TIER_EVENTS_ENABLED |
Отправка события смены уровня tiers.customer.tier-changed в брокер |
false |
loyalty.tier-events.topic |
LOYALTY_TIER_EVENTS_TOPIC |
Топик события | loyalty.tiers.tier-changed |
loyalty.tier-events.tick / .batch-size |
LOYALTY_TIER_EVENTS_TICK / LOYALTY_TIER_EVENTS_BATCH_SIZE |
Период и размер пачки отправителя | PT5S / 200 |
loyalty.tier-events.publish-timeout |
LOYALTY_TIER_EVENTS_PUBLISH_TIMEOUT |
Ожидание подтверждения брокера; без подтверждения событие уйдёт следующим тиком | PT10S |
loyalty.tier-events.pending-ttl |
LOYALTY_TIER_EVENTS_PENDING_TTL |
Сколько неотправленное событие ждёт отправки, прежде чем его спишут без доставки | P7D |
loyalty.tier-events.retention |
LOYALTY_TIER_EVENTS_RETENTION |
Сколько отправленные и списанные события хранятся в очереди | P7D |
Без пересмотра уровни не понижаются
Пока LOYALTY_TIER_REVIEW_CRON не задан, понижение уровня и продление периода не выполняются —
уровень клиента только растёт по покупкам. Задайте расписание на контуре, где действуют правила
пересчёта уровней конфигурации программы. События смены уровня пишутся в очередь исходящих всегда
и уходят после включения отправки; пролежавшие дольше pending-ttl не отправляются. Списание
устаревших и уборку отправленных по retention выполняет сам отправитель, поэтому пока отправка
выключена, очередь событий уровня (tier_event_outbox) только растёт.
Открытие счетов в новых валютах¶
У каждого клиента в статусе ACTIVE есть счёт в каждой включённой валюте. При создании включённой валюты и включении выключенной фоновая задача открывает счёт каждому активному клиенту; при выкладке версии разовый прогон ставится по всем уже включённым валютам.
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.account-opening.enabled |
LOYALTY_ACCOUNT_OPENING_ENABLED |
Фоновое открытие счетов | true |
loyalty.account-opening.tick |
LOYALTY_ACCOUNT_OPENING_TICK |
Период проверки очереди задач | PT10S |
loyalty.account-opening.batch-size |
LOYALTY_ACCOUNT_OPENING_BATCH_SIZE |
Клиентов в одной транзакции | 1000 |
loyalty.account-opening.max-duration |
LOYALTY_ACCOUNT_OPENING_MAX_DURATION |
Предельная длительность одного тика | PT50S |
Задача безопасна на нескольких репликах и продолжается после перезапуска пода. Валюта, в которой счёт открыть не на что (нет базового субсчёта или плана счетов), заведению клиента не мешает: такие случаи считает показатель loyalty.accounts.opening.unopenable (см. «Наблюдаемость»), а задача откроет счета после исправления.
Сегменты клиентов из внешней системы¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.segments.max-members-per-request |
LOYALTY_SEGMENTS_MAX_MEMBERS_PER_REQUEST |
Предел участников в одной пачке заливки (больше — 413 segment_batch_too_large); предела числа клиентов в сегменте нет |
10000 |
loyalty.segments.complete-statement-timeout |
LOYALTY_SEGMENTS_COMPLETE_STATEMENT_TIMEOUT |
Таймаут запроса, применяющего состав при завершении заливки | 10m |
loyalty.segments.history-retention |
LOYALTY_SEGMENTS_HISTORY_RETENTION |
Срок хранения истории входов и выходов клиентов | P3Y |
loyalty.segments.fill-retention |
LOYALTY_SEGMENTS_FILL_RETENTION |
Срок хранения журнала заливок | P1Y |
loyalty.segments.cleanup-cron |
LOYALTY_SEGMENTS_CLEANUP_CRON |
Уборка по срокам хранения (UTC) | выключено (в примерах чарта — 0 50 3 * * *) |
Приём сегментов описан в разделе «Интеграции» → «Сегменты клиентов из внешней системы».
Профиль клиента: источники данных карточки¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.client-profile.source-schemas |
LOYALTY_CLIENT_PROFILE_SOURCE_SCHEMAS |
Белый список схем БД (через запятую), из которых администратор выбирает таблицы-источники карточек и таблиц профиля клиента | пусто — выбор источников выключен |
loyalty.client-profile.max-bulk-ids |
LOYALTY_CLIENT_PROFILE_MAX_BULK_IDS |
Предел пакетного удаления полей и версий настройки карточки (больше — 400 bulk_size_exceeded) |
200 |
Чтение чужих таблиц — только из белого списка
Источники карточки — таблицы смежных систем в той же базе. Приложение видит только схемы из
LOYALTY_CLIENT_PROFILE_SOURCE_SCHEMAS и только то, на что у роли приложения в БД есть права;
строки связываются с клиентом по строковой колонке external_id. Выдавайте роли приложения права
на чтение ровно тех таблиц, которые нужны карточке.
Уведомления клиентам: очередь исходящих¶
Сообщения клиенту (предупреждения о сгорании, сообщение о факте сгорания, уведомления о начислении и списании) ставятся в очередь исходящих outbound_notification в той же транзакции, что и сам факт: откат операции уносит и сообщение, касса не ждёт канал доставки. Отправитель публикует созревшие сообщения событием notifications.customer.message-requested в брокер — дальше канал (SMS, push, e-mail) выбирает потребитель, например диаграмма RT Flow.
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.notifications.enabled |
LOYALTY_NOTIFICATIONS_ENABLED |
Публикация сообщений в брокер. Выключенная — сообщения копятся в очереди | false |
loyalty.notifications.topic |
LOYALTY_NOTIFICATIONS_TOPIC |
Топик события | loyalty.notifications.customer-message |
loyalty.notifications.send-hour |
LOYALTY_NOTIFICATIONS_SEND_HOUR |
Час отправки несрочных сообщений — в поясе клиента | 9 |
loyalty.notifications.quiet-from / .quiet-to |
LOYALTY_NOTIFICATIONS_QUIET_FROM / LOYALTY_NOTIFICATIONS_QUIET_TO |
Тихие часы в поясе клиента | 21 / 9 |
loyalty.notifications.tick / .batch-size |
LOYALTY_NOTIFICATIONS_TICK / LOYALTY_NOTIFICATIONS_BATCH_SIZE |
Период и размер пачки отправителя | PT10S / 200 |
loyalty.notifications.max-attempts |
LOYALTY_NOTIFICATIONS_MAX_ATTEMPTS |
Попыток отправки до перевода сообщения в разбор | 5 |
loyalty.notifications.publish-timeout |
LOYALTY_NOTIFICATIONS_PUBLISH_TIMEOUT |
Ожидание подтверждения брокера | PT10S |
loyalty.notifications.pending-ttl |
LOYALTY_NOTIFICATIONS_PENDING_TTL |
Срок годности неотправленного сообщения | P7D |
loyalty.notifications.valid-for |
LOYALTY_NOTIFICATIONS_VALID_FOR |
Срок годности ответных сообщений (начисление, списание) | P1D |
loyalty.notifications.retention |
LOYALTY_NOTIFICATIONS_RETENTION |
Сколько хранятся закрытые сообщения | P90D |
loyalty.notifications.housekeeping-tick |
LOYALTY_NOTIFICATIONS_HOUSEKEEPING_TICK |
Период уборки очереди (списание по сроку, удаление закрытого) — работает и при выключенной отправке | PT1H |
loyalty.notifications.purchase-city-window |
LOYALTY_NOTIFICATIONS_PURCHASE_CITY_WINDOW |
Окно «самого частого города покупок» для определения пояса клиента | P90D |
Пояс клиента определяется так: пояс из профиля (приходит из мастер-системы) → город профиля по справочнику городов → самый частый город покупок за окно → пояс программы. Отправляет очередь одна реплика; ответные сообщения уходят сразу, срочные (сгорание этой ночью) не ждут утра. Сообщение, не ушедшее за max-attempts попыток, переходит в разбор: GET /v1/admin/notifications (роль LOYALTY_NOTIFICATION_READ), вернуть в очередь или снять с основанием — POST /v1/admin/notifications/{id}/retry и .../discard (роль LOYALTY_NOTIFICATION_WRITE). Города профиля, по которым пояс не определился, показывает GET /v1/admin/timezones/unmatched-cities (роль LOYALTY_DICTIONARY_READ) — по нему дополняют справочник городов при внедрении. Экрана разбора очереди в интерфейсе нет.
Включайте отправку после настройки потребителя
Включайте LOYALTY_NOTIFICATIONS_ENABLED только после того, как на брокере заведён топик и
потребитель готов доставлять сообщения. Сообщения, пролежавшие в очереди дольше pending-ttl, при
включении не уходят разом, а списываются.
Мобильный кошелёк¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.wallet.enabled |
LOYALTY_WALLET_ENABLED |
Выпуск карты участника в мобильный кошелёк | false |
loyalty.wallet.pass-type-identifier |
LOYALTY_WALLET_PASS_TYPE_ID |
Pass Type ID сертификата Apple | pass.ru.example.loyalty |
loyalty.wallet.team-identifier |
LOYALTY_WALLET_TEAM_ID |
Team ID Apple Developer | TEAMID0000 |
loyalty.wallet.google-issuer-id |
LOYALTY_WALLET_GOOGLE_ISSUER_ID |
Issuer ID эмитента Google Wallet | 0000000000000 |
loyalty.wallet.origins |
LOYALTY_WALLET_ORIGINS |
Домены, которым разрешено сохранять карту в Google Кошелёк (через запятую) | — |
Подпись — заглушка
Пока сертификат Apple (Pass Type ID) и ключ сервисного аккаунта Google не подключены (секреты заказчика передаются адаптеру подписи отдельно, не через эти переменные), выпущенный пакет Apple структурно верен, но не подписан (заголовок ответа X-Loyalty-Pass-Signed: false), а ссылка «Сохранить в Google Кошелёк» не собирается вовсе. Включайте loyalty.wallet.enabled только вместе с реальным адаптером подписи.
Реферальная связь и контакты клиента¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.contact.phone.default-country |
LOYALTY_CONTACT_PHONE_COUNTRY |
Код страны для нормализации телефона | 7 |
loyalty.contact.phone.canonical-form |
LOYALTY_CONTACT_PHONE_FORM |
Каноничная форма телефона: PLUS (+7XXXXXXXXXX) или LOCAL (8XXXXXXXXXX) |
PLUS |
Реферальный код клиента генерируется приложением автоматически при создании клиента — отдельных настроек не требует.
Валюты и обмен баллов¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.currency.exchange.mapping |
— | Соответствие валюта → счета пополнения/депозита для операций обмена (см. таблицу выше) | PTS → MRK, RUB → RUB_REVENUE |
Справочник валют — источник истины по курсу
Список валют (деньги и баллы одной записью, тип MONEY/POINTS), их состояние и история курса ведутся справочником /v1/admin/currencies (роли LOYALTY_DICTIONARY_READ/WRITE — устаревший /v1/admin/point-currencies работает поверх того же справочника для обратной совместимости). Курс валюты баллов — не переменная окружения, а запись справочника. Бюджет акций и денежная оценка обязательств в отчёте по баллам считаются по курсу валюты баллов программы (points.currency). Отдельного свойства валюты бюджета больше нет: loyalty.promotion.budget.point-currency и customer.default.currency не читаются, и если они заданы на контуре, их можно убрать. Пары обмена валют ведутся справочником /v1/admin/exchange-pairs (роли LOYALTY_DICTIONARY_READ/WRITE), смена курса пары пишется в журнал аудита.
Леджер: партиционирование¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.ledger.partitions.months-ahead |
LOYALTY_LEDGER_PARTITIONS_AHEAD |
На сколько месяцев вперёд держать нарезанные партиции | 12 |
loyalty.ledger.partitions.warn-runway-days |
LOYALTY_LEDGER_PARTITIONS_WARN_DAYS |
Порог предупреждения о коротком запасе нарезки | 90 |
loyalty.ledger.partitions.interval |
LOYALTY_LEDGER_PARTITIONS_INTERVAL |
Как часто проверять нарезку | PT6H |
loyalty.ledger.partitions.integrity-cron |
LOYALTY_LEDGER_INTEGRITY_CRON |
Ночная проверка уникальности идентификатора проводки и отсутствия лотов-сирот | 0 15 3 * * * |
Генератор идентификаторов (snowflake)¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.id-generator.snowflake.instance-id-bits |
— | Разрядность номера реплики в идентификаторе | 10 |
loyalty.id-generator.snowflake.sequence-bits |
— | Разрядность порядкового номера внутри миллисекунды | 12 |
loyalty.id-generator.snowflake.generator-epoch |
— | Эпоха генератора (момент начала отсчёта) | 2026-01-01T00:00:00.000Z |
loyalty.id-generator.lease.ttl |
— | Срок аренды слота instance-id; протухший слот переиспользуется | PT10M |
loyalty.id-generator.lease.heartbeat |
— | Период продления аренды. Инвариант: ttl ≥ 3 × heartbeat (проверяется при старте) |
PT1M |
Эти параметры затрагивают формат идентификаторов и совместимость с историческими данными — менять их на контуре с накопленными данными не следует.
Обмен данными с внешними системами¶
| Свойство | Переменная окружения | Назначение | Значение по умолчанию |
|---|---|---|---|
loyalty.data-exchange.lag |
LOYALTY_DATA_EXCHANGE_LAG |
Отступ верхней границы ленты изменений и заданий выгрузки от текущего момента | PT5M |
loyalty.data-exchange.tick |
LOYALTY_DATA_EXCHANGE_TICK |
Как часто проверять, не пора ли запустить задание регламента (за тик берётся одно задание) | PT30S |
loyalty.data-exchange.dtp.enabled |
LOYALTY_DTP_ENABLED |
Переливка витрин в целевую БД потребителя (DTP) | false |
loyalty.data-exchange.dtp.target.url / .username / .password |
LOYALTY_DTP_TARGET_URL / LOYALTY_DTP_TARGET_USER / LOYALTY_DTP_TARGET_PASSWORD |
Подключение к целевой БД | — |
loyalty.data-exchange.dtp.processes.<витрина>.batch-size |
LOYALTY_DTP_BATCH |
Размер пачки при переливке (пример для DWH_TRANSACTIONS) |
1000 |
Каталог витрин (datasets) и запрос вставки в целевую БД (merge-statement) задаются конфигурацией — это позволяет добавлять новые витрины без правки кода. Задания выгрузки в поставке засеяны выключенными (DWH_TRANSACTIONS, DWH_BALANCES, DWH_CUSTOMERS, ежечасное расписание) — включение требует настроенного приёмника. Расписание (cron) заданий трактуется в поясе программы.
Витрина проводок DWH_TRANSACTIONS отдаёт клиента и субсчёт по каждой ноге проводки — debitCustomerExternalId/creditCustomerExternalId и debitSubaccountCode/creditSubaccountCode (у системной ноги поле пустое): так в витрине видны обе стороны перевода баллов между клиентами. Запрос вставки в целевую БД при подключении приёмника задаётся под этот состав полей.
Запрос вставки DWH_TRANSACTIONS по умолчанию нужно заменить
Поставляемый в конфигурации приложения merge-statement витрины DWH_TRANSACTIONS ещё ссылается на
прежние поля :customerExternalId и :subaccountCode, которых в витрине больше нет: с ним переливка
этой витрины завершится ошибкой. Перед включением LOYALTY_DTP_ENABLED задайте собственный
loyalty.data-exchange.dtp.processes.DWH_TRANSACTIONS.merge-statement под поля по ногам проводки и
под таблицу приёмника.
Внешний справочник (dictionary-api) и MDM¶
Справочники работают в гибридном режиме: операционные справочники — локально (полный CRUD), а внешние soft-словари (мастер-данные) могут читаться из внешнего dictionary-api в режиме read-only. Сейчас ни один справочник продукта из dictionary-api не читается: товарные категории и товары — собственный каталог продукта (API /v1/admin/product-categories и /v1/admin/product-items с CSV-импортом; экрана в веб-интерфейсе пока нет), а сегменты клиентов приходят из внешней системы через интеграционный API (см. «Интеграции»).
| Переменная | Назначение | Значение по умолчанию |
|---|---|---|
LOYALTY_DICTIONARY_ENABLED |
Включение внешнего dictionary-api |
false |
LOYALTY_DICTIONARY_BASE_URL |
Базовый URL внешнего dictionary-api |
— |
LOYALTY_MDM_ADAPTER |
Источник golden-профиля клиента: local (встроенная заглушка) или dataocean (Data Ocean Governance MDM) |
local |
LOYALTY_MDM_BASE_URL |
Базовый URL MDM (при dataocean) |
— |
LOYALTY_MDM_ENTITY_SLUG |
Слаг сущности клиента (проектно-настраиваемый) | customer |
LOYALTY_MDM_EXTERNAL_SYSTEM |
Код системы-потребителя в MDM | LOYALTY |
LOYALTY_MDM_TOKEN_URL / _CLIENT_ID / _CLIENT_SECRET / _SCOPE |
Учётные данные OAuth2 client-credentials к MDM | — |
По умолчанию интеграция с dictionary-api выключена, и REMOTE-словари отдаются как available=false без обращения к сети. Недоступность MDM отдаётся как 503 mdm_unavailable, а не как «клиент не найден» — карточка клиента при этом показывает деградацию, а не ошибку 404.
Runtime-конфиг веб-интерфейса (loyalty-ui)¶
Веб-интерфейс читает конфигурацию из window._env_.loyalty, которую при старте контейнера рендерит entrypoint из переменных окружения пода (public/config.js в образе, файл перезаписывается — пересборка образа под контур не нужна).
| Переменная | Назначение | Значение по умолчанию |
|---|---|---|
API_URL |
Корень backend API (без хвостового /v1 дублирования — база должна оканчиваться на /v1) |
/loyalty/v1 (same-origin) |
BASE_PATH |
basename роутера standalone-режима |
/loyalty |
KEYCLOAK_URL |
Адрес Keycloak (только для standalone) | — |
KEYCLOAK_REALM |
Realm Keycloak (только для standalone) | — |
KEYCLOAK_CLIENT_ID |
Публичный клиент входа (общий для платформы — frontend) |
— |
COMMIT_TAG |
Версия сборки (git-тег, подставляет CI); Core UI показывает её на странице «Помощь» | — (пусто вне CI-сборки) |
PROMOTION_APPROVAL_ENABLED |
Согласование запуска акции в интерфейсе: true — включено, пусто или иное значение — выключено. Задаётся в паре с LOYALTY_PROMOTION_APPROVAL_ENABLED бэкенда |
— (выключено; в примерах чарта — true на dev и qa, false на prod) |
B2C_API_URL и B2C_TOKEN — код их читает, штатный entrypoint не рендерит
Рантайм-конфиг умеет принимать отдельный корень API (b2cApiUrl, по умолчанию — значение
API_URL) и статический токен для отладочного доступа к кабинету (b2cToken), но штатный
entrypoint-скрипт образа рендерит в config.js только API_URL, BASE_PATH, KEYCLOAK_*,
COMMIT_TAG и PROMOTION_APPROVAL_ENABLED. Чтобы задать B2C_API_URL/B2C_TOKEN на реальном контуре, config.js нужно
переопределить отдельно (доработать entrypoint или смонтировать готовый файл).
OIDC обязателен для standalone-режима
В standalone-режиме (без встраивания в Core UI) все три параметра KEYCLOAK_* обязательны — дефолтов в коде нет, их отсутствие интерфейс показывает как ошибку конфигурации при запуске. При встраивании в Core UI токен даёт хост-портал, и поля KEYCLOAK_* игнорируются.
Приоритет источников¶
flowchart LR
ENV["Переменная окружения<br/>(LOYALTY_*)"] -->|переопределяет| YAML["Свойство приложения<br/>(loyalty.*)"]
YAML -->|seed при старте| DB[("Таблицы леджера<br/>(валюты, план счетов,<br/>системные счета)")]
DB -.->|"приоритет над env<br/>для узкого набора ключей"| CFG["system_config<br/>(правится в админке)"]
Действует стандартный порядок Spring Boot: переменная окружения переопределяет значение из файла конфигурации. Из итоговых значений на старте формируются (при отсутствии) записи в БД леджера. Отдельно от этой цепочки — узкий набор ключей system_config (см. «Операционная конфигурация в БД» выше), для которых значение из БД приоритетнее переменной окружения и правится без передеплоя.
Механики-как-параметры — в дорожной карте
Настраиваемые шаблоны механик акций («механики-как-параметры») в текущей версии не реализованы — набор механик закрыт кодом. Это отдельный пункт дорожной карты, не пересекающийся с операционной конфигурацией выше.