POST /api/v1/platform/referral-accruals/{accrualId}/cancel — Ручная отмена реферального начисления
Ручная отмена реферального начисления оператором ПИН по итогам разбора: отзыв связи по фроду/ошибке
привязки (BondRevoked) либо отмена ошибочного начисления (ManualCorrection) —
domain/referral-accrual.md, INV-RA-5. Отмена из Pending — свободная;
из Approved — только пока начисление не включено в реферальное поручение; из Paid — запрещена
(выплаченное вознаграждение компенсируется вне статусной машины).
Назначение
Менеджер ПИН исключает начисление из выплатного контура: после отмены оно никогда не попадёт в реферальное
поручение, а если кошелёк партнёра уже был кредитован (отмена из Approved) — контекст wallet
компенсирует кредит корректирующей проводкой по событию (ADR-0065). Автоматическая отмена по расторжению
сделки (DealCancelled) — задача consumer-а
(../../consumers/crm-deal-cancelled.md), не этой операции.
Feature group — referral-accruals.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | referral-accruals |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/cancel-referral-accrual.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Менеджер ПИН) |
| Auth policy / Permissions | rewards.accruals.manage (платформенный scope) |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — реферальные начисления платформенные (ADR-0052) |
| Audit | update: CancelledAt, CancelReason, Version + 1; переход фиксируется событием статуса |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/platform/referral-accruals/{accrualId}/cancel |
| Success status | 200 |
| Idempotency header | не применяется (повтор из Cancelled → REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN — двойной компенсации кошелька нет) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | actor-stamping |
AccrualId | route {accrualId} | Guid | Да | Required, непустой | — |
CancelReason | body | ReferralCancelReason | Да | только BondRevoked | ManualCorrection (DealCancelled зарезервирован за consumer-ом расторжения сделки) — иначе ValidationError | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version начисления (конкуренты — авто-апрув регистрацией акта, включение в поручение) | optimistic concurrency |
Модель ответа ReferralAccrualModel
Полная проекция начисления — дом list-referral-accruals.md
(«ReferralAccrualModel»). Дельта операции: Status = Cancelled, заполнены CancelledAt и
CancelReason, новая Version.
Алгоритм
- Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право
rewards.accruals.manage.
- Проверка входа и предусловий.
- формат:
AccrualId непустой, CancelReason ∈ {BondRevoked, ManualCorrection},
ExpectedVersion >= 1; иначе ValidationError;
- существование: начисление с
AccrualId существует; иначе REWARDS_REFERRAL_ACCRUAL_NOT_FOUND;
- конкуренция: версия совпадает с
ExpectedVersion; иначе REWARDS_CONCURRENCY_CONFLICT.
Начисление читается под блокировкой (защита от параллельного авто-апрува и захвата поручением).
- Guard отмены (INV-RA-5). Нарушения →
REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN:
- из
Pending — отмена допустима всегда;
- из
Approved — только пока начисление свободно (PayoutOrderItemId is null); включённое в
поручение освобождается только через Reject/Cancel самого поручения, после чего отмена возможна;
- из
Paid и Cancelled — запрещена: выплаченное вознаграждение компенсируется вне статусной машины
(регламент ManualCorrection + корректировка кошелька), повтор отмены — no effect.
- Переход и фиксация. В одном согласованном изменении: начисление →
Cancelled (CancelledAt,
CancelReason из запроса, Version + 1); публикуется событие статуса (см. «Побочные эффекты»).
Родительское комиссионное начисление и его строки распределения не затрагиваются — отмена рефералки
не влияет на комиссию агентства.
- Результат. Проекция начисления (
Status = Cancelled, CancelledAt, CancelReason, новая версия)
→ HTTP 200. Постусловие: начисление навсегда вне выплатного контура; при отмене из Approved
контекст wallet по событию компенсирует ранее сделанный кредит счёта ReferralCommission
корректирующей проводкой (ADR-0065, асинхронно — окно секунды).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | ReferralAccrualStatusChangedKafkaEvent → pin.rewards.referral-accruals.v1, key = AccrualId (`FromStatus = Pending |
| External calls | none |
| Cache / projections / search | none |
| Notifications | notifications по событию: партнёру «начисление отменено» (без раскрытия деталей разбора); при Approved → Cancelled wallet компенсирует кредит счёта ReferralCommission корректирующей проводкой (ADR-0065) |
Ошибки и failure modes
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) — дом
README.md «Общие ошибки». Специфичные для операции:
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_REFERRAL_ACCRUAL_NOT_FOUND | 404 | Начисление не существует | «Реферальное начисление не найдено» | no retry |
REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN | 409 | Отмена из Approved при занятом поручением начислении; отмена из Paid/Cancelled (INV-RA-5) | «Начисление нельзя отменить в текущем состоянии» | no retry |
Acceptance criteria
- AC-1 (отмена из Pending). Given начисление
Pending (Version = N); When Менеджер ПИН
отменяет с CancelReason = ManualCorrection и ExpectedVersion = N; Then 200,
Status = Cancelled, CancelledAt/CancelReason заполнены, Version = N+1; опубликовано событие
статуса; при последующей регистрации акта родителя эта рефералка не апрувится (каскад берёт только
Pending).
- AC-2 (отмена из Approved с компенсацией кошелька). Given начисление
Approved, свободное
(PayoutOrderItemId = null), кошелёк партнёра ранее кредитован; When отмена (BondRevoked);
Then 200, Status = Cancelled; по событию wallet компенсирует кредит корректирующей проводкой —
баланс партнёра уменьшается асинхронно (окно секунды).
- AC-3 (занято поручением). Given начисление
Approved включено в реферальное поручение
(PayoutOrderItemId != null); When отмена; Then REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN
(409) — сначала Reject/Cancel поручения освобождает начисление.
- AC-4 (выплачено). Given начисление
Paid; When отмена; Then
REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN (409) — финансовая история неизменяема, возврат денег —
регламентом компенсации вне статусной машины.
- AC-5 (повторная отмена). Given начисление уже
Cancelled; When повторный cancel; Then
REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN (409) — двойной компенсации кошелька не происходит.
- AC-6 (недопустимая причина). Given
CancelReason = DealCancelled в запросе; When отмена;
Then ValidationError (400) — причина расторжения сделки проставляется только consumer-ом
pin.crm.deal-cancelled.v1.
- AC-7 (гонка с авто-апрувом). Given параллельные
cancel (из Pending) и регистрация акта
родителя; When обе операции; Then блокировка и сверка версии пропускают одну: победила отмена —
каскад акта рефералку пропускает; победил апрув — отмена получает REWARDS_CONCURRENCY_CONFLICT,
оператор перечитывает Approved и повторяет отмену уже с компенсацией кошелька.
- AC-8 (гонка с включением в поручение). Given параллельные
cancel (из Approved) и захват
начисления реферальным поручением; When обе операции; Then проходит одна; проигравшая отмена
получает REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN.
- AC-9 (доступ). Given актор без
rewards.accruals.manage; When отмена; Then FORBIDDEN
(403).
Совместимость и наблюдаемость
- Payload события — additive-only; breaking → новая версия топика. Enum
ReferralCancelReason
расширяется additive (unknown-enum-правило у потребителей).
- Logs:
AccrualId, DealId, PartnerUserId, CancelReason, FromStatus→Cancelled (суммы не
логируются — Sensitive); metrics: rewards_referral_accrual_cancel_total{reason}; traces: OTel,
correlation через traceparent; dashboard rewards-overview.
- НФТ (ADR-0056): мутация одного агрегата + одно событие; целевая латентность p95 < 300 мс; частота —
единичные отмены (ручной разбор).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы