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

DELETE /api/investors/me/referral-links/{id} — Удалить реферальную ссылку (deleteReferralLink)

Назначение

Мягко удалить собственную реферальную ссылку: строка сохраняется (DeletedAt), slug освобождается (уникальность slug действует только среди не удалённых ссылок), журнал регистраций и реферальные связи по ссылке остаются нетронутыми (основание выплат). Default-ссылку удалить нельзя (INV-REF-L5 — на неё завязан шаринг фронта по умолчанию). Общий конверт — README.md.

Metadata

ПолеЗначение
Сервис/контекстreferral
API areapublic
Feature groupreferral-links
Статусapproved
Документdocs/06-services/referral/api/public/delete-referral-link.md

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

ПроверкаЗначение
Actor typeUser
Auth policy / Permissionsauthenticated; permission referral.links.manage-self
Scope (RBAC)self
Record-levelINV-REF-L4: только владелец; чужая ссылка → REFERRAL_LINK_NOT_FOUND
Auditdelete (soft): DeletedAt/DeletedByUserId + событие ReferralLinkDeletedEvent

HTTP-контракт

ПолеЗначение
MethodDELETE
Route/api/investors/me/referral-links/{id}
Success status200 (возвращает тело DeleteReferralLinkResult, не 204)
Idempotencyповторный DELETE удалённой ссылки → REFERRAL_LINK_NOT_FOUND (не идемпотентен по ответу)
Correlationсквозной trace через заголовок traceparent

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

Body отсутствует.

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный токен401
idrouteGuidДавалидный Guid; ссылка существует, не удалена, принадлежит актору, не defaultrecord-level INV-REF-L4; guard INV-REF-L5

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

ПолеТипОбязательностьИсточникMaskingОписание
idstring (Guid)ДаReferralLinkEntity.IdИдентификатор удалённой ссылки (подтверждение).

Алгоритм

  1. Контекст и доступ. Пользователь авторизован; требуется permission referral.links.manage-self.
  2. Проверка входа. id — непустой валидный Guid, иначе ValidationError.
  3. Проверка владения и состояния. Ссылка должна существовать, быть не удалённой и принадлежать актору (OwnerUserId == UserId); иначе (в том числе уже удалённая) → REFERRAL_LINK_NOT_FOUND (INV-REF-L4). Если ссылка помечена как default (IsDefault == true) → отказ REFERRAL_LINK_DEFAULT_UNDELETABLE (INV-REF-L5). Журнал/связи не проверяются — soft-delete их не затрагивает (referral-link.md, раздел «Связи»).
  4. Мягкое удаление. Ссылка помечается удалённой (DeletedAt = now(), DeletedByUserId = UserId), прочие поля не меняются; переход состояния Active → Deleted (referral-link.md), восстановление не предусмотрено. Slug автоматически освобождается (уникальность среди не удалённых). Параллельное изменение той же ссылки → CONCURRENCY_CONFLICT. Фиксируется событие ReferralLinkDeletedEvent. В ответ возвращается id.

Последствия для процесса привязки: удалённая ссылка не проходит validateReferralLink (isValid = false → фронт очищает cookie) и не резолвится guard-ом G1 (attach-rules.md) — новые регистрации/связи по ней не создаются. Slug становится доступен для повторного использования другими пользователями; журнал (getLinkRegistrations показывает исторический linkUrl) и связи по ссылке живут дальше.

Гонка «удаление ↔ привязка по тому же slug»: если процесс привязки (consumer T1/T2 или resolve) зарезолвил ссылку до коммита удаления — связь/запись журнала создаются штатно и переживают последующий soft-delete (журнал и связь ссылаются по Id, INV-REF-L6/INV-REF-R2); если удаление зафиксировано раньше — guard G1 не находит ссылку и привязка не выполняется (no-op, warning). Обе ветви корректны: потерянной или дублированной связи не возникает.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/невалиден токен«Требуется авторизация»re-login
ValidationError400id — не Guidперечень полейисправить
REFERRAL_LINK_NOT_FOUND400Не существует / чужая / уже удалена (INV-REF-L4)«Ссылка не найдена»no retry
REFERRAL_LINK_DEFAULT_UNDELETABLE400IsDefault == true (INV-REF-L5)«Ссылку по умолчанию удалить нельзя»no retry
CONCURRENCY_CONFLICT400Конкурентное изменение«Данные изменены, повторите»reload + retry

Предусловия и постусловия

Предусловия: ссылка существует, не удалена, принадлежит актору (INV-REF-L4) и не является default (INV-REF-L5).

Постусловия (при успехе): ссылка помечена удалённой (DeletedAt/DeletedByUserId), переход Active → Deleted, восстановление не предусмотрено; slug освобождён (уникальность действует среди не удалённых — INV-REF-L1) и доступен для повторного использования другими пользователями; журнал регистраций и реферальные связи по ссылке сохранены (основание выплат, linkUrl в журнале исторический); поднято ReferralLinkDeletedEvent. Далее ссылка не проходит validateReferralLink (isValid = false) и не резолвится guard-ом G1 — новые регистрации/связи по ней не создаются.

Acceptance Criteria

  • Given авторизованный владелец и его не-default ссылка, When DELETE …/referral-links/{id}, Then 200 {id} (не 204); ссылка soft-deleted, slug освобождён, журнал и связи не тронуты, событие удаления поднято.
  • Given повторный DELETE уже удалённой ссылки, When запрос, Then REFERRAL_LINK_NOT_FOUND (операция не идемпотентна по ответу).
  • Given ссылка default (IsDefault = true), When DELETE, Then REFERRAL_LINK_DEFAULT_UNDELETABLE (INV-REF-L5).
  • Given ссылка чужая или не существует, When DELETE, Then REFERRAL_LINK_NOT_FOUND (INV-REF-L4, чужая/несуществующая/удалённая неразличимы — нет user enumeration).
  • Given параллельное изменение той же ссылки, When DELETE, Then CONCURRENCY_CONFLICT (reload + retry).
  • Given id не Guid, When запрос, Then ValidationError; без токена — 401.

Совместимость и наблюдаемость

  • Контракт стабилен: 200 + {id} (не 204) — менять на 204 нельзя без обновления клиента (breaking).
  • Logs: UserId, LinkId — без PII; metrics: referral_links_deleted_total; traces — сквозной trace через traceparent.

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

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

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