Consumer: Rewards Commission Money Received
Назначение
Consumer слушает событие получения денег по комиссионному начислению
(pin.rewards.commission-money-received.v1 — публикуется при регистрации акта поступления средств,
гейт ADR-0067) и проводит в журнале
кошелька транзакцию CommissionCredit: полученная от застройщика сумма распределяется по счетам
получателей строк распределения — агента, агентства и дохода ПИН. Это единственный путь пополнения
счетов AgentCommission/AgencyCommission/PlatformRevenue: до подтверждения получения денег суммы
существуют только как статусы начислений в rewards и в балансах не отражаются (ADR-0065 п.3).
Owning-сервис и триггер
| Поле | Значение |
|---|---|
| Сервис/контекст | wallet |
| Обрабатываемое событие | pin.rewards.commission-money-received (CommissionMoneyReceivedKafkaEvent) |
| Topic | pin.rewards.commission-money-received.v1 (key = id начисления) |
| ContractVersion | 1; при несовпадении версии — warn-and-proceed |
| Consumer group | pin-wallet-rewards-money-received |
| Producer | rewards: регистрация акта поступления средств (../../rewards/domain/platform-receipt.md) — по одному событию на каждое начисление акта |
| Эффект (событие → что меняется) | одна транзакция CommissionCredit в журнале (../domain/ledger-transaction.md): дебет PlatformSettlement, кредиты счетов получателей; балансы счетов |
Контекст события
- Из метаданных доступны: идентификатор сообщения (дедупликация), агрегат-источник (
AggregateId= id начисления), время события; корреляция трейса восстанавливается из метаданных. - Обработка — под системным актором вне tenant-контекста: счета кошелька — платформенные, холдер-based.
- Ключ бизнес-идемпотентности: транзакция
(OperationKind = CommissionCredit, SourceId = Id начисления)— unique в журнале.
Входные данные / payload события
Контракт — ../../rewards/events/commission-money-received.md. Ниже — поля, которые consumer читает.
| Поле payload | Тип | Обязательность | Как используется (маппинг) |
|---|---|---|---|
Id | Guid | Да | Id начисления → SourceId транзакции (идемпотентность); = Kafka key |
TenantId | Guid | Да | Агентство начисления → холдер счёта AgencyCommission (HolderCompanyId) |
PlatformReceiptId | Guid | Да | Трассировка (лог проведения) |
MoneyReceivedAt | DateTimeOffset | Да | → OccurredAt транзакции (момент бизнес-факта) |
CommissionAmount | Money (Value + CurrencyId + CurrencySnapshot) | Да | Сумма дебета PlatformSettlement; сверка Σ строк |
Distributions[] | список строк | Да (ровно 3) | По одной строке каждого RecipientType (Platform/Agency/Agent) — кредиты счетов |
Distributions[].RecipientType | string (enum CommissionRecipientType) | Да | Выбор счёта-получателя (таблица проводок ниже) |
Distributions[].RecipientUserId | Guid? | Да для Agent | Холдер счёта AgentCommission |
Distributions[].Amount | Money | Да | Сумма кредита строки |
Маппинг «строка распределения → проводка»
Одна транзакция CommissionCredit на событие; внутри — дебет и кредиты двойной записью
(Σ дебетов = Σ кредитов, ../domain/ledger-transaction.md):
| Строка payload | Дебет | Кредит | Сумма |
|---|---|---|---|
| — (итог) | PlatformSettlement (системный) | — | CommissionAmount |
RecipientType = Agent | — | AgentCommission холдера RecipientUserId | Amount строки |
RecipientType = Agency | — | AgencyCommission холдера TenantId (компания-агентство) | Amount строки |
RecipientType = Platform | — | PlatformRevenue (системный) | Amount строки |
Строка с Amount.Value = 0 (нулевая доля агента при срезанном пуле; Platform при fallback 0 п.п. —
../../rewards/domain/commission-accrual.md, INV-5/INV-10)
проводку не порождает и счёт lazy не создаёт; сверка сумм при этом обязана сходиться.
Алгоритм
- Контекст события. Дедупликация по идентификатору сообщения;
ContractVersion— warn-and-proceed. Проверить бизнес-идемпотентность: транзакция(CommissionCredit, Id начисления)уже проведена → пропустить как дубль (ack, no-op). - Проверка входа.
Distributions— ровно три строки, по одной каждогоRecipientType; у строкиAgentзаполненRecipientUserId; неизвестныйRecipientType/ нарушение состава → non-retriable, DLQ (WALLET_UNKNOWN_ENUM— кредитуемый счёт не определить). ΣDistributions[].Amount.Value=CommissionAmount.Value— иначе DLQ + алерт (WALLET_DISTRIBUTION_SUM_MISMATCH: контракт producer-а нарушен, проводить нечего; внутренний guard баланса проводок — INV-WLT-5). ЕдиныйCurrencyIdвсех сумм, допустимый платформой (RUB-only, ADR-0048) — иначе DLQ (WALLET_CURRENCY_MISMATCH, INV-WLT-4). - Резолв счетов (lazy). Для каждой ненулевой строки получить-или-создать счёт:
AgentCommission(HolderUserId = RecipientUserId),AgencyCommission(HolderCompanyId = TenantId), системныеPlatformSettlement/PlatformRevenue— единственные экземпляры. Гонка lazy-создания разрешается уникальностью «холдер + вид» (INV-WLT-2, ../domain/wallet-account.md): проигравший повторяет с уже созданным счётом. - Проведение. Одна транзакция
CommissionCredit(SourceId = Idначисления,OccurredAt = MoneyReceivedAt) с проводками по таблице маппинга; пересчёт балансов затронутых счетов. - Какие факты фиксируются. Публикуется
pin.wallet.transaction-recorded.v1(../events/wallet-transaction-recorded.md, key =AccountId) — по одному событию на каждую проводку холдерского счёта (Agent- иAgency-кредиты; проводки системных счетов не публикуются); потребительnotifications(«вам начислено»). - Согласованность. Транзакция, проводки, балансы и исходящие события — в одном согласованном изменении; offset подтверждается после фиксации.
- Результат. Успех → ack; контролируемая ошибка → retry/DLQ по политике ниже.
Диаграмма
Идемпотентность и replay
- Business key: unique
(OperationKind = CommissionCredit, SourceId = Id начисления); дубль — пропуск (no-op). - Version-guard: несовпадение
ContractVersion— warn-and-proceed; неизвестные additive-поля payload игнорируются. - Replay: да, безопасен — уже проведённые события пропускаются по unique-ключу; полный replay топика восстанавливает журнал кредитов без задвоения.
Retry и DLQ
| Сбой | Retry | DLQ | Ручное действие |
|---|---|---|---|
| Транзиентная ошибка хранилища | экспоненциальный retry | после исчерпания — да | replay после восстановления |
Неизвестный RecipientType / не три строки / нет RecipientUserId у Agent (WALLET_UNKNOWN_ENUM) | нет | Да | разбор контракта с rewards, ручной replay после фикса |
Σ строк ≠ CommissionAmount (WALLET_DISTRIBUTION_SUM_MISMATCH) | нет | Да + алерт | инцидент producer-а: сверка с начислением в rewards, компенсация — на стороне rewards (ADR-0021) |
Валюта вне допустимых (WALLET_CURRENCY_MISMATCH, INV-WLT-4) | нет | Да | разбор (ADR-0048) |
Наблюдаемость и безопасность
- Logs: consumed / skipped-duplicate / posted / failed —
Idначисления,PlatformReceiptId, id транзакции, без сумм (Sensitive). Metrics:wallet_transactions_recorded_total{kind="CommissionCredit"}, дубликаты, DLQ, lag (now −MoneyReceivedAt). - Traces: span приёма → span проведения; корреляция из метаданных события.
- PII нет; суммы — Sensitive-класс.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: LedgerTransactionEntity (+ LedgerEntryEntity)
- API (3): GET /api/v1/agency/wallet/agent-balances — Балансы агентов агентства, GET /api/v1/my/wallet — Мой кошелёк (балансы счетов), Wallet API — реестр операций и общие контрактные модели
- Use Cases (3): UC-RWD-010. Акт поступления средств — гейт получения денег, UC-WLT-001. Получение денег кредитует балансы получателей, UC-WLT-006. Компенсирующее начисление отражается в балансе после MoneyReceived
- События (2): Event Contract: pin.rewards.commission-money-received (CommissionMoneyReceivedKafkaEvent), Event Contract: pin.wallet.transaction-recorded (WalletTransactionRecordedKafkaEvent)
- Консюмеры (2): Consumer: Rewards Commission Distribution Adjusted, Consumer: Rewards Payout Order Status Changed
- index.md (1): Wallet Service
Связанные документы
- Контракт входного события: ../../rewards/events/commission-money-received.md; источник каскада: ../../rewards/domain/platform-receipt.md, строки распределения: ../../rewards/domain/commission-accrual.md.
- Целевые сущности: ../domain/wallet-account.md, ../domain/ledger-transaction.md; исходящее событие: ../events/wallet-transaction-recorded.md.
- ADR-0065 (карта операций), ADR-0067 (гейт денег), ADR-0021 (рассинхроны).