UC-WLT-006. Компенсирующее начисление отражается в балансе после MoneyReceived
Назначение
Описывает системный сценарий, как компенсирующее начисление — новое начисление, созданное в rewards
для исправления ошибки после того, как деньги по исходному начислению уже получены (MoneyReceived), —
доходит до кошелька и отражается в балансе получателя. После получения денег исходное начисление не
отменяется и не разворачивается (ADR-0067): недобор/пропуск лечится отдельным компенсирующим начислением,
которое проходит собственную цепочку Confirmed → MoneyReceived (свой акт поступления средств) и приходит
в кошелёк событием commission-money-received со своим Id. Для кошелька это обычный кредит: он
проводит отдельную транзакцию CommissionCredit, увеличивает балансы получателей и остаётся идемпотентным
и сверяемым — компенсация не смешивается с исходной проводкой. Связан с контекстом wallet.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-WLT-006 |
| Контекст | wallet (см. docs/06-services/wallet/) |
| Тип | альтернативный (частный случай наполнения баланса — UC-WLT-001 для компенсирующего начисления) |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | System (consumer события commission-money-received в wallet) |
| Permissions / scope | не применяется (m2m-обработка событий) |
| Tenant | вне tenant-модели — счета user-centric / company / системные (wallet-account.md) |
Предусловие
- По исходному начислению деньги уже получены (
MoneyReceived) и разнесены по счетам (UC-WLT-001) — прямая отмена/разворот исходной операции запрещены (INV-8rewards, ADR-0067). - В
rewardsобнаружена ошибка недобора/пропуска и создано компенсирующее (доначисляющее) начисление (ручное начисление следующим периодом, commission-accrual.md); оно подтверждено (Confirmed, построены три строки) и вошло в свой зарегистрированный акт поступления средств →MoneyReceived. - Опубликовано
pin.rewards.commission-money-received.v1компенсирующего начисления с построчной детализациейDistributions[]; егоIdотличается отIdисходного начисления.
Триггер
| Поле | Значение |
|---|---|
| Источник | Kafka event |
| Триггерящий элемент | pin.rewards.commission-money-received.v1 (компенсирующего начисления) → consumer rewards-commission-money-received.md |
Основной Поток
- Кошелёк получает событие получения денег по компенсирующему начислению: его
Id, агентство (TenantId),CommissionAmountи три строки распределения; проверяет вход (ровно три строки, Σ строк =CommissionAmount, единая валюта — иначе DLQ, negative-ветки consumer-дока). - Кошелёк проводит отдельную транзакцию
CommissionCreditcSourceId = Id компенсирующего начисления: дебетPlatformSettlementнаCommissionAmount, кредиты счетов получателей по каждой ненулевой строке (Agent → AgentCommission,Agency → AgencyCommission,Platform → PlatformRevenue). Так какSourceIdкомпенсации ≠SourceIdисходной операции, уникальный ключ(CommissionCredit, SourceId)не конфликтует с исходной проводкой (INV-WLT-7) — это независимый кредит, а не повтор. - Счета-получатели создаются lazy при первом кредите (если ещё нет, INV-WLT-2); балансы и
BalanceAfterпроводок фиксируются атомарно (ledger-transaction.md). Баланс получателя увеличивается на сумму его строки — так недобор доначисляется поверх ранее полученного. - По каждой проводке холдерского счёта публикуется
pin.wallet.transaction-recorded.v1;notificationsуведомляет холдеров («вам начислено {Amount}, баланс {BalanceAfter}»). - Итог: в журнале счёта — две независимые транзакции
CommissionCredit(исходная и компенсирующая) со своимиSourceId; текущий баланс = их суммарный эффект. Оператор ПИН видит обе строки в журнале и может свериться (UC-WLT-005); связь «компенсация ↔ исходная сделка» прослеживается черезrewards(обе ссылаются на одинDealId), а не по журналу кошелька.
Альтернативные Потоки
A1. Переплату кредитом не отразить (одностороннее ограничение)
- Если ошибка — перебор (получателю разнесли больше, чем следовало), компенсирующее начисление не
применимо: суммы проводок строго
> 0,Creditтолько увеличивает баланс (INV-WLT-5/INV-WLT-3). Уменьшение баланса выполняется другим путём — ручной корректировкой ПИН-админаAdjustmentсо знаком «минус» и обязательной причиной (UC-WLT-003) либо системнойCorrection, но не компенсирующим начислением. Этот сценарий покрывает только доначисление (недобор/ пропуск).
A2. Компенсирующее реферальное вознаграждение
- Если компенсируется недобор по реферальной ветке, компенсирующее реферальное начисление проходит
Pending → Approved(поMoneyReceivedродителя) и приходит в топикpin.rewards.referral-accruals.v1; кошелёк проводит независимуюReferralCredit(SourceId= id компенсирующего реферального начисления), кредитуя счётReferralCommissionпартнёра — раздельно от егоAgentCommission-баланса (UC-WLT-001 A1).
A3. Получатель компенсации — уволенный агент
- Строка
Agentкомпенсирующего начисления ссылается на иммутабельныйAgentUserId, который мог быть уволен (UC-RWD-017). Кошелёк кредитует его user-centric счётAgentCommission(счёт переживает утрату агентства, ADR-0061 п.5) — компенсация доходит до бывшего агента так же, как обычный кредит.
A4. Повторная доставка события компенсации
- То же событие компенсации доставлено повторно (at-least-once) / обрабатывается параллельно: проведение
упирается в unique
(CommissionCredit, Id компенсации)→ no-op (INV-WLT-7); баланс не меняется дважды,transaction-recordedповторно не публикуется. При этом с исходной проводкой конфликта нет и не было — у неё другойSourceId.
A5. Нулевые строки в компенсации
- Если в распределении компенсации есть нулевая строка (например, срезанный пул), она проводку не порождает
и счёт lazy не создаёт (UC-WLT-001 AC6); сверка Σ строк с
CommissionAmountсходится.
Постусловие
- По компенсирующему начислению существует ровно одна транзакция
CommissionCredit(SourceId= егоId), независимая от исходной; балансы получателей увеличены на суммы её ненулевых строк,PlatformSettlementуменьшен наCommissionAmountкомпенсации. - Исходная транзакция не изменена (журнал append-only, INV-WLT-6); текущий баланс = суммарный эффект обеих транзакций.
- Для каждого затронутого счёта
Balance = Σ Credit − Σ Debitего проводок; событияtransaction-recordedопубликованы, уведомления доставлены.
Согласованность и межсервисное взаимодействие
- Ожидает на входе. Долговечное
commission-money-receivedкомпенсирующего начисления с уникальнымId(≠ исходного) и построчнымDistributions[]. Решение «компенсировать» и создание компенсирующего начисления — целиком вrewards; кошелёк ошибку не диагностирует и не инициирует компенсацию. - Предоставляет на выходе.
transaction-recordedпо холдерским проводкам (потребительnotifications); проводки системных счетов событий не порождают. - Идемпотентность и различимость. Ключ
(OperationKind, SourceId)разводит исходную и компенсирующую операции — они никогда не схлопываются: повтор каждой — no-op в своей строке, но кредит компенсации проводится как самостоятельная транзакция. - Порядок и восстановление. Событие компенсации доставляется at-least-once; проведение атомарно
(частичных проводок нет). Компенсация приходит после исходного кредита по бизнес-процессу (свой более
поздний акт), но кошелёк не полагается на сквозной порядок — корректность обеспечивают независимые
SourceIdи guard-ы проведения. - Одностороннее ограничение (важно). Кошелёк кредитует, но компенсирующее начисление не может
«списать»: перебор лечится
Adjustment/Correction(A1), а не начислением. Это следствие «кредит — всегда> 0» (INV-WLT-5) и «журнал не правится» (INV-WLT-6).
Возможные Ошибки
| Код | HTTP | Условие | Поведение (consumer) |
|---|---|---|---|
| — (идемпотентный no-op) | — | Повтор события компенсации (A4, INV-WLT-7) | Skip, событие не переиздаётся |
WALLET_DISTRIBUTION_SUM_MISMATCH | — (DLQ + алерт) | Σ строк Distributions[] ≠ CommissionAmount компенсации | В DLQ, проводить нечего (нарушен контракт producer-а) |
WALLET_CURRENCY_MISMATCH | — (DLQ) | Валюта строк вне допустимой платформой / смешана (INV-WLT-4, RUB-only) | В DLQ, балансы не изменены |
WALLET_UNKNOWN_ENUM | — (DLQ) | Не три строки / неизвестный RecipientType / нет RecipientUserId у Agent-строки | В DLQ |
WALLET_BALANCE_NEGATIVE_FORBIDDEN | — (DLQ + алерт) | Дебет PlatformSettlement без покрытия / рассинхрон | DLQ + issue Reconciliation Center (ADR-0021) |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
WalletTransactionRecordedKafkaEvent | pin.wallet.transaction-recorded.v1 | Атомарно с проведением CommissionCredit компенсации — по холдерским проводкам | notifications |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Given компенсирующее начисление с Id ≠ исходного дошло до MoneyReceived, When кошелёк обработал событие, Then проведена отдельная CommissionCredit-транзакция и балансы получателей выросли на суммы её строк | интеграционный тест |
| AC2 | Given исходная и компенсирующая операции, When обе проведены, Then у них разные SourceId, конфликта идемпотентности нет, исходная проводка не изменена | тест различимости (INV-WLT-6/7) |
| AC3 | Given повтор события компенсации, When доставлено дважды, Then no-op по (CommissionCredit, Id компенсации), баланс не меняется дважды | тест двойной доставки (A4) |
| AC4 | Given получатель компенсации — уволенный агент, When кошелёк проводит кредит, Then кредитован его user-centric счёт (A3) | тест user-centric счёта |
| AC5 | Given перебор (нужно уменьшить баланс), When пытаются «компенсировать начислением», Then это невозможно — уменьшение только Adjustment/Correction (A1) | тест ограничения одностороннего кредита |
| AC6 | Given любой затронутый счёт, When сценарий завершён, Then Balance = пересчёту из журнала (Σ Credit − Σ Debit) | сверочный пересчёт (ADR-0021) |
| AC7 | Given нарушенный контракт события (Σ строк ≠ CommissionAmount), When обработка, Then DLQ + алерт, балансы не изменены | негативный тест |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- Наполнение баланса (базовый сценарий): UC-WLT-001; ручная корректировка (путь уменьшения): UC-WLT-003; журнал и сверка: UC-WLT-005.
- Domain entity: ledger-transaction.md (виды операций, идемпотентность
(OperationKind, SourceId), INV-WLT-5..7), wallet-account.md (счета, lazy-создание, user-centric). - Consumer: rewards-commission-money-received.md.
- Источник компенсации: ../../rewards/domain/commission-accrual.md
(гейт
MoneyReceived, запрет отмены после денег), уволенный получатель — ../../rewards/use-cases/UC-RWD-017-terminated-agent-earned-commissions.md. - ADR-0067 (компенсации вместо разворота), ADR-0065 (момент кредита), ADR-0021 (сверка), ADR-0061 п.5 (user-centric счета).