Consumer: Rewards Commission Distribution Adjusted
Назначение
Consumer слушает событие корректировки распределения комиссионного начисления
(pin.rewards.commission-distribution-adjusted.v1 — публикуется, когда руководитель агентства меняет доли
Agency/Agent по начислению, уже прошедшему гейт получения денег,
ADR-0067) и выравнивает балансы
получателей под новое деление пула: проводит в журнале кошелька транзакцию Adjustment, переносящую
дельту между холдерскими счетами AgentCommission и AgencyCommission. Первичный кредит по этому
начислению уже проведён consumer-ом получения денег
(rewards-commission-money-received.md) по старому распределению;
данный consumer лишь довносит разницу. Строка Platform и суммарный баланс получателей не меняются —
дельта переносится с одного холдерского счёта на другой.
Owning-сервис и триггер
| Поле | Значение |
|---|---|
| Сервис/контекст | wallet |
| Обрабатываемое событие | pin.rewards.commission-distribution-adjusted (CommissionDistributionAdjustedKafkaEvent) |
| Topic | pin.rewards.commission-distribution-adjusted.v1 (key = AccrualId — строгий порядок корректировок per-начисление) |
| ContractVersion | 1; при несовпадении версии — warn-and-proceed |
| Consumer group | pin-wallet-rewards-distribution-adjusted |
| Producer | rewards: per-deal корректировка распределения в статусе MoneyReceived (../../rewards/api/agency/adjust-accrual-distribution.md) — по одному событию на каждую корректировку |
| Эффект (событие → что меняется) | одна транзакция Adjustment в журнале (../domain/ledger-transaction.md): перенос дельты между счетами AgentCommission и AgencyCommission; балансы обоих счетов |
Контекст события
- Из метаданных доступны: идентификатор сообщения (дедупликация),
AggregateId=AccrualId, время события; корреляция трейса — из метаданных. - Обработка — под системным актором вне tenant-контекста: счета кошелька платформенные, холдер-based;
причина корректировки (
Reason) фиксируется вCommentтранзакции как аудит-след. - Ключ бизнес-идемпотентности: транзакция
(OperationKind = Adjustment, SourceId), гдеSourceIdдетерминированно выводится из пары(AccrualId, Version)события — каждая корректировка (со своимVersion) порождает не более однойAdjustment-транзакции (INV-WLT-7).
Входные данные / payload события
Контракт — ../../rewards/events/commission-distribution-adjusted.md. Ниже — поля, которые consumer читает.
| Поле payload | Тип | Обязательность | Как используется (маппинг) |
|---|---|---|---|
AccrualId | Guid | Да | Начисление → часть SourceId транзакции (с Version); = Kafka key |
TenantId | Guid | Да | Агентство → холдер счёта AgencyCommission (HolderCompanyId) |
AgentUserId | Guid | Да | Агент → холдер счёта AgentCommission (HolderUserId) |
OldAgencyAmount / NewAgencyAmount | Money (Value + CurrencyId + CurrencySnapshot) | Да | Дельта агентства NewAgencyAmount − OldAgencyAmount |
OldAgentAmount / NewAgentAmount | Money | Да | Дельта агента NewAgentAmount − OldAgentAmount (= −дельта агентства) |
Reason | string | Да | Причина корректировки → Comment транзакции (аудит) |
Version | long | Да | Доменная версия начисления → часть SourceId (идемпотентность), монотонный guard |
Маппинг «дельта → проводка»
Одна транзакция Adjustment на событие; внутри — две проводки двойной записью между холдерскими счетами
(Σ дебетов = Σ кредитов, ../domain/ledger-transaction.md, INV-WLT-5).
Пусть Δ = NewAgentAmount.Value − OldAgentAmount.Value (дельта агента; дельта агентства = −Δ):
Знак Δ | Смысл | Дебет | Кредит | Сумма |
|---|---|---|---|---|
Δ > 0 | доля агента выросла, агентства — уменьшилась | AgencyCommission холдера TenantId | AgentCommission холдера AgentUserId | ` |
Δ < 0 | доля агента уменьшилась, агентства — выросла | AgentCommission холдера AgentUserId | AgencyCommission холдера TenantId | ` |
Δ = 0 | доли не изменились (суммы совпали) | — | — | No-op (ack): переносить нечего |
PlatformSettlement, PlatformRevenue и строка Platform в переносе не участвуют — дельта остаётся внутри
пула агентства.
Алгоритм
- Контекст события. Дедупликация по идентификатору сообщения;
ContractVersion— warn-and-proceed. Проверить бизнес-идемпотентность: транзакция(Adjustment, SourceId из AccrualId + Version)уже проведена → пропустить как дубль (ack, no-op, INV-WLT-7). - Проверка входа. Единый
CurrencyIdвсех сумм, допустимый платформой (RUB-only, ADR-0048) — иначе DLQ (WALLET_CURRENCY_MISMATCH, INV-WLT-4). Сохранение пула (следствие INV-11 producer-а):(NewAgencyAmount − OldAgencyAmount) + (NewAgentAmount − OldAgentAmount) = 0— иначе DLQ + алерт (WALLET_DISTRIBUTION_SUM_MISMATCH: контракт producer-а нарушен, перенос увёл бы Σ балансов). ДельтаΔ = 0→ No-op (ack). - Резолв счетов. Счета
AgentCommission(HolderUserId = AgentUserId) иAgencyCommission(HolderCompanyId = TenantId) уже существуют (кредитованы при получении денег); при их отсутствии — рассинхрон (WALLET_ACCOUNT_NOT_FOUND/ детектор покрытия) → DLQ + алерт. - Проведение. Одна транзакция
Adjustment(SourceIdизAccrualId + Version,Comment = Reason,ActorUserId = null— системная авто-компенсация) с двумя проводками по таблице маппинга; пересчёт балансов обоих счетов. Дебетуемый счёт не может уйти в минус (INV-WLT-3): если у дебетуемой стороны баланс меньше|Δ|(доля уже израсходована выплатой) — не проводить, DLQ + алерт (WALLET_BALANCE_NEGATIVE_FORBIDDEN, детектор рассинхрона, а не бизнес-отказ — ADR-0021). - Какие факты фиксируются. По каждой проводке холдерского счёта публикуется
pin.wallet.transaction-recorded.v1(../events/wallet-transaction-recorded.md, key =AccountId) — потребительnotifications(«ваша доля скорректирована»). - Согласованность. Транзакция, обе проводки, балансы и исходящие события — в одном согласованном изменении; offset подтверждается после фиксации.
- Результат. Успех → ack; контролируемая ошибка → retry/DLQ по политике ниже.
Диаграмма
Идемпотентность и replay
- Business key: unique
(OperationKind = Adjustment, SourceId из AccrualId + Version); дубль — пропуск (no-op, INV-WLT-7). Каждая корректировка со своимVersion— отдельная транзакция. - Порядок: key топика =
AccrualId— последовательные корректировки приходят по возрастаниюVersion; устаревший повтор упирается в идемпотентность. - Version-guard: несовпадение
ContractVersion— warn-and-proceed; неизвестные additive-поля игнорируются. - Replay: да, безопасен — уже проведённые переносы пропускаются по unique-ключу; полный replay топика восстанавливает переносы без задвоения.
Retry и DLQ
| Сбой | Retry | DLQ | Ручное действие |
|---|---|---|---|
| Транзиентная ошибка хранилища | экспоненциальный retry | после исчерпания — да | replay после восстановления |
Валюта вне допустимых (WALLET_CURRENCY_MISMATCH, INV-WLT-4) | нет | Да | разбор контракта (ADR-0048) |
Пул не сохранён — Σ дельт ≠ 0 (WALLET_DISTRIBUTION_SUM_MISMATCH) | нет | Да + алерт | инцидент producer-а: сверка с начислением в rewards (ADR-0021) |
| Баланс дебетуемой стороны < ` | Δ | (WALLET_BALANCE_NEGATIVE_FORBIDDEN`, INV-WLT-3) | нет |
Счёт получателя не найден (WALLET_ACCOUNT_NOT_FOUND) | нет | Да + алерт | рассинхрон: кредит по получению денег не проведён — сверка с consumer-ом получения денег |
Наблюдаемость и безопасность
- Logs: consumed / skipped-duplicate / posted / failed —
AccrualId,Version, id транзакции, без сумм и без текстаReason(Sensitive). Metrics:wallet_transactions_recorded_total{kind="Adjustment"}, дубликаты, DLQ, lag (now − время события). - Traces: span приёма → span проведения; корреляция из метаданных события.
- PII нет; суммы и балансы — Sensitive-класс.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): POST /api/v1/agency/commission-accruals/{accrualId}/adjust-distribution — Per-deal корректировка долей внутри пула, POST /api/v1/agency/commission-adjustment-requests/{requestId}/resolve — Решение по заявке агента на корректировку доли
- События (1): Event Contract: pin.rewards.commission-distribution-adjusted (CommissionDistributionAdjustedKafkaEvent)
- index.md (1): Wallet Service
Связанные документы
- Контракт входного события: ../../rewards/events/commission-distribution-adjusted.md; операция-источник: ../../rewards/api/agency/adjust-accrual-distribution.md; строки распределения: ../../rewards/domain/commission-accrual.md (INV-11).
- Первичный кредит по начислению: rewards-commission-money-received.md.
- Целевые сущности: ../domain/wallet-account.md,
../domain/ledger-transaction.md (операция
Adjustment, INV-WLT-3/5/7); исходящее событие: ../events/wallet-transaction-recorded.md. - Контур: ../../rewards/sagas/commission-settlement-saga.md.
- ADR-0065 (карта операций), ADR-0067 (гейт денег, корректировки), ADR-0021 (рассинхроны).