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

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)
Topicpin.rewards.commission-money-received.v1 (key = id начисления)
ContractVersion1; при несовпадении версии — warn-and-proceed
Consumer grouppin-wallet-rewards-money-received
Producerrewards: регистрация акта поступления средств (../../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ТипОбязательностьКак используется (маппинг)
IdGuidДаId начисления → SourceId транзакции (идемпотентность); = Kafka key
TenantIdGuidДаАгентство начисления → холдер счёта AgencyCommission (HolderCompanyId)
PlatformReceiptIdGuidДаТрассировка (лог проведения)
MoneyReceivedAtDateTimeOffsetДаOccurredAt транзакции (момент бизнес-факта)
CommissionAmountMoney (Value + CurrencyId + CurrencySnapshot)ДаСумма дебета PlatformSettlement; сверка Σ строк
Distributions[]список строкДа (ровно 3)По одной строке каждого RecipientType (Platform/Agency/Agent) — кредиты счетов
Distributions[].RecipientTypestring (enum CommissionRecipientType)ДаВыбор счёта-получателя (таблица проводок ниже)
Distributions[].RecipientUserIdGuid?Да для AgentХолдер счёта AgentCommission
Distributions[].AmountMoneyДаСумма кредита строки

Маппинг «строка распределения → проводка»

Одна транзакция CommissionCredit на событие; внутри — дебет и кредиты двойной записью (Σ дебетов = Σ кредитов, ../domain/ledger-transaction.md):

Строка payloadДебетКредитСумма
— (итог)PlatformSettlement (системный)CommissionAmount
RecipientType = AgentAgentCommission холдера RecipientUserIdAmount строки
RecipientType = AgencyAgencyCommission холдера TenantId (компания-агентство)Amount строки
RecipientType = PlatformPlatformRevenue (системный)Amount строки

Строка с Amount.Value = 0 (нулевая доля агента при срезанном пуле; Platform при fallback 0 п.п. — ../../rewards/domain/commission-accrual.md, INV-5/INV-10) проводку не порождает и счёт lazy не создаёт; сверка сумм при этом обязана сходиться.

Алгоритм

  1. Контекст события. Дедупликация по идентификатору сообщения; ContractVersion — warn-and-proceed. Проверить бизнес-идемпотентность: транзакция (CommissionCredit, Id начисления) уже проведена → пропустить как дубль (ack, no-op).
  2. Проверка входа. 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).
  3. Резолв счетов (lazy). Для каждой ненулевой строки получить-или-создать счёт: AgentCommission (HolderUserId = RecipientUserId), AgencyCommission (HolderCompanyId = TenantId), системные PlatformSettlement/PlatformRevenue — единственные экземпляры. Гонка lazy-создания разрешается уникальностью «холдер + вид» (INV-WLT-2, ../domain/wallet-account.md): проигравший повторяет с уже созданным счётом.
  4. Проведение. Одна транзакция CommissionCredit (SourceId = Id начисления, OccurredAt = MoneyReceivedAt) с проводками по таблице маппинга; пересчёт балансов затронутых счетов.
  5. Какие факты фиксируются. Публикуется pin.wallet.transaction-recorded.v1 (../events/wallet-transaction-recorded.md, key = AccountId) — по одному событию на каждую проводку холдерского счёта (Agent- и Agency-кредиты; проводки системных счетов не публикуются); потребитель notifications («вам начислено»).
  6. Согласованность. Транзакция, проводки, балансы и исходящие события — в одном согласованном изменении; offset подтверждается после фиксации.
  7. Результат. Успех → 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

СбойRetryDLQРучное действие
Транзиентная ошибка хранилищаэкспоненциальный 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-класс.

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

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

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