Event Contract: pin.crm.booking-paid (BookingPaidEvent)
Версионируемый контракт интеграционного события «оплата брони подтверждена». Consumer-документы ссылаются
сюда, не переописывая payload; дом полей агрегата — entity: BookingEntity.
Назначение
Фиксирует подтверждение оплаты платного бронирования: менеджер застройщика проверяет прикреплённый
клиентом чек и вручную ставит PaymentStatus = Paid (эквайринга нет — решение 9 ТЗ, оплата подтверждается
по чеку). Публичное: даёт downstream-контекстам факт «бронь оплачена» без синхронного запроса в crm —
messenger рисует в чат сделки карточку «договор оплачен» (US-INV-16), rewards учитывает факт оплаты
брони в модели вознаграждений (промежуточный этап воронки; окончательная комиссия — по
deal-completed).
Эквайринга нет: оплата брони подтверждается вручную менеджером застройщика по прикреплённому клиентом чеку
(BookingPaymentProof), а не автоматическим платёжным шлюзом.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.crm.booking-paid |
| Topic | pin.crm.booking-paid.v1 |
| Partitions / key | 3 (ADR-0056, 1–10k активных) / key = AggregateId = BookingId (порядок событий per-бронь) |
| ContractVersion | 1 |
| Producer | crm: подтверждение оплаты брони по чеку (INV-BKG-4) |
| Статус | draft |
Событие публикуется атомарно с мутацией агрегата: пока подтверждение оплаты не зафиксировано, событие не
отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = BookingId.
Payload BookingPaidEvent
Дом полей — entity: BookingEntity (раздел «Доменные и интеграционные события»);
здесь перечислено только то, что реально в событии.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
BookingId | Guid | Да | BookingEntity.Id | Immutable | Ключ агрегата брони (= AggregateId = Kafka key). |
DealId | Guid | Да | BookingEntity.DealId | Immutable | Сделка-владелец (deal.md); адресат карточки в чат сделки. |
TenantId | Guid | Да | BookingEntity.TenantId | Immutable | Застройщик объекта (DeveloperCompanyId); tenant boundary. |
PropertyObjectId | Guid | Да | BookingEntity.PropertyObjectId | Immutable | Объект (логическая ссылка на chessboard); нужен rewards/аналитике воронки. |
PaymentAmount | Money | Да | BookingEntity.PaymentAmount (snapshot ProgramTerms.Price) | Additive only | Подтверждённая сумма оплаты брони (Amount + CurrencyId). |
Currency | CurrencySnapshot | Да | справочник валют handbook по PaymentAmount.CurrencyId | Additive only | Display-снимок валюты платежа на момент публикации (snapshots.md). |
ConfirmedByUserId | Guid | Да | BookingEntity.PaymentConfirmedByUserId | Immutable | Менеджер застройщика, подтвердивший оплату по чеку. |
PaidAt | DateTimeOffset | Да | BookingEntity.PaidAt | Immutable | Момент подтверждения оплаты (US-INV-06: «Оплачено» + дата). |
Событие эмитится только для платной программы (ProgramTerms.IsPaid = true) — путь AwaitingPayment
проходит только платная бронь, поэтому PaymentAmount всегда присутствует.
Вложенная модель Money (дом — deal.md)
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|---|---|---|---|
Amount | decimal | Да | numeric(18,2), > 0 для платной брони | Сумма платежа. |
CurrencyId | short | Да | ссылка на справочник валют handbook; RUB-only (ADR-0048) | Валюта платежа; display-код — в снапшоте Currency. |
CurrencySnapshot — вложенная snapshot-модель handbook; её поля не переописываются
(snapshots.md), display-код валюты (IsoCode) живёт только внутри неё.
ФИО/телефон клиента, RequisiteSetId, номер эскроу, бинарь чека (BookingPaymentProof) в payload не
входят — consumers, которым нужно ФИО, читают собственную реплику identity (см. раздел «Безопасность»
ниже).
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
Менеджер застройщика подтверждает оплату по чеку из статуса AwaitingPayment, при наличии документа-чека BookingPaymentProof (INV-BKG-4); эквайринга нет (решение 9) | PaymentStatus: Pending → Paid (Booking.Status остаётся AwaitingPayment до подтверждения брони) | атомарно со сменой состояния агрегата |
BookingPaidEvent (факт оплаты) и BookingConfirmedEvent (переход → Booked) —
разные контракты: если подтверждение оплаты и подтверждение брони выполняются вместе, обе записи
публикуются в том же согласованном изменении, но остаются самостоятельными событиями своих топиков.
Потребители (consumers)
Сверено с README («Events И Consumers») и booking.md.
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
messenger | Карточка «договор оплачен» в чат сделки DealId (US-INV-16) | MessageId | < 1 мин |
rewards | Учёт факта оплаты брони в модели вознаграждений (промежуточный этап; итоговая комиссия — по deal-completed) | MessageId | < 5 мин |
Доставка, порядок и обработка сбоев
- Гарантия публикации. Событие уходит только после фиксации подтверждения оплаты (
PaymentStatus: Pending → Paid); при откате изменения не публикуется. Доставка — at-least-once, ключBookingId. - Порядок. События одной брони упорядочены ключом
BookingId.BookingPaidEventиBookingConfirmedEvent— разные топики; если оба фиксируются в одном изменении, их взаимный порядок между топиками не гарантируется — потребители обрабатывают их независимо. - Идемпотентность потребителя.
messenger/rewardsдедуплицируют поMessageId;rewardsдополнительно — first-seen поBookingIdдля факта оплаты (повторная доставка не задваивает этап воронки). - Retry. Транзиентный сбой потребителя → повтор обработки с backoff; событие доступно до успешной обработки, side effect не теряется.
- DLQ. Неустранимый сбой (нераспознанная схема) после исчерпания попыток → dead-letter c алертом оператору; проигрывание из DLQ безопасно (обработка идемпотентна).
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик версию кодирует по конвенции — суффикс.v1.- Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
- Unknown enum — не применяется: payload не содержит enum-полей (валюта — типизированная ссылка
CurrencyId+ snapshot, не enum).
Удаление/переименование/смена типа или смысла поля — новый ContractVersion = 2 + новый топик
pin.crm.booking-paid.v2 + параллельная публикация (dual-publish) на период миграции + deadline миграции
consumers + sunset .v1.
Безопасность
Событие не переносит ФИО/телефоны, платёжные реквизиты, эскроу-счёт и бинарь чека — только идентификаторы
и сумму. messenger/rewards, которым нужно отображаемое имя, берут его из собственной реплики identity
(pin.identity.users.v1).
| Поле | Класс данных | Правило |
|---|---|---|
BookingId, DealId, TenantId, PropertyObjectId, ConfirmedByUserId, PaidAt | Internal | include |
PaymentAmount {Amount, CurrencyId}, Currency (CurrencySnapshot) | Internal (финансовая сумма) | include; в логах — не разглашать сверх необходимости |
Событие НЕ содержит secrets, tokens, raw credentials, полные PII/платёжные реквизиты, unbounded вложения.
Если ФИО/телефон когда-либо попадут в связанные логи producer/consumer (join с репликой identity), они
маскируются по общему правилу PII: ФИО — инициалы, телефон — +7***1234.
Поток события
Наблюдаемость
- Логи (без PII и без суммы): факт публикации
booking-paidсBookingId/DealId/PropertyObjectId/ConfirmedByUserId/PaidAt;PaymentAmountв логах не печатается (финансовая величина). У потребителя — результат обработки (карточка «договор оплачен» / учтён этап воронки). - Метрики: число подтверждений оплаты по топику; сквозная задержка до
messenger(< 1 мин — карточка «договор оплачен») иrewards(< 5 мин); глубина DLQ (порог алерта > 0); доля повторных доставок. - Доступ: топик — m2m ACL Kafka (
messenger/rewards); чек, реквизиты и эскроу в событие не входят.
Acceptance Criteria
- Given платная бронь в
AwaitingPaymentс прикреплённым чекомBookingPaymentProof, When менеджер застройщика подтверждает оплату по чеку, ThenPaymentStatus: Pending → Paid, ровно одноbooking-paidпубликуется атомарно с мутацией,PaymentAmount = ProgramTerms.Price,ConfirmedByUserId/PaidAtзаполнены. - Given бронь без прикреплённого чека, When менеджер пытается подтвердить оплату, Then подтверждение не проходит (guard INV-BKG-4) и событие не публикуется.
- Given бесплатная программа (
ProgramTerms.IsPaid = false), When проходит подтверждение брони, Thenbooking-paidне эмитится (путьAwaitingPaymentпроходит только платная бронь). - Given подтверждение оплаты и подтверждение брони выполнены вместе, When изменение фиксируется,
Then публикуются оба события своих топиков (
booking-paidиbooking-confirmed) — самостоятельными записями. - Given повторная доставка события, When
rewardsобрабатывает, Then этап оплаты учитывается один раз (first-seen поBookingId). - Given breaking-изменение payload, When вводится новый контракт, Then
pin.crm.booking-paid.v2- dual-publish + sunset
.v1.
- dual-publish + sunset
Обратные ссылки
Автоссылки: где используется этот документ.
- API (6): GET /api/agent/crm/deals/{dealId} — Карточка сделки (агент), GET /api/investor/crm/deals/{dealId} — Карточка сделки (инвестор), POST /api/agent/crm/bookings/{bookingId}/confirm — Подтвердить бронь (бесплатная программа), POST /api/developer/crm/bookings/{bookingId}/confirm-payment — Подтвердить оплату брони, POST /api/investor/crm/bookings/{bookingId}/payment-proof — Прикрепить чек оплаты брони, PUT /api/investor/crm/bookings/{bookingId}/requisites — Выбрать реквизиты для брони
- Use Cases (2): UC-BKG-004. Оплата платного бронирования (чек + подтверждение), UC-CAL-007. Дедлайны брони CRM в календаре агента
- События (4): Event Contract: pin.crm.booking-cancelled (BookingCancelledEvent), Event Contract: pin.crm.booking-confirmed (BookingConfirmedEvent), Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.booking-expired (BookingExpiredEvent)
- Консюмеры (4): Consumer: Booking Paid Card (MessengerBookingPaidConsumer), Consumer: Calendar CRM Booking Sync (calendar-crm-booking-events), Consumer: CRM Events → Notifications (notifications-crm-events), Consumer: DevStatsFunnelBookingPaidConsumer
- Саги (1): Saga «Booking» (Резерв → Бронь → передача в продажу)
- scenarios (1): UC-OVR-006. Сделка: от брони до регистрации (инвестор-инициатор)
- index.md (1): Calendar Service
- registries (1): Реестр событий PIN
Связанные документы
- Сущность/дом полей: booking.md (раздел «Доменные и интеграционные события»,
INV-BKG-4); родительский процесс и
MoneyVO — deal.md;CurrencySnapshot— snapshots.md. - Источник (операция): подтверждение оплаты — область Developer, будущий api-method по конвенции
../api/. - Соседние контракты семейства брони: booking-created.md, booking-confirmed.md, booking-expired.md, booking-cancelled.md.
- Консюмеры:
../consumers/(messenger/rewards — будущие пути по конвенции); карта событий — README.md.