POST /api/messenger/contacts/requests/{contactId}/decline — Отклонить заявку в контакты
Назначение
Адресат отклоняет входящую заявку в контакты (Pending → Declined). Профиль инициатора не раскрывается,
звонок недоступен, а пара освобождается (выходит из ограничения активной пары ux_contacts_active_pair, т.к.
оно действует только для статусов Pending и Accepted) — при желании инициатор позже подаст заявку заново.
Вызывает пользователь-адресат; тело ответа не возвращается. Интеграционного события нет (отклонение приватно,
кросс-сервисных потребителей нет); инициатору уходит только realtime-push ContactRequestDeclined для
обновления интерфейса.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | messenger |
| API area | public |
| Feature group | contacts |
| Статус | draft |
| Документ | docs/06-services/messenger/api/contacts/decline-contact-request.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (только адресат заявки) |
| Auth policy / Permissions | messenger.contacts.manage (каталог — ../../../identity/domain/permission.md) |
| Scope (RBAC) | платформа (person-level, вне tenant, ADR-0052) |
| Record-level | actor == AddresseeUserId (INV-CT-3) |
| Tenant isolation | не применяется — контакты вне tenant |
| Audit | update (переход Declined; RespondedByUserId = actor, RespondedAt) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/messenger/contacts/requests/{contactId}/decline |
| Success status | 204 (отклонено); идемпотентный повтор на Declined → 204 |
| Idempotency header | не применяется (идемпотентность — по целевому статусу Declined, см. Алгоритм) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ActorUserId | identity claims | Guid | Да (вычисляется) | должен == AddresseeUserId | record-level (INV-CT-3) |
ContactId | route | Guid | Да | != Guid.Empty; существующая связь | — |
Модель ответа <нет тела>
Успех — 204 No Content (тела нет). Ошибки — ProblemDetails (см. ниже). Итоговый статус связи
(Declined) интерфейс получает из realtime-push ContactRequestDeclined либо перезапросом
list-contact-requests.md.
Предусловия и постусловия
Предусловия. Актор авторизован и имеет messenger.contacts.manage. Связь contactId существует; актор —
адресат заявки (actor == AddresseeUserId, INV-CT-3). Связь в статусе Pending (или уже Declined — для
идемпотентного повтора).
Постусловия. Связь в Declined, RespondedAt = now, RespondedByUserId = actor. Профиль инициатора не
раскрыт, звонок недоступен. Пара выходит из ограничения активной пары ux_contacts_active_pair (оно действует
только для Pending/Accepted) — инициатор может позже подать заявку заново. Интеграционное событие не
публикуется (отклонение приватно); инициатору уходит только realtime ContactRequestDeclined.
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь-адресат; требуется право
messenger.contacts.manage(иначе —FORBIDDEN). Идентификатор связиcontactIdберётся из маршрута. - Проверка входа.
ContactIdнепуст — иначеVALIDATION_FAILED. - Загрузка связи и проверка прав. Найти связь по
contactId; если её нет —NOT_FOUND. Актор должен быть адресатом заявки (actor == AddresseeUserId,INV-CT-3); иначе —MSG_CONTACT_FORBIDDEN. - Бизнес-правила и переход. Идемпотентность: если связь уже
Declined— ничего не менять (204). Если связь ужеAcceptedилиCancelled— операция недопустима (MSG_CONTACT_INVALID_STATE; удаление подтверждённого контакта — remove-contact.md). Иначе перевести связьPending → Declined:RespondedAt = now,RespondedByUserId = actor. Параллельная гонкаDecline/Accept— конфликтMSG_CONTACT_CONFLICT. - Что меняется. Обновляется единственный агрегат связи; пара освобождается для новой заявки. Переход и аудит фиксируются в одном согласованном изменении.
- Факты и события. Интеграционное событие не публикуется (отклонение приватно). Инициатору уходит только
realtime-push
ContactRequestDeclined(адресат уже видит результат). - Результат.
204 No Content. Ошибки — 400/403/404/409.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | нет (отклонение приватно) |
| Проекции / поиск | освобождается ограничение активной пары ux_contacts_active_pair — пара доступна для новой заявки |
| Уведомления | realtime-push ContactRequestDeclined инициатору (без email/push из notifications) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | ContactId пуст/некорректен | «Проверьте запрос» | no retry |
FORBIDDEN | 403 | Нет permission | «Недостаточно прав» | no retry |
MSG_CONTACT_FORBIDDEN | 403 | INV-CT-3: актор не адресат заявки | «Отклонить может только адресат» | no retry |
NOT_FOUND | 404 | Связь не найдена | «Заявка не найдена» | no retry |
MSG_CONTACT_INVALID_STATE | 409 | Заявка Accepted/Cancelled | «Заявка недоступна для отклонения» | no retry |
MSG_CONTACT_CONFLICT | 409 | Гонка Decline/Accept | «Повторите — состояние изменилось» | retry (перечитать) |
Повтор на уже Declined — не ошибка: идемпотентный 204.
Acceptance Criteria
- Отклонение (happy). Дано: связь
Pending, актор — адресат. Когда отклоняет. Тогда связь →Declined,204; пара освобождается для новой заявки; инициатору уходит realtimeContactRequestDeclined; профиль не раскрыт, Kafka-события нет. - Идемпотентный повтор. Дано: связь уже
Declined. Когда актор отклоняет снова. Тогда204без изменений. - Не адресат. Дано: актор — инициатор/третье лицо. Когда пытается отклонить. Тогда
403 MSG_CONTACT_FORBIDDEN(INV-CT-3). - Недопустимый статус. Дано: связь
AcceptedилиCancelled. Когда актор отклоняет. Тогда409 MSG_CONTACT_INVALID_STATE(удаление подтверждённого контакта — черезremove-contact). - Гонка Decline/Accept. Дано: параллельные отклонение и подтверждение. Тогда побеждает один переход;
проигравший —
409 MSG_CONTACT_CONFLICT, перечитывает состояние. - Нет связи. Дано:
contactIdне существует. Тогда404 NOT_FOUND. - Повторная заявка после отклонения. Дано: связь
Declined, пара свободна. Когда инициатор подаёт заявку заново. Тогда новая заявка создаётся (ограничение активной пары не мешает).
Совместимость и наблюдаемость
- Контракт стабилен (нет тела ответа); ужесточение guard-ов — breaking для UI-сценариев.
- Logs: actor,
contactId(без ФИО/телефона); metricsmessenger_contact_requests_declined_total{result}, latency p95; traces черезtraceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): DELETE /api/messenger/contacts/{contactId} — Удалить контакт / отозвать исходящую заявку, GET /api/messenger/contacts/requests — Заявки в контакты (входящие/исходящие), POST /api/messenger/contacts/requests/{contactId}/accept — Принять заявку в контакты
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../../domain/contact.md (
INV-CT-3,ContactDeclined). - Соседние: accept-contact-request.md, remove-contact.md (отзыв исходящей / удаление контакта), list-contact-requests.md.
- ADR: 0052 tenancy, 0055 own-mini-messenger.