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

UC-WLT-006. Компенсирующее начисление отражается в балансе после MoneyReceived

Назначение

Описывает системный сценарий, как компенсирующее начисление — новое начисление, созданное в rewards для исправления ошибки после того, как деньги по исходному начислению уже получены (MoneyReceived), — доходит до кошелька и отражается в балансе получателя. После получения денег исходное начисление не отменяется и не разворачивается (ADR-0067): недобор/пропуск лечится отдельным компенсирующим начислением, которое проходит собственную цепочку Confirmed → MoneyReceived (свой акт поступления средств) и приходит в кошелёк событием commission-money-received со своим Id. Для кошелька это обычный кредит: он проводит отдельную транзакцию CommissionCredit, увеличивает балансы получателей и остаётся идемпотентным и сверяемым — компенсация не смешивается с исходной проводкой. Связан с контекстом wallet.

Metadata

ПолеЗначение
IDUC-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-8 rewards, 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

Основной Поток

  1. Кошелёк получает событие получения денег по компенсирующему начислению: его Id, агентство (TenantId), CommissionAmount и три строки распределения; проверяет вход (ровно три строки, Σ строк = CommissionAmount, единая валюта — иначе DLQ, negative-ветки consumer-дока).
  2. Кошелёк проводит отдельную транзакцию CommissionCredit c SourceId = Id компенсирующего начисления: дебет PlatformSettlement на CommissionAmount, кредиты счетов получателей по каждой ненулевой строке (Agent → AgentCommission, Agency → AgencyCommission, Platform → PlatformRevenue). Так как SourceId компенсации ≠ SourceId исходной операции, уникальный ключ (CommissionCredit, SourceId) не конфликтует с исходной проводкой (INV-WLT-7) — это независимый кредит, а не повтор.
  3. Счета-получатели создаются lazy при первом кредите (если ещё нет, INV-WLT-2); балансы и BalanceAfter проводок фиксируются атомарно (ledger-transaction.md). Баланс получателя увеличивается на сумму его строки — так недобор доначисляется поверх ранее полученного.
  4. По каждой проводке холдерского счёта публикуется pin.wallet.transaction-recorded.v1; notifications уведомляет холдеров («вам начислено {Amount}, баланс {BalanceAfter}»).
  5. Итог: в журнале счёта — две независимые транзакции CommissionCredit (исходная и компенсирующая) со своими SourceId; текущий баланс = их суммарный эффект. Оператор ПИН видит обе строки в журнале и может свериться (UC-WLT-005); связь «компенсация ↔ исходная сделка» прослеживается через rewards (обе ссылаются на один DealId), а не по журналу кошелька.

Альтернативные Потоки

A1. Переплату кредитом не отразить (одностороннее ограничение)

  1. Если ошибка — перебор (получателю разнесли больше, чем следовало), компенсирующее начисление не применимо: суммы проводок строго > 0, Credit только увеличивает баланс (INV-WLT-5/INV-WLT-3). Уменьшение баланса выполняется другим путём — ручной корректировкой ПИН-админа Adjustment со знаком «минус» и обязательной причиной (UC-WLT-003) либо системной Correction, но не компенсирующим начислением. Этот сценарий покрывает только доначисление (недобор/ пропуск).

A2. Компенсирующее реферальное вознаграждение

  1. Если компенсируется недобор по реферальной ветке, компенсирующее реферальное начисление проходит Pending → Approved (по MoneyReceived родителя) и приходит в топик pin.rewards.referral-accruals.v1; кошелёк проводит независимую ReferralCredit (SourceId = id компенсирующего реферального начисления), кредитуя счёт ReferralCommission партнёра — раздельно от его AgentCommission-баланса (UC-WLT-001 A1).

A3. Получатель компенсации — уволенный агент

  1. Строка Agent компенсирующего начисления ссылается на иммутабельный AgentUserId, который мог быть уволен (UC-RWD-017). Кошелёк кредитует его user-centric счёт AgentCommission (счёт переживает утрату агентства, ADR-0061 п.5) — компенсация доходит до бывшего агента так же, как обычный кредит.

A4. Повторная доставка события компенсации

  1. То же событие компенсации доставлено повторно (at-least-once) / обрабатывается параллельно: проведение упирается в unique (CommissionCredit, Id компенсации) → no-op (INV-WLT-7); баланс не меняется дважды, transaction-recorded повторно не публикуется. При этом с исходной проводкой конфликта нет и не было — у неё другой SourceId.

A5. Нулевые строки в компенсации

  1. Если в распределении компенсации есть нулевая строка (например, срезанный пул), она проводку не порождает и счёт 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
WalletTransactionRecordedKafkaEventpin.wallet.transaction-recorded.v1Атомарно с проведением CommissionCredit компенсации — по холдерским проводкамnotifications

Acceptance Criteria

#КритерийПроверка
AC1Given компенсирующее начисление с Id ≠ исходного дошло до MoneyReceived, When кошелёк обработал событие, Then проведена отдельная CommissionCredit-транзакция и балансы получателей выросли на суммы её строкинтеграционный тест
AC2Given исходная и компенсирующая операции, When обе проведены, Then у них разные SourceId, конфликта идемпотентности нет, исходная проводка не измененатест различимости (INV-WLT-6/7)
AC3Given повтор события компенсации, When доставлено дважды, Then no-op по (CommissionCredit, Id компенсации), баланс не меняется дваждытест двойной доставки (A4)
AC4Given получатель компенсации — уволенный агент, When кошелёк проводит кредит, Then кредитован его user-centric счёт (A3)тест user-centric счёта
AC5Given перебор (нужно уменьшить баланс), When пытаются «компенсировать начислением», Then это невозможно — уменьшение только Adjustment/Correction (A1)тест ограничения одностороннего кредита
AC6Given любой затронутый счёт, When сценарий завершён, Then Balance = пересчёту из журнала (Σ Credit − Σ Debit)сверочный пересчёт (ADR-0021)
AC7Given нарушенный контракт события (Σ строк ≠ CommissionAmount), When обработка, Then DLQ + алерт, балансы не измененынегативный тест

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

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

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