POST /api/v1/platform/receipts/{receiptId}/register — Регистрация акта поступления средств
Перевод акта Draft → Registered — единый гейт всех денежных следствий
(ADR-0067): после сверки
сходимости (Σ позиций = сумме акта, INV-RC-2) атомарно все начисления-позиции переходят
Confirmed → MoneyReceived, связанные реферальные начисления — Pending → Approved, публикуются события
получения денег. До регистрации никакие выплаты (агентские и реферальные) недоступны; Registered-акт
неизменяем и не откатывается (INV-RC-4, domain/platform-receipt.md).
Назначение
Финансовый оператор ПИН сверил черновик акта с фактическим платежом застройщика и фиксирует получение
денег: с этого момента доли агентов доступны платёжным поручениям агентств, реферальные вознаграждения —
реферальным поручениям, кошельки получателей кредитуются (ADR-0065). Feature group — receipts.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | receipts |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/register-platform-receipt.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (финансовый оператор ПИН) |
| Auth policy / Permissions | rewards.receipts.manage (право независимо от прав арбитража сделок CRM, ADR-0067 п.5) |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — платформенная сущность (ADR-0052); каскад затрагивает начисления разных агентств одного застройщика |
| Audit | update: RegisteredAt, RegisteredByUserId = ActorUserId, Version + 1; Registered-акт неизменяем — сам себе аудит-след |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/platform/receipts/{receiptId}/register |
| Success status | 200 |
| Idempotency header | не применяется — естественная идемпотентность: повторный вызов по уже Registered-акту — no-op, 200 с текущей проекцией; каскад и события не повторяются |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | actor-stamping (RegisteredByUserId) |
ReceiptId | route {receiptId} | Guid | Да | Required, непустой | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version акта (конкурент — Cancel/правка состава) | optimistic concurrency |
Модель ответа PlatformReceiptModel
Полная проекция акта — дом: create-platform-receipt.md
(«PlatformReceiptModel»). Дельта операции: Status = Registered, заполнен RegisteredAt,
ItemsTotal = Amount (сходимость сверена), новая Version. RegisteredByUserId наружу не выводится
(внутренний audit-stamp).
Алгоритм
- Контекст и доступ. Операцию выполняет финансовый оператор ПИН в платформенном scope; требуется
право
rewards.receipts.manage. - Проверка входа и предусловий.
- формат:
ReceiptIdнепустой,ExpectedVersion >= 1; иначеValidationError; - существование: акт с
ReceiptIdсуществует; иначеREWARDS_RECEIPT_NOT_FOUND; - идемпотентный повтор: если акт уже
Registered— операция завершается no-op:200с текущей проекцией, каскад и публикации не повторяются (двойногоMoneyReceived/Approvedне бывает); - допустимость состояния: статус —
Draft; изCancelledрегистрация невозможна →REWARDS_RECEIPT_INVALID_STATUS_TRANSITION; - конкуренция: версия совпадает с
ExpectedVersion; иначеREWARDS_CONCURRENCY_CONFLICT. Акт читается под блокировкой вместе с позициями (защита от параллельныхCancel/правки состава).
- формат:
- Перепроверка состава по актуальному состоянию (позиции могли устареть с момента включения). Каждое
начисление-позиция читается под блокировкой:
Status = ConfirmedиDeveloperCompanyId= застройщику акта (INV-RC-1); нарушение (начисление отменено по расторжению сделки, ещё не подтверждено) →REWARDS_RECEIPT_ITEM_INVALID— оператор исключает позицию из состава и корректирует сумму акта;- сумма позиции равна текущему
CommissionAmountначисления; расхождение (gross скорректирован после включения — adjust-commission-accrual.md) →REWARDS_RECEIPT_ITEM_STALE— оператор пересобирает состав; ItemsCount >= 1.
- Сходимость (INV-RC-2).
Σ Items[].Amount.Value = Amount.Valueакта; иначеREWARDS_RECEIPT_SUM_MISMATCH. Расхождение фактического платежа лечится до регистрации: корректировкой gross начисления, исключением непокрытых позиций (они остаютсяConfirmedдо следующего акта) или исправлением суммы акта. - Атомарный каскад (INV-RC-4). В одном согласованном изменении:
- акт →
Registered(RegisteredAt,RegisteredByUserId = актор,Version + 1); - каждое начисление-позиция →
Confirmed → MoneyReceived(MoneyReceivedAt = RegisteredAt,PlatformReceiptId = ReceiptId) — domain/commission-accrual.md; - каждое реферальное начисление в
Pending, чьё родительское начисление вошло в акт, →Pending → Approved(ApprovedAt = RegisteredAt) — domain/referral-accrual.md, INV-RA-3; отменённые рефералки каскад не трогает; - атомарно публикуются все события (см. «Побочные эффекты»).
Либо всё, либо ничего: частичной регистрации не бывает; наблюдатель не увидит
Registered-акт с начислениями ещё вConfirmed.
- акт →
- Результат. Проекция акта (
Status = Registered,RegisteredAt, новая версия) → HTTP200. Постусловие:Agent-строки распределений позиций доступны платёжным поручениям агентств,Approved-рефералки — реферальным поручениям (create-referral-payout-order.md); кошельки получателей кредитуются контекстомwalletасинхронно по событиям (ADR-0065, окно — секунды).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | CommissionMoneyReceivedKafkaEvent → pin.rewards.commission-money-received.v1, key = id начисления — по одному на каждое начисление акта (payload: DealId, PlatformReceiptId, Number, MoneyReceivedAt, Distributions[] {RecipientType Platform/Agency/Agent, RecipientUserId?, Amount}; деньги — Value + CurrencyId + CurrencySnapshot, ../../../handbook/domain/snapshots.md); контракт — ../../events/commission-money-received.md. CommissionAccrualStatusChangedKafkaEvent → pin.rewards.commission-accrual-status-changed.v1, key = id начисления (Confirmed → MoneyReceived) — по каждому начислению (../../events/commission-accrual-status-changed.md). ReferralAccrualStatusChangedKafkaEvent → pin.rewards.referral-accruals.v1, key = id рефералки (Pending → Approved) — по каждой апрувнутой (domain/referral-accrual.md) |
| External calls | none |
| Cache / projections / search | none (сводки ЛК — SQL по PostgreSQL, ADR-0056) |
| Notifications | notifications по событиям: агентам/агентствам «деньги по сделке получены — доля доступна к выплате», партнёрам «вознаграждение подтверждено» |
Интеграционная последовательность. Каскад внутри rewards синхронен и атомарен — сразу после 200
Agent-строки и Approved-рефералки доступны сборке поручений. Кредиты кошельков (счета
AgentCommission/AgencyCommission/PlatformRevenue по Distributions[], ReferralCommission по
апруву рефералки — контекст wallet, ADR-0065) и уведомления — асинхронно по событиям (окно — секунды);
в этом окне баланс кошелька может ещё не отражать полученные деньги при уже изменённых статусах в
rewards.
Ошибки и failure modes
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) — дом
README.md «Общие ошибки». Специфичные для операции:
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
REWARDS_RECEIPT_NOT_FOUND | 404 | Акт не существует | «Акт не найден» | no retry |
REWARDS_RECEIPT_INVALID_STATUS_TRANSITION | 409 | Регистрация из Cancelled | «Акт нельзя зарегистрировать в текущем статусе» | no retry |
REWARDS_RECEIPT_ITEM_INVALID | 409 | Позиция ссылается на начисление не в Confirmed / чужого застройщика (INV-RC-1 при перепроверке) | «Позиция акта недействительна — пересоберите состав» | no retry (исключить позицию) |
REWARDS_RECEIPT_ITEM_STALE | 409 | Сумма позиции разошлась с актуальным CommissionAmount (gross скорректирован после включения) | «Сумма позиции устарела — пересоберите состав» | no retry (пересобрать состав) |
REWARDS_RECEIPT_SUM_MISMATCH | 409 | Σ позиций ≠ сумме акта (INV-RC-2) | «Сумма позиций не сходится с суммой поступления» | no retry (выровнять суммы) |
Acceptance criteria
- AC-1 (регистрация с каскадом). Given
Draft-акт (Version = N), позиции —Confirmed-начисления застройщика, Σ позиций =Amount, у части начислений естьPending-рефералки; When оператор регистрирует сExpectedVersion = N; Then акт →Registered(RegisteredAt,Version = N+1); каждое начисление →MoneyReceived(MoneyReceivedAt,PlatformReceiptId); каждаяPending-рефералка родителей →Approved; опубликованыcommission-money-received×N,commission-accrual-status-changed×N, реферальные status-changed ×K; ответ200. Постусловие:Agent-строки иApproved-рефералки доступны поручениям;walletасинхронно кредитует счета получателей. - AC-2 (идемпотентный повтор). Given акт уже
Registered; When повторныйregister(в т.ч. сетевой ретрай клиента); Then200с текущей проекцией — no-op: статусы начислений/рефералок не меняются, события повторно не публикуются. - AC-3 (расхождение суммы). Given Σ позиций ≠
Amount; When регистрация; ThenREWARDS_RECEIPT_SUM_MISMATCH(409) — акт остаётсяDraft, каскад не выполняется, события не публикуются. - AC-4 (устаревшая позиция). Given после включения позиции gross начисления скорректирован
ПИН-админом; When регистрация; Then
REWARDS_RECEIPT_ITEM_STALE(409) — оператор пересобирает состав по актуальным суммам. - AC-5 (начисление отменено). Given начисление-позиция отменено из
Confirmed(расторжение сделки) до регистрации; When регистрация; ThenREWARDS_RECEIPT_ITEM_INVALID(409) — оператор исключает позицию и корректирует сумму акта. - AC-6 (атомарность). Given акт с N позициями и K рефералками; When регистрация; Then все
переходы и все публикации происходят в одном согласованном изменении — промежуточное состояние
(«акт
Registered, а начисление ещёConfirmed») наружу не наблюдается; при любом отказе шага 3–5 акт целиком остаётсяDraft. - AC-7 (гонка с отменой/правкой состава). Given параллельные
registerиcancel(или правка состава) одногоDraft-акта; When обе операции; Then одна побеждает по блокировке и сверке версии, проигравшая —REWARDS_CONCURRENCY_CONFLICT(409) либо guard статуса. - AC-8 (рефералка без родителя не апрувится). Given рефералка сделки, чьё начисление в акт не
вошло; When регистрация; Then эта рефералка остаётся
Pending— апрув только через акт её родительского начисления (INV-RA-3). - AC-9 (доступ). Given актор без
rewards.receipts.manage; When регистрация; ThenFORBIDDEN(403).
Совместимость и наблюдаемость
- Payload событий — additive-only; breaking → новая версия топика (dual-publish + sunset). Повторная доставка событий потребителями дедуплицируется по id начисления/рефералки + переходу (конвенция README.md «Согласованность и порядок событий»).
- Logs:
ReceiptId,Number,DeveloperCompanyId,ItemsCount, число апрувнутых рефералок, id начислений (суммы не логируются — Sensitive); metrics:rewards_receipt_register_total{result},rewards_accrual_awaiting_receipt_max_age_minutes(gauge + алерт — оператор ПИН как узкое место, ADR-0067); traces: OTel, correlation черезtraceparent; dashboardrewards-overview; runbook —runbooks/rewards-receipt-register.md(по конвенции). - НФТ (ADR-0056): каскад — десятки-сотни строк (позиции + начисления + рефералки) в одной транзакции под блокировкой + публикация 2N+K событий; целевая латентность p95 < 1 с; частота — единицы регистраций в неделю на застройщика.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (7): GET /api/v1/platform/receipts — Реестр актов поступления средств, GET /api/v1/platform/referral-accruals — Реестр реферальных начислений, POST /api/v1/platform/commission-accruals/{accrualId}/adjust — Корректировка суммы начисления (gross), POST /api/v1/platform/receipts — Создание акта поступления средств (черновик), POST /api/v1/platform/referral-accruals/{accrualId}/adjust — Корректировка суммы реферального начисления, POST /api/v1/platform/referral-payout-orders — Создание реферального платёжного поручения, Rewards API — реестр операций и общие контрактные модели
- Use Cases (2): UC-RWD-010. Акт поступления средств — гейт получения денег, US-реестр: Финансовый блок ПИН (US-FIN)
- registries (1): Реестр API-методов PIN
- index.md (1): Rewards Service
Связанные документы
- Сущность, инварианты и «Регистрация (атомарный каскад)»: domain/platform-receipt.md (INV-RC-1..4).
- Гейт и статусы начислений: domain/commission-accrual.md
(
MoneyReceived, INV-7/8); авто-апрув рефералок: domain/referral-accrual.md (INV-RA-3). - Смежные операции: create-platform-receipt.md (создание, правка состава, отмена черновика), list-platform-receipts.md.
- Выплаты после гейта: ../agency/create-payout-order.md (агентские), create-referral-payout-order.md (реферальные).
- Контракты событий: ../../events/commission-money-received.md, ../../events/commission-accrual-status-changed.md, ../../events/referral-accrual-status-changed.md.
- ADR-0067 (гейт, неизменяемость, компенсации), ADR-0065 (кредиты кошелька), ADR-0056 (simplicity-first).