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 group | notifications-rewards-events |
| Producer | rewards |
| ContractVersion | ожидается 1; несовпадение — warn-and-proceed |
Топики и маппинг событий
| Топик | Событие → NotificationKind | Получатель | Urgency |
|---|---|---|---|
pin.rewards.commission-accrual-created.v1 | CommissionAccrued | агент-бенефициар (+ руководитель) | Normal |
pin.rewards.commission-accrual-status-changed.v1 | CommissionStatusChanged; переход → MoneyReceived — no-op (уведомление о получении денег — только по commission-money-received, дубль исключён — rewards/events/commission-accrual-status-changed.md) | агент-бенефициар | Normal |
pin.rewards.commission-money-received.v1 | CommissionMoneyReceived («деньги от застройщика получены») | агент-бенефициар + руководитель агентства | Normal |
pin.rewards.referral-accruals.v1 (два контракта топика) | created → ReferralRewardAccrued; status-changed → ReferralRewardStatusChanged (Approved/Paid/Cancelled) | партнёр (PartnerUserId) | Normal |
pin.rewards.payout-order-status-changed.v1 | PayoutStatusChanged | получатель выплаты (агент/агентство) | Normal |
pin.rewards.reward-rule-changed.v1 | RewardRuleChanged | агенты/агентство в 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/RuleId | Guid | Да | id для RewardNotificationPayload |
BeneficiaryUserId/AgencyId/PartnerUserId | Guid | Да | получатель fan-out (партнёр — для реферальных начислений) |
Amount | Money | зав. | → Amount (сумма + валюта) |
Status | enum | Да | → Status начисления/выплаты |
Алгоритм
Общая логика постановки в очередь — crm-events.md:
- Восстановить контекст события и сверить
ContractVersion(несовпадение — warn-and-proceed); проверить идемпотентность по идентификатору сообщения — дубль пропускается (ack, no-op). - Проверить payload (
Amount >= 0, идентификаторы не пусты); при нарушении —NTF_INVALID_PAYLOAD(в DLQ). - Определить получателей (бенефициар / агентство / scope правила); применить дедуп; если получателей нет —
no-recipients(ack). - Для каждого получателя определить каналы по виду и срочности, резолвить шаблон и адрес (адрес — мягкая
деградация при отсутствии); применить тихие часы. Если шаблон не резолвится —
NTF_TEMPLATE_NOT_RESOLVED(в DLQ). - Создать уведомление в статусе
Pendingи отрендерить тело (сумма форматируется по валюте); повтор события не создаёт дубль (dedup по ключу fan-out, INV-NTF-1). Уведомление и отметка идемпотентности фиксируются в одном согласованном изменении (persist-then-send). - Успех → ack; retriable-сбой → retry; non-retriable → DLQ.
Диаграмма
Идемпотентность и replay
- Consumer-level по
MessageId; business-level — dedup-unique (INV-NTF-1). Replay: да.
Retry и DLQ
| Сбой | Код | Retry | DLQ |
|---|---|---|---|
| Инфраструктурный | — | backoff + пауза партиции | Нет |
| Нарушение payload | NTF_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 < 0→NTF_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 проверка входа, ThenNTF_INVALID_PAYLOAD, DLQ + алерт. - Given повторная доставка того же события, When обработка, Then
skipped-duplicate(ack), дублей нет.
Обратные ссылки
Автоссылки: где используется этот документ.
- События (1): Event Contract: pin.rewards.commission-accrual-created
- index.md (1): Notifications Service
Связанные документы
- Целевой агрегат: notification.md; общий алгоритм — crm-events.md; входные контракты — rewards/events/; карта — README контекста.
- ADR: 0052, 0056.