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

Event Contract: pin.crm.booking-paid (BookingPaidEvent)

Версионируемый контракт интеграционного события «оплата брони подтверждена». Consumer-документы ссылаются сюда, не переописывая payload; дом полей агрегата — entity: BookingEntity.

Назначение

Фиксирует подтверждение оплаты платного бронирования: менеджер застройщика проверяет прикреплённый клиентом чек и вручную ставит PaymentStatus = Paid (эквайринга нет — решение 9 ТЗ, оплата подтверждается по чеку). Публичное: даёт downstream-контекстам факт «бронь оплачена» без синхронного запроса в crmmessenger рисует в чат сделки карточку «договор оплачен» (US-INV-16), rewards учитывает факт оплаты брони в модели вознаграждений (промежуточный этап воронки; окончательная комиссия — по deal-completed).

Решение 9 ТЗ

Эквайринга нет: оплата брони подтверждается вручную менеджером застройщика по прикреплённому клиентом чеку (BookingPaymentProof), а не автоматическим платёжным шлюзом.

Metadata

ПолеЗначение
Event typepin.crm.booking-paid
Topicpin.crm.booking-paid.v1
Partitions / key3 (ADR-0056, 1–10k активных) / key = AggregateId = BookingId (порядок событий per-бронь)
ContractVersion1
Producercrm: подтверждение оплаты брони по чеку (INV-BKG-4)
Статусdraft

Событие публикуется атомарно с мутацией агрегата: пока подтверждение оплаты не зафиксировано, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = BookingId.

Payload BookingPaidEvent

Дом полей — entity: BookingEntity (раздел «Доменные и интеграционные события»); здесь перечислено только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
BookingIdGuidДаBookingEntity.IdImmutableКлюч агрегата брони (= AggregateId = Kafka key).
DealIdGuidДаBookingEntity.DealIdImmutableСделка-владелец (deal.md); адресат карточки в чат сделки.
TenantIdGuidДаBookingEntity.TenantIdImmutableЗастройщик объекта (DeveloperCompanyId); tenant boundary.
PropertyObjectIdGuidДаBookingEntity.PropertyObjectIdImmutableОбъект (логическая ссылка на chessboard); нужен rewards/аналитике воронки.
PaymentAmountMoneyДаBookingEntity.PaymentAmount (snapshot ProgramTerms.Price)Additive onlyПодтверждённая сумма оплаты брони (Amount + CurrencyId).
CurrencyCurrencySnapshotДасправочник валют handbook по PaymentAmount.CurrencyIdAdditive onlyDisplay-снимок валюты платежа на момент публикации (snapshots.md).
ConfirmedByUserIdGuidДаBookingEntity.PaymentConfirmedByUserIdImmutableМенеджер застройщика, подтвердивший оплату по чеку.
PaidAtDateTimeOffsetДаBookingEntity.PaidAtImmutableМомент подтверждения оплаты (US-INV-06: «Оплачено» + дата).

Событие эмитится только для платной программы (ProgramTerms.IsPaid = true) — путь AwaitingPayment проходит только платная бронь, поэтому PaymentAmount всегда присутствует.

Вложенная модель Money (дом — deal.md)

ПолеТипОбязательностьОграниченияОписание
AmountdecimalДаnumeric(18,2), > 0 для платной брониСумма платежа.
CurrencyIdshortДассылка на справочник валют handbook; RUB-only (ADR-0048)Валюта платежа; display-код — в снапшоте Currency.

CurrencySnapshot — вложенная snapshot-модель handbook; её поля не переописываются (snapshots.md), display-код валюты (IsoCode) живёт только внутри неё.

Что не входит в payload

ФИО/телефон клиента, 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).
Breaking change

Удаление/переименование/смена типа или смысла поля — новый 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, PaidAtInternalinclude
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 менеджер застройщика подтверждает оплату по чеку, Then PaymentStatus: Pending → Paid, ровно одно booking-paid публикуется атомарно с мутацией, PaymentAmount = ProgramTerms.Price, ConfirmedByUserId/PaidAt заполнены.
  • Given бронь без прикреплённого чека, When менеджер пытается подтвердить оплату, Then подтверждение не проходит (guard INV-BKG-4) и событие не публикуется.
  • Given бесплатная программа (ProgramTerms.IsPaid = false), When проходит подтверждение брони, Then booking-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.

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

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

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

  • Сущность/дом полей: booking.md (раздел «Доменные и интеграционные события», INV-BKG-4); родительский процесс и Money VO — deal.md; CurrencySnapshotsnapshots.md.
  • Источник (операция): подтверждение оплаты — область Developer, будущий api-method по конвенции ../api/.
  • Соседние контракты семейства брони: booking-created.md, booking-confirmed.md, booking-expired.md, booking-cancelled.md.
  • Консюмеры: ../consumers/ (messenger/rewards — будущие пути по конвенции); карта событий — README.md.