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

Consumer: Wallet Events → Notifications (notifications-wallet-events)

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

Назначение

Consumer group notifications-wallet-events уведомляет холдера счёта о движении его внутреннего баланса: «вам начислено», «вам выплачено», «баланс скорректирован». Денежных операций не выполняет — только оповещает о фактах журнала кошелька. persist-then-send; кросс-контекстной записи нет.

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

ПолеЗначение
Сервис/контекстnotifications
Consumer groupnotifications-wallet-events
Producerwallet
Topicpin.wallet.transaction-recorded.v1 (key = AccountId — строгий порядок per-account, BalanceAfter монотонно согласован)
ContractVersionожидается 1; несовпадение — warn-and-proceed

Маппинг событий

OperationKind / DirectionNotificationKindТекст (суть)ПолучательUrgency
CommissionCredit, ReferralCreditWalletCredited«вам начислено {Amount}, баланс {BalanceAfter}»холдер (HolderUserId); для AccountKind = AgencyCommission — руководитель агентства HolderCompanyId (роль с rewards.payouts.manage)Normal
PayoutDebitWalletDebited«вам выплачено {Amount}»холдер / руководитель агентстваNormal
Adjustment, CorrectionWalletAdjusted«баланс скорректирован ({Direction})»холдер / руководитель агентстваNormal

Поля уведомления — WalletNotificationPayload (notification.md).

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

  • Actor = система; события системных счетов в топик не публикуются — каждый входящий факт адресуем холдеру.
  • Идемпотентность: consumer-level по ключу notifications-wallet:{MessageId}; business-level — dedup-unique (INV-NTF-1) + уникальный EntryId проводки.

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

Поле payloadТипОбяз.Как используется
EntryId / TransactionIdGuidДадедупликация; ссылка в WalletNotificationPayload
AccountId, AccountKindGuid / enumДаAccount { Id, Kind } payload; выбор адресата
HolderUserId / HolderCompanyIdGuid?XORполучатель fan-out (пользователь / руководитель агентства)
OperationKind, DirectionenumДавыбор NotificationKind и шаблона
Amount, BalanceAfter, CurrencyId + CurrencySnapshotdecimal / snapshotДасуммы в тексте (форматирование по валюте)
AccountVersionlongДамонотонный guard порядка per-account

Алгоритм

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

  1. Восстановить контекст события и сверить ContractVersion (несовпадение — warn-and-proceed); проверить идемпотентность по идентификатору сообщения — дубль пропускается (ack, no-op).
  2. Проверить payload (Amount > 0, EntryId/AccountId не пусты, холдер задан — XOR); при нарушении — NTF_INVALID_PAYLOAD (в DLQ).
  3. Определить получателя: HolderUserId; для AgencyCommission — руководитель агентства HolderCompanyId (резолв через identity, роль с rewards.payouts.manage); применить дедуп.
  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/EntryId; 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=wallet}.
  • Безопасность: system-actor; суммы и балансы — Sensitive-класс (наружу только холдеру/руководителю, в логах маскируются); m2m ACL Kafka.

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

  • Порядок per-account гарантирован ключом топика; «поздний» повтор события со старым AccountVersion вреда не несёт (уведомление по-событийное, дедуп по EntryId).
  • Неизвестное значение OperationKind/AccountKind/Direction → не маппится → ignored-event (ack), лог; версия ниже/выше 1 — warn-and-proceed по известным полям.

Постусловие

  • Холдеру (или руководителю агентства) по каждому разрешённому каналу создан ровно один NotificationEntity(Pending, Kind = WalletCredited | WalletDebited | WalletAdjusted); тело содержит сумму и баланс, отформатированные по валюте; offset подтверждён после фиксации.
  • Обратной записи в wallet и денежных операций не выполнялось.

Acceptance criteria

  • Given transaction-recorded с OperationKind = CommissionCredit и HolderUserId, When consumer обрабатывает, Then холдеру создаётся NotificationEntity(Pending, Kind = WalletCredited) с суммой и BalanceAfter.
  • Given событие счёта AgencyCommission, When обработка, Then уведомление адресовано руководителю агентства HolderCompanyId; при недоступности резолва — retry (backoff).
  • Given повторная доставка того же события (EntryId совпадает), When обработка, Then skipped-duplicate (ack), дублей нет.
  • Given событие с неизвестным OperationKind, When маппинг, Then ignored-event (ack), лог.

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

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

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