Перейти к основному содержимому

Consumer: Rewards Events → Notifications (notifications-rewards-events)

Маппит события сервиса rewards (начисления комиссий, выплаты, правила) в NotificationKind и ставит уведомления в очередь доставки. Дом payload — rewards/events/; целевой агрегат — notification.md; общий алгоритм постановки в очередь — crm-events.md.

Назначение

Consumer group notifications-rewards-events уведомляет агента/агентство о начислениях, смене их статуса, движении выплаты и изменении правил вознаграждения. Платёжных операций не выполняет (out of scope) — только оповещает о фактах. persist-then-send; кросс-контекстной записи нет.

Owning-сервис и триггер

ПолеЗначение
Сервис/контекстnotifications
Consumer groupnotifications-rewards-events
Producerrewards
ContractVersionожидается 1; несовпадение — warn-and-proceed

Топики и маппинг событий

ТопикСобытие → NotificationKindПолучательUrgency
pin.rewards.commission-accrual-created.v1CommissionAccruedагент-бенефициар (+ руководитель)Normal
pin.rewards.commission-accrual-status-changed.v1CommissionStatusChanged; переход → MoneyReceivedno-op (уведомление о получении денег — только по commission-money-received, дубль исключён — rewards/events/commission-accrual-status-changed.md)агент-бенефициарNormal
pin.rewards.commission-money-received.v1CommissionMoneyReceived («деньги от застройщика получены»)агент-бенефициар + руководитель агентстваNormal
pin.rewards.referral-accruals.v1 (два контракта топика)created → ReferralRewardAccrued; status-changed → ReferralRewardStatusChanged (Approved/Paid/Cancelled)партнёр (PartnerUserId)Normal
pin.rewards.payout-order-status-changed.v1PayoutStatusChangedполучатель выплаты (агент/агентство)Normal
pin.rewards.reward-rule-changed.v1RewardRuleChangedагенты/агентство в scope правилаLow

Дом полей payload — rewards/events/commission-accrual-created.md и смежные; поля уведомления — RewardNotificationPayload (notification.md, Money).

Контекст события

  • Actor = система; tenant — по получателю (агентство — TenantId компании). Метаданные события (идентификатор сообщения, агрегат-источник, время) доступны из конверта события.
  • Идемпотентность: consumer-level по ключу notifications-rewards:{EventType}:{MessageId}; business-level — dedup-unique (INV-NTF-1).

Входные данные / payload события

Поле payloadТипОбяз.Как используется
AccrualId/PayoutOrderId/RuleIdGuidДаid для RewardNotificationPayload
BeneficiaryUserId/AgencyId/PartnerUserIdGuidДаполучатель fan-out (партнёр — для реферальных начислений)
AmountMoneyзав.Amount (сумма + валюта)
StatusenumДаStatus начисления/выплаты

Алгоритм

Общая логика постановки в очередь — crm-events.md:

  1. Восстановить контекст события и сверить ContractVersion (несовпадение — warn-and-proceed); проверить идемпотентность по идентификатору сообщения — дубль пропускается (ack, no-op).
  2. Проверить payload (Amount >= 0, идентификаторы не пусты); при нарушении — NTF_INVALID_PAYLOAD (в DLQ).
  3. Определить получателей (бенефициар / агентство / scope правила); применить дедуп; если получателей нет — no-recipients (ack).
  4. Для каждого получателя определить каналы по виду и срочности, резолвить шаблон и адрес (адрес — мягкая деградация при отсутствии); применить тихие часы. Если шаблон не резолвится — NTF_TEMPLATE_NOT_RESOLVED (в DLQ).
  5. Создать уведомление в статусе Pending и отрендерить тело (сумма форматируется по валюте); повтор события не создаёт дубль (dedup по ключу fan-out, INV-NTF-1). Уведомление и отметка идемпотентности фиксируются в одном согласованном изменении (persist-then-send).
  6. Успех → ack; retriable-сбой → retry; non-retriable → DLQ.

Диаграмма

Идемпотентность и replay

  • Consumer-level по MessageId; business-level — dedup-unique (INV-NTF-1). Replay: да.

Retry и DLQ

СбойКодRetryDLQ
Инфраструктурныйbackoff + пауза партицииНет
Нарушение payloadNTF_INVALID_PAYLOADНетДа + алерт
Шаблон не резолвитсяNTF_TEMPLATE_NOT_RESOLVEDНетДа + алерт

Наблюдаемость и безопасность

  • Logs: consumed/skipped-duplicate/no-recipients/enqueued/failed.
  • Metrics: notifications_enqueued_total{kind,channel}, notifications_consumer_lag_seconds{source=rewards}.
  • Безопасность: system-actor; сумма — sensitive (наружу только бенефициару); m2m ACL Kafka.

Кросс-сервисные контракты

Ожидает от rewards: at-least-once, устойчивый MessageId, партиционирование по AggregateId (начисление/выплата/правило — порядок статусов одной сущности сохраняется в партиции), ContractVersion; непустые AccrualId/PayoutOrderId/RuleId и BeneficiaryUserId/AgencyId; Status; Amount (Money, >= 0) для событий с суммой.

Ожидает от identity: для reward-rule-changed — состав агентов/агентства в scope правила (резолв аудитории); недоступность резолва — retriable (backoff), как инфраструктурный сбой.

Предоставляет: NotificationEntity(Pending) бенефициару/агентству/аудитории правила; обратной записи в rewards нет; платёжных операций consumer не выполняет (out of scope) — только оповещает о фактах; исходящее событие — только dispatched (dispatcher).

Порядок, версии и неизвестные события

  • Порядок статусов. commission-accrual-status-changed и payout-order-status-changed образуют последовательности (напр. Pending → Approved → Paid); consumer оповещает по-событийно, без сверки с текущим состоянием (не source of truth денег). Переупорядочивание может дать «поздний» статус — вред низкий; кросс-событийного подавления в первой версии нет. Повтор безопасен (dedup INV-NTF-1).
  • Неизвестное значение Status (новый под-статус) → не маппится → ignored-event (ack), лог; версия ниже/выше 1 — warn-and-proceed по известным полям; отсутствие обязательного известного поля или Amount < 0NTF_INVALID_PAYLOAD (DLQ).

Постусловие

  • Получателю (бенефициар/руководитель/агентство/аудитория правила) по каждому разрешённому каналу создан ровно один NotificationEntity(Pending); тело содержит сумму, отформатированную по валюте; отметка идемпотентности и уведомления — в одном согласованном изменении; offset подтверждён после фиксации.
  • Сумма доставлена только бенефициару/получателю выплаты; в логах маскируется.
  • Обратной записи в rewards и платёжных операций не выполнялось.

Acceptance criteria

  • Given commission-accrual-created с Amount, When consumer обрабатывает, Then бенефициару (и руководителю) создаётся NotificationEntity(Pending, Kind=CommissionAccrued) с суммой в валюте начисления.
  • Given payout-order-status-changed, When обработка, Then получателю выплаты создаётся PayoutStatusChanged с новым статусом.
  • Given reward-rule-changed, When резолв аудитории scope правила недоступен, Then обработка ретраится (backoff); после исчерпания — DLQ.
  • Given событие с Amount < 0, When проверка входа, Then NTF_INVALID_PAYLOAD, DLQ + алерт.
  • Given повторная доставка того же события, When обработка, Then skipped-duplicate (ack), дублей нет.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы