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

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.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areaplatform
Feature groupreferral-accruals
Статусdraft
Документdocs/06-services/rewards/api/platform/cancel-referral-accrual.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (Менеджер ПИН)
Auth policy / Permissionsrewards.accruals.manage (платформенный scope)
Scope (RBAC)платформа
Tenant isolationвне tenant-модели — реферальные начисления платформенные (ADR-0052)
Auditupdate: CancelledAt, CancelReason, Version + 1; переход фиксируется событием статуса

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/platform/referral-accruals/{accrualId}/cancel
Success status200
Idempotency headerне применяется (повтор из CancelledREWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN — двойной компенсации кошелька нет)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claims (sub)GuidДаRequiredactor-stamping
AccrualIdroute {accrualId}GuidДаRequired, непустой
CancelReasonbodyReferralCancelReasonДатолько BondRevoked | ManualCorrection (DealCancelled зарезервирован за consumer-ом расторжения сделки) — иначе ValidationError
ExpectedVersionbodylongДа>= 1; сверка с Version начисления (конкуренты — авто-апрув регистрацией акта, включение в поручение)optimistic concurrency

Модель ответа ReferralAccrualModel

Полная проекция начисления — дом list-referral-accruals.mdReferralAccrualModel»). Дельта операции: Status = Cancelled, заполнены CancelledAt и CancelReason, новая Version.

Алгоритм

  1. Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право rewards.accruals.manage.
  2. Проверка входа и предусловий.
    • формат: AccrualId непустой, CancelReason ∈ {BondRevoked, ManualCorrection}, ExpectedVersion >= 1; иначе ValidationError;
    • существование: начисление с AccrualId существует; иначе REWARDS_REFERRAL_ACCRUAL_NOT_FOUND;
    • конкуренция: версия совпадает с ExpectedVersion; иначе REWARDS_CONCURRENCY_CONFLICT. Начисление читается под блокировкой (защита от параллельного авто-апрува и захвата поручением).
  3. Guard отмены (INV-RA-5). Нарушения → REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN:
    • из Pending — отмена допустима всегда;
    • из Approved — только пока начисление свободно (PayoutOrderItemId is null); включённое в поручение освобождается только через Reject/Cancel самого поручения, после чего отмена возможна;
    • из Paid и Cancelled — запрещена: выплаченное вознаграждение компенсируется вне статусной машины (регламент ManualCorrection + корректировка кошелька), повтор отмены — no effect.
  4. Переход и фиксация. В одном согласованном изменении: начисление → Cancelled (CancelledAt, CancelReason из запроса, Version + 1); публикуется событие статуса (см. «Побочные эффекты»). Родительское комиссионное начисление и его строки распределения не затрагиваются — отмена рефералки не влияет на комиссию агентства.
  5. Результат. Проекция начисления (Status = Cancelled, CancelledAt, CancelReason, новая версия) → HTTP 200. Постусловие: начисление навсегда вне выплатного контура; при отмене из Approved контекст wallet по событию компенсирует ранее сделанный кредит счёта ReferralCommission корректирующей проводкой (ADR-0065, асинхронно — окно секунды).

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsReferralAccrualStatusChangedKafkaEventpin.rewards.referral-accruals.v1, key = AccrualId (`FromStatus = Pending
External callsnone
Cache / projections / searchnone
Notificationsnotifications по событию: партнёру «начисление отменено» (без раскрытия деталей разбора); при Approved → Cancelled wallet компенсирует кредит счёта ReferralCommission корректирующей проводкой (ADR-0065)

Ошибки и failure modes

Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) — дом README.md «Общие ошибки». Специфичные для операции:

КодHTTP statusУсловиеСообщениеRetry
REWARDS_REFERRAL_ACCRUAL_NOT_FOUND404Начисление не существует«Реферальное начисление не найдено»no retry
REWARDS_REFERRAL_ACCRUAL_CANCEL_FORBIDDEN409Отмена из 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 мс; частота — единичные отмены (ручной разбор).

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

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

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