UC-WLT-002. Выплата поручения дебетует баланс получателя
Назначение
Описывает системный сценарий: платёжное поручение отмечено выплаченным в rewards
(payout-order.md) — и кошелёк списывает выплаченные суммы с
балансов получателей: доли агентов — со счетов AgentCommission, реферальные вознаграждения — со счетов
ReferralCommission. Баланс холдера показывает только невыплаченный остаток обязательств. Связан с
контекстом wallet.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-WLT-002 |
| Контекст | wallet (см. docs/06-services/wallet/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | System (consumer событий rewards) |
| Permissions / scope | не применяется (m2m-обработка событий) |
| Tenant | вне tenant-модели (wallet-account.md) |
Предусловие
- Поручение прошло
PendingPayment → Paid(ручная отметка после фактической выплаты вне платформы, решение №9 ТЗ); каскадrewardsвыполнен (позиции, строки-источники, начисления — payout-order.md, INV-7). - Балансы получателей ранее кредитованы по гейту получения денег (UC-WLT-001) — кредиты предшествуют дебетам по бизнес-процессу.
- Событие
pin.rewards.payout-order-status-changed.v1сToStatus = Paidнесёт позицииItems[](id позиции, получатель, сумма) и вид поручения (payout-order-status-changed.md).
Триггер
| Поле | Значение |
|---|---|
| Источник | Kafka event |
| Триггерящий элемент | pin.rewards.payout-order-status-changed.v1 (ToStatus = Paid) → consumer rewards-payout-order-status-changed.md |
Основной Поток
- Кошелёк получает событие выплаты поручения: вид поручения, позиции
Items[]с получателями и суммами. - Для каждой позиции определяется холдерский счёт:
AgencyCommission-поручение → счётAgentCommissionагента-получателя;ReferralReward-поручение → счётReferralCommissionпартнёра. - По каждой позиции проводится транзакция
PayoutDebit(SourceId= id позиции): дебет холдерского счёта, кредитPlatformSettlementна сумму позиции (ledger-transaction.md). - Guard no-negative: баланс холдерского счёта после дебета
>= 0(wallet-account.md, INV-WLT-3) — при выполнении транзакция и балансы фиксируются атомарно. - По каждой проводке холдерского счёта публикуется
pin.wallet.transaction-recorded.v1;notificationsдоставляет получателю «вам выплачено {Amount}». - Холдер видит уменьшенный баланс и операцию списания в истории (UC-WLT-004).
Альтернативные Потоки
A1. Дебет без покрытия (рассинхрон rewards ↔ wallet)
- Дебет позиции увёл бы баланс холдерского счёта ниже нуля (потерян/задержан кредит, счёт не существует).
- Это не бизнес-отказ, а детектор рассинхрона: транзакция не проводится, сообщение уходит в
DLQ + алерт (
WALLET_BALANCE_NEGATIVE_FORBIDDEN), создаётся issue Reconciliation Center (ADR-0021). - Разбор: недостающий кредит доводится командой владельца данных (
rewards) либо компенсирующей проводкой — журнал не правится. - Сообщение реплеится из DLQ: уже проведённые позиции — no-op, недостающие — проводятся.
A2. Повторная доставка события
- Событие
Paidдоставлено повторно (at-least-once) или обрабатывается параллельно вторым инстансом. - Каждая позиция упирается в unique
(OperationKind, SourceId)— no-op (INV-WLT-7): балансы не меняются, события повторно не публикуются.
A3. Частично обработанное событие
- Обработка прервалась после части позиций (сбой/рестарт) либо часть позиций ушла в DLQ (A1).
- Повторная доставка/replay доводит оставшиеся позиции; идемпотентность гарантирует эквивалентность однократной полной обработке.
Постусловие
- По каждой позиции поручения существует ровно одна транзакция
PayoutDebit«дебет холдерского счёта / кредитPlatformSettlement». - Балансы получателей уменьшены на суммы своих позиций и остались
>= 0; балансPlatformSettlementувеличен (погашение обязательств). - Для каждого затронутого счёта:
Balance= Σ кредитов − Σ дебетов его проводок. - События
pin.wallet.transaction-recorded.v1опубликованы; уведомления о выплате доставлены.
Возможные Ошибки
| Код | HTTP | Условие | Поведение |
|---|---|---|---|
WALLET_BALANCE_NEGATIVE_FORBIDDEN | — (consumer) | Счёт получателя отсутствует / дебет позиции не покрыт балансом (INV-WLT-3) | Транзакция не проводится; DLQ + алерт + issue Reconciliation Center; replay после исправления |
WALLET_UNKNOWN_ENUM | — (consumer) | Неизвестный Kind поручения / пустые Items[] при Paid — счёт списания не определить | Non-retriable → DLQ, разбор контракта с rewards |
WALLET_CURRENCY_MISMATCH | — (consumer) | Валюта позиции ≠ валюте счёта / вне допустимых (INV-WLT-4) | Non-retriable → DLQ + алерт (дефект контракта данных) |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
WalletTransactionRecordedKafkaEvent | pin.wallet.transaction-recorded.v1 | Атомарно с проведением каждой PayoutDebit-транзакции | notifications |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Given поручение с N позициями перешло в Paid, When кошелёк обработал событие, Then проведено N PayoutDebit-транзакций и балансы получателей уменьшены ровно на суммы позиций | интеграционный тест consumer-а |
| AC2 | Given реферальное поручение (ReferralReward), When обработано событие Paid, Then списание идёт со счетов ReferralCommission партнёров, не затрагивая их AgentCommission-балансы | тест раздельности балансов |
| AC3 | Given уже обработанное событие Paid, When оно доставлено повторно, Then балансы не изменились (no-op по (OperationKind, SourceId)) | тест двойной доставки (INV-WLT-7) |
| AC4 | Given позиция, не покрытая балансом, When проводится дебет, Then транзакция не проведена, сообщение в DLQ, алерт поднят, покрытые позиции того же события проведены | негативный тест A1 |
| AC5 | Given исправленный рассинхрон, When сообщение реплеится из DLQ, Then доведены только недостающие позиции (проведённые — no-op) и балансы сходятся с журналом | тест replay |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (1): UC-WLT-004. Холдер смотрит баланс и историю операций
- scenarios (1): UC-OVR-010. От завершённой сделки до выплаты комиссии и реферального вознаграждения
- registries (1): Реестр use cases и user stories PIN
- index.md (1): Wallet Service
Связанные Документы
- Domain entity: wallet-account.md, ledger-transaction.md.
- Consumer: rewards-payout-order-status-changed.md.
- Событие-источник:
payout-order-status-changed.md;
поручение — payout-order.md;
каскад
Paidначислений — commission-accrual.md (INV-7) и referral-accrual.md (INV-RA-5). - Производимое событие: wallet-transaction-recorded.md.
- Кредитная сторона: UC-WLT-001.
- ADR-0065, ADR-0021.