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

GET /api/public/crm/purchase-config — Конфигурация способов оплаты (витрина)

Витрина карточки объекта недвижимости без авторизации запрашивает конфигурацию способов оплаты застройщика (по developerId либо propertyObjectId) — чтобы отрисовать варианты 100% оплаты, ипотеки и пресетов рассрочки и параметры калькулятора.

Назначение

Публичная read-модель конфигурации способов оплаты застройщика для карточки объекта недвижимости витрины: доступность и параметры 100% оплаты и ипотеки + список пресетов рассрочки с промежуточными платежами — чтобы фронт отрисовал варианты оплаты и параметры калькулятора. Отдаётся без авторизации. Только чтение; дом полей и семантика — purchase-program.md. Расчёт графика платежей — отдельный метод (preview-payment-schedule.md / get-payment-schedule.md), не этот endpoint. Привилегированный админ-аналог того же агрегата — get-purchase-program.md (под crm.purchase-programs.manage).

Metadata

ПолеЗначение
Сервис/контекстcrm
API areapublic (гейт витрины: анонимно)
Feature grouppurchase-programs
Статусdraft
Документdocs/06-services/crm/api/public/get-purchase-config.md

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

ПроверкаЗначение
Actor typeAnonymous (публичная карточка объекта; JWT не требуется)
Auth policy / Permissionsnone (публичный read-only метод); rate limit по IP
Scope (RBAC)не применяется (актор — вне tenant; tenant резолвится из параметра, не из JWT)
Record-levelне применяется
Tenant isolationtenant не из JWT: DeveloperCompanyId резолвится из developerId (напрямую) либо propertyObjectId (через chessboard, шаг 3); агрегат фильтруется явным TenantId = resolvedDeveloperId
Auditread (не sensitive); PII отсутствует (числовая конфигурация оплаты)
Доступ

Метод публичный: актор Anonymous, JWT не требуется, permissions/scope/record-level не применяются. Tenant не берётся из claims — резолвится из developerId (напрямую) либо propertyObjectId (через chessboard, шаг 3). Кодов авторизации (401/403) у метода нет.

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/public/crm/purchase-config?developerId={guid} или ?propertyObjectId={guid}
Success status200
Idempotency headerне применяется (безопасный GET)
Correlationсквозной trace через заголовок traceparent

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

Важно

Ровно один из developerId/propertyObjectId (взаимоисключающие входы витрины: карточка объекта знает propertyObjectId, листинг застройщика — developerId).

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
DeveloperIdqueryGuid?Условнозадан ровно один из DeveloperId/PropertyObjectId; != Guid.Empty; трактуется как DeveloperCompanyId
PropertyObjectIdqueryGuid?Условнозадан ровно один из DeveloperId/PropertyObjectId; != Guid.Empty; резолвится в chessboardDeveloperCompanyId (шаг 3); тенант вычисляется, не из claims

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

Публичная проекция агрегата; дом полей и семантика — purchase-program.md (enum MilestoneKind; правило расчёта графика). Внутренние поля админ-таба (Id программы, Version, UpdatedAt, audit) наружу не отдаются. Все enum сериализуются строкой.

ПолеТипОбязательностьИсточникMaskingОписание
DeveloperIdGuidДаresolved (query/chessboard, шаг 3)Застройщик-владелец конфигурации (эхо резолва).
FullPaymentFullPaymentConfigModelДаPurchaseProgramEntityУсловия 100% оплаты.
MortgageMortgageConfigModelДаPurchaseProgramEntityУсловия ипотеки.
InstallmentPresetsIReadOnlyList<InstallmentPresetConfigModel>Да (может быть пустым)live InstallmentPresetEntity (шаг 3)Пресеты рассрочки, SortOrder asc.

InstallmentPresets — именованная проекция-массив ограниченной кардинальности (не страничная выборка).

FullPaymentConfigModel

ПолеТипОбязательностьИсточникОписание
IsAvailableboolДаIsFullPaymentEnabledДоступна ли 100% оплата у застройщика.
DiscountPercentdecimalДаFullPaymentDiscountPercentСкидка при 100% оплате, % (0..100).

MortgageConfigModel

ПолеТипОбязательностьИсточникОписание
IsAvailableboolДаIsMortgageEnabledДоступна ли ипотека у застройщика.
AnnualRatePercentdecimalДаMortgageAnnualRatePercentГодовая ставка для калькулятора, % (0..100).
TermMonthsintДаMortgageTermMonthsСрок ипотеки, месяцы (1..600).

InstallmentPresetConfigModel

Проекция InstallmentPresetEntity (дом — purchase-program.md).

ПолеТипОбязательностьИсточникОписание
IdGuidДаIdИдентификатор пресета (для выбора способа оплаты при брони/сделке; Deal.InstallmentPresetId).
NamestringДаNameНазвание («Рассрочка 12 месяцев»).
InitialPaymentPercentdecimalДаInitialPaymentPercentПервоначальный взнос, % от цены (0..100).
MonthlyFixedPaymentdecimalДаMonthlyFixedPaymentФиксированный ежемесячный платёж, RUB (>= 0).
TermMonthsintДаTermMonthsСрок рассрочки, месяцы (1..600).
DiscountPercentdecimalДаDiscountPercentСкидка пресета от цены объекта, % (0..100).
MilestonePaymentsIReadOnlyList<MilestonePaymentModel>Да (может быть пустым)typed jsonb MilestonePayments.ItemsПромежуточные обязательные платежи.
SortOrderintДаSortOrderПорядок показа опций (>= 0).

MilestonePaymentModel

Проекция элемента typed jsonb MilestonePayment (ContractVersion = 1, дом — purchase-program.md); 1:1.

ПолеТипОбязательностьИсточникОписание
MonthintДаMonthМесяц платежа (1..TermMonths).
KindMilestoneKind (enum строкой)ДаKindpercent / fixed / remainder.
ValuedecimalДаValueЗначение по Kind (percent: %; fixed: RUB; remainder: 0).

Алгоритм

  1. Контекст и доступ. Публичный метод; актор Anonymous (JWT не требуется). Tenant в контексте отсутствует — резолвится на шаге 3.
  2. Проверка входа и предусловий. Задан ровно один из DeveloperId/PropertyObjectId и он != Guid.Empty (ни одного / оба / пустой → VALIDATION_FAILED). Существование объекта/агрегата — на шаге 3. Допустимость состояния не применяется (конфигурация без статусной машины, читается всегда). Scope/record-level не применяются (аноним, публичное чтение).
  3. Что читаем. Порядок: сначала резолв тенанта, затем агрегат.
    • резолв DeveloperCompanyId: если задан propertyObjectId — lookup объекта в chessboard → нет → CRM_PROPERTY_OBJECT_NOT_FOUND; из ответа берём DeveloperCompanyId. Если задан developerIdresolvedDeveloperId = developerId (внешний вызов не нужен);
    • живая программа покупки тенанта (единственная живая, INV-PPR-1); фильтр по тенанту — явный (TenantId == resolvedDeveloperId). Пусто → CRM_PURCHASE_PROGRAM_NOT_FOUND (застройщик ещё не сидирован миграцией);
    • живые пресеты рассрочки программы (SortOrder asc → lower(Name) asc), с их MilestonePayments из typed jsonb.
  4. Бизнес-правила и маппинг. Переходов состояний нет (чтение). Маппинг поле-в-поле program → PurchaseConfigModel (FullPayment = {IsFullPaymentEnabled, FullPaymentDiscountPercent}; Mortgage = {IsMortgageEnabled, MortgageAnnualRatePercent, MortgageTermMonths}; DeveloperId = resolvedDeveloperId); presets → InstallmentPresetConfigModel[]; MilestonePayments.Items → MilestonePaymentModel[]. Enum Kind сериализуется строкой. Внутренние поля (Id программы, Version, UpdatedAt, audit) в публичную проекцию не мапятся. Расчётов графика нет.
  5. Результат. PurchaseConfigModel → HTTP 200; при отказе — соответствующий код. Программа и пресеты согласованы на момент запроса.

Диаграмма

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

ТипДетали
Внешние вызовыchessboard lookup объекта по PropertyObjectIdDeveloperCompanyId (только если задан propertyObjectId; timeout 2 c, retry 1); при developerId — нет

Ошибки и failure modes

Ошибки
КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400не задан либо заданы оба developerId/propertyObjectId, либо Guid.Empty«Укажите застройщика или объект»no retry
CRM_PROPERTY_OBJECT_NOT_FOUND404propertyObjectId не найден в chessboard при резолве тенанта«Объект не найден»no retry
CRM_PURCHASE_PROGRAM_NOT_FOUND404у застройщика нет живого агрегата (не сидирован миграцией)«Программа покупки не найдена»no retry

Авторизационных кодов (401/403) нет — метод публичный.

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

  • Публичная конфигурация оплаты для карточки объекта, без авторизации (вход по застройщику/объекту). Новые optional поля PurchaseConfigModel/дочерних моделей — additive (без breaking); удаление/переименование/ смена типа поля или значения enum MilestoneKind — breaking (migration + rollout). Enum сериализуется строкой; клиент обязан толерантно обрабатывать неизвестное значение.
  • Изменения конфигурации поднимают доменное PurchaseProgramChanged в командах upsert (purchase-program.md); у публичного чтения интеграционных событий нет.
  • Logs: purchase-config viewed (resolvedDeveloperId, propertyObjectId?, счётчик пресетов; PII отсутствует); metrics: crm_purchase_config_requests_total{result}, latency p95 < 1 c (SLO чтений); traces — сквозной trace через traceparent; dashboard Grafana pin-crm.

Acceptance Criteria

  • AC-1 (вход по застройщику). Дано: у застройщика есть живая программа покупки. Когда: запрос с developerId. Тогда: 200 с FullPayment/Mortgage-конфигом и InstallmentPresets (по SortOrder asc), без обращения к chessboard.
  • AC-2 (вход по объекту). Дано: объект существует в chessboard. Когда: запрос с propertyObjectId. Тогда: DeveloperCompanyId резолвится из объекта, DeveloperId в ответе = резолву, 200.
  • AC-3 (оба/ни одного входа). Дано: заданы оба developerId и propertyObjectId либо ни одного (либо Guid.Empty). Когда: запрос. Тогда: отказ VALIDATION_FAILED (400).
  • AC-4 (объект не найден). Дано: propertyObjectId отсутствует в chessboard. Когда: запрос. Тогда: отказ CRM_PROPERTY_OBJECT_NOT_FOUND (404).
  • AC-5 (программа не сидирована). Дано: у застройщика нет живого агрегата программы. Когда: запрос. Тогда: отказ CRM_PURCHASE_PROGRAM_NOT_FOUND (404).
  • AC-6 (анонимный доступ). Дано: запрос без JWT. Когда: валидный вход. Тогда: 200 (авторизация не требуется, кодов 401/403 нет); внутренние поля (Id/Version/UpdatedAt/audit) в ответ не попадают.
  • AC-7 (пустой список пресетов). Дано: у программы нет живых пресетов рассрочки. Когда: запрос. Тогда: 200, InstallmentPresets = [] (не ошибка).

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

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

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