ADR-0066 — удержание ПИН и реферальные ставки
Статус
Accepted
Дата
2026-07-15
Контекст
В финансовом контуре rewards посчитана только агентская сторона комиссии, а доля самой платформы и
реферальные деньги не формализованы:
- Доли ПИН нет вовсе. Начисление (rewards/domain/commission-accrual.md)
считает gross-комиссию по правилу застройщика (
CommissionAmount = база × CommissionPercent / 100, reward-rule.md) и при подтверждении распределяет её двумя строкамиAgent+Agencyпо политике агентства (agency-commission-policy.md) — от gross. Вознаграждение ПИН как площадки нигде не фиксируется, не начисляется и не отчитывается. - Реферальные деньги повисли между контекстами.
referralвладеет фактом связи «кто кого привёл» (ReferralBond) и явно декларирует границу: деньги — зонаrewards, начисления строятся поpin.referral.bonds.v1(referral/README.md, решение №7). Но вrewardsнет ни ставок реферального вознаграждения, ни реферальных начислений, ни обещанного consumer-а связей — обязательство «основание будущих выплат» ничем не подкреплено. - Решение владельца по модели монетизации. ПИН удерживает часть ставки застройщика в процентных пунктах (0.25–1.50 п.п.), остальная ставка достаётся агентской стороне; реферальные партнёры получают процент от конечной цены сделки, ставки различаются по партнёрам и застройщикам и могут временно повышаться (акции).
Решение
1. Удержание ПИН — только процентные пункты от ставки застройщика
- Вводится сущность
PlatformCommissionTerms(контекстrewards, платформенный scope):AgencyCompanyId?(null— глобальный дефолт для всех агентств),RetentionPercentagePoints— жёсткий диапазон 0.25–1.50 п.п., статусыDraft/Active/Archived. - Модель расчёта:
PlatformFeeAmount = база × RetentionPercentagePoints / 100, где база — та же, что у применённого правила застройщика (CalculationBase: базовая или конечная цена). Пул агентской стороны =CommissionAmount(gross) −PlatformFeeAmount. Политика агентства делит пул, а не gross (вход алгоритма распределения в agency-commission-policy.md меняется соответственно). - Резолюция условий на момент завершения сделки: активные per-agency условия → глобальный дефолт →
fallback 0 п.п. (начисление создаётся без удержания, warn-метрика
rewards_platform_terms_missing_total+ алерт). - В начисление снимается снапшот на момент
DealCompletedAt:PlatformRetentionPercentagePoints,PlatformFeeAmount,AppliedPlatformTermsId; смена условий не пересчитывает существующие начисления. - Удержание — процентные пункты цены, а не доля комиссии.
PlatformFeeAmountсчитается от базы начисления (CalculationBase), а не отCommissionAmount. Как следствие, при последующей корректировке gross-комиссии (компенсирующее начисление, ADR-0067) удержание ПИН не пересчитывается — оно зафиксировано в пунктах цены и остаётся привязанным к базе, а не плавает вслед за суммой комиссии. - Guard корректности конфигурации: удержание не может съесть ставку целиком —
RetentionPercentagePoints < CommissionPercentприменённого правила; нарушение при создании начисления → DLQREWARDS_PLATFORM_RETENTION_EXCEEDS_RULE(ручной разбор). - Многомерная scoped+time-bounded модель условий удержания (измерения
Developer→Project→LayoutGroupc периодамиEffectiveFrom/EffectiveToи акциями, резолюция «наиболее специфичная активная наasOf = CompletedAt») вынесена в ADR-0068; настоящее решение фиксирует базовый инвариант удержания (пункты цены, снапшот, guard), поверх которого ADR-0068 добавляет измерения специфичности additive.
Сквозной пример: цена сделки 10 000 000 ₽, ставка застройщика 10% → gross-комиссия 1 000 000 ₽.
Удержание ПИН 1.00 п.п. → PlatformFeeAmount = 10 000 000 × 1 / 100 = 100 000 ₽; пул агентской стороны —
900 000 ₽, то есть 9% от цены сделки. Внутри агентства пул делит политика с per-agent оверрайдами:
агенту А настроена доля 3% от цены (300 000 ₽ агенту, 600 000 ₽ — удержание агентства), агенту Б — 5%
(500 000 ₽ агенту, 400 000 ₽ агентству). Доля ПИН при этом одинакова и не зависит от внутренних настроек
агентства.
2. Распределение начисления — три строки: Platform + Agency + Agent
- Enum
CommissionRecipientTypeрасширяется значениемPlatform(строка удержания ПИН,RecipientUserId = null— получатель не персона, а платформа). - При подтверждении начисления строится ровно три строки распределения:
Platform(=PlatformFeeAmount),Agency(удержание агентства из пула),Agent(остаток пула). Инварианты: по одной строке каждого типа; Σ строк =CommissionAmount(инварианты INV-5/INV-9 начисления переоформляются с «двух строк» на «три»). - Строка
Platformнеприкосновенна для агентства: per-deal корректировка распределения руководителем агентства перераспределяет толькоAgency/Agentвнутри пула.
3. Реферальные ставки — сущность rewards, 4 уровня специфичности
- Ставки живут в
rewards(владелец денег), а не вreferral:referralвладеет только фактом связи «кто кого привёл» и денег не считает (его README, решение №7). - Вводится сущность
ReferralRateRule:PartnerUserId?,DeveloperCompanyId?(обеnull= глобальная ставка),Percent— процент отFinalPrice(конечной цены) сделки,EffectiveFrom/EffectiveTo?— сроки действия (в т.ч. временно повышенные ставки на период акции), статусыDraft/Active/Archived. - Резолюция «специфичное побеждает»: Partner×Developer > Partner > Developer > Global. Внутри одной
специфичности периоды активных правил не пересекаются — проверка при активации
(
REWARDS_REFERRAL_RATE_OVERLAP), поэтому резолв на момент сделки всегда детерминирован. - Нет подошедшего правила на момент завершения сделки → реферальное начисление не создаётся (warn-метрика; глобальная ставка выступает страховочным уровнем).
4. Реферальное начисление — отдельная сущность ReferralAccrual
- Реферальное вознаграждение — отдельная сущность, а не kind-дискриминатор на
CommissionAccrual: другой получатель (партнёр-пользователь, не агент/агентство), нет распределения по политике агентства, собственный жизненный цикл. - Ключевые поля:
DealId(unique — одно реферальное начисление на сделку),BondId(основание — связь изreferral),PartnerUserId,CommissionAccrualId?(родительское агентское начисление), снапшот применённой ставки,Amount = FinalPrice × Percent / 100. - Статусы:
Pending → Approved → Paid | Cancelled.Approvedпроставляется автоматически при переходе родительского начисления вMoneyReceived(гейт «ПИН подтвердил получение денег», ADR-0067);Paid— по платёжному поручению видаReferralReward(tenant — ПИН);Cancelled— отмена сделки / отзыв связи / ручная коррекция. - Guard самореферала: если реферер связи совпадает с агентом-исполнителем сделки
(
ReferrerUserId == AgentUserId) — начисление не создаётся (skip + метрика), самому себе партнёрка не платится. - Основание — реплика связей:
rewardsконсюмитpin.referral.bonds.v1(закрывается задекларированный, но отсутствовавший consumer); закрытие связи (bond-closed) прекращает создание новых начислений, уже созданные остаются.
5. Управление — платформенный scope
- Условиями платформы и реферальными ставками управляет Менеджер ПИН (платформенный scope); в
rewardsвводится новая API-областьplatform(CRUD/activate/archive условий и ставок, реестр реферальных начислений). - Permissions:
rewards.platform-terms.view/rewards.platform-terms.manage,rewards.referral-rates.manage; партнёр видит свои реферальные начисления поrewards.referral.view-own(record-level поPartnerUserId).
Область Действия
| Область | Входит | Не входит |
|---|---|---|
| Сервисы | rewards (условия/ставки/начисления/API platform), referral (только ссылки на rewards-артефакты вместо деклараций), wallet (счета ReferralCommission/PlatformRevenue, ADR-0065), identity (permissions), notifications | crm (статусы/операции сделки не меняются), chessboard, catalog |
| Контракты | CommissionRecipientType + Platform; новый топик pin.rewards.referral-accruals.v1; consumer pin.referral.bonds.v1 в rewards | контракт pin.referral.bonds.v1 не меняется; scoped-измерения удержания (Developer/Project/LayoutGroup + периоды) — ADR-0068 |
| Данные | PlatformCommissionTerms, ReferralRateRule, ReferralAccrual, реплика связей в rewards; снапшот-поля платформы в начислении | пересчёт исторических начислений; частичные удержания |
| Эксплуатация | warn-метрики fallback-условий и «нет ставки»; DLQ REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE | банковская интеграция выплат (решение №9 ТЗ) |
Последствия
| Контекст | Последствие |
|---|---|
rewards | +3 агрегата (PlatformCommissionTerms, ReferralRateRule, ReferralAccrual) и реплика связей; начисление получает снапшот удержания и три строки распределения (INV-5/INV-9 переоформляются); вход политики агентства — пул, не gross; платёжные поручения получают вид ReferralReward; новая API-область platform. |
referral | Ничего не считает по деньгам — в README/событиях декларации заменяются конкретными ссылками на ставки/начисления/consumer в rewards. |
wallet | Кредиты по строке Platform — на счёт PlatformRevenue; по реферальным начислениям — на счёт ReferralCommission партнёра (ADR-0065). |
identity | Каталог permissions пополняется rewards.platform-terms.*, rewards.referral-rates.manage, rewards.referral.view-own. |
notifications | Уведомления партнёру о создании/подтверждении/выплате реферального начисления (топик pin.rewards.referral-accruals.v1). |
Затронутые контракты и совместимость
| Контракт | Версия | Совместимость | Миграция |
|---|---|---|---|
CommissionRecipientType (+Platform) в распределении начисления | 1 | greenfield, in-place (ContractVersion=1) | потребители Distributions обязаны обрабатывать три типа |
pin.rewards.referral-accruals.v1 (created / status-changed) | 1 | новый топик | additive |
pin.referral.bonds.v1 | 1 | не меняется | +новый потребитель rewards (реплика связей) |
pin.rewards.commission-money-received.v1 (ADR-0067) | 1 | новый топик | несёт Distributions[] уже с Platform-строкой |
Альтернативы (рассмотрено и отклонено)
| Вариант | Описание | Причина отклонения |
|---|---|---|
| Удержание ПИН — процент от суммы комиссии | PlatformFee = CommissionAmount × n% | Отклонено решением владельца: модель монетизации сформулирована в пунктах ставки — доля ПИН фиксирована относительно цены сделки и не плавает вслед за ставкой застройщика; «1 п.п. из 10%» прозрачно объясняется агентствам. |
| Договорная ставка агентства, остаток — ПИН | Агентству гарантируется x% от цены, всё сверх x% уходит платформе | Отклонено решением владельца: остаточная модель непрозрачна (доход ПИН скачет от правила к правилу), требует пер-агентских переговоров по каждому застройщику и стимулирует конфликты вокруг ставок. |
Реферальные ставки в контексте referral | referral хранит ставки и считает суммы вознаграждений | Отклонено: referral не считает деньги (его собственная граница, README решение №7) — появилась бы вторая денежная зона истины, дублирование Money-правил/валютных инвариантов и связка ставок с политиками/гейтом выплат через два контекста. |
Kind-дискриминатор на CommissionAccrual | Реферальное начисление — та же сущность с Kind = Referral | Отклонено: ломает инварианты и индексы — unique DealId уже занят агентским начислением; инварианты распределения (три строки, политика агентства) и confirm-актор (застройщик) к рефералке неприменимы; статус-машина обросла бы условными ветками. Отдельная сущность проще и честнее. |
Риски
| Риск | Митигация |
|---|---|
| Fallback 0 п.п.: платформа молча теряет доход, пока условия не заведены | warn-метрика rewards_platform_terms_missing_total + алерт; глобальный дефолт заводится первым шагом внедрения |
| Ошибка конфигурации: удержание ≥ ставки правила застройщика (пул ≤ 0) | guard при создании начисления → DLQ REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE, ручной разбор Менеджером ПИН |
| Гонка активации двух ставок одной специфичности с пересекающимися периодами | проверка непересечения при Activate под блокировкой + REWARDS_REFERRAL_RATE_OVERLAP |
Самореферал через вторую учётку (guard ловит только прямое совпадение UserId) | осознанное ограничение v1; admin-коррекции связей (rebind/revoke в referral) остаются инструментом фрод-разбора |
| Ретро-снапшот: смена условий/ставок ожидаемо не влияет на созданные начисления | зафиксировано в модели (снапшот на DealCompletedAt); корректировки — только явными операциями ADR-0067 |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (4): Entity / Aggregate: PlatformCommissionTermsEntity, Entity / Aggregate: PlatformReceiptEntity, Entity / Aggregate: ReferralAccrualEntity, Entity / Aggregate: ReferralRateRuleEntity
- API (8): GET /api/v1/platform/referral-accruals — Реестр реферальных начислений, POST /api/v1/platform/commission-accruals/{accrualId}/adjust — Корректировка суммы начисления (gross), POST /api/v1/platform/commission-terms — Создание условий платформенной комиссии (черновик), POST /api/v1/platform/commission-terms/{termsId}/activate — Активация условий платформенной комиссии, POST /api/v1/platform/referral-accruals/{accrualId}/adjust — Корректировка суммы реферального начисления, POST /api/v1/platform/referral-accruals/{accrualId}/cancel — Ручная отмена реферального начисления, POST /api/v1/platform/referral-rate-rules — Создание ставки реферального вознаграждения (черновик), POST /api/v1/platform/referral-rate-rules/{rateRuleId}/activate — Активация ставки реферального вознаграждения
- Use Cases (6): UC-RWD-007. Платформа настраивает условия удержания ПИН, UC-RWD-008. Платформа настраивает ставки реферального вознаграждения, UC-RWD-011. Корректировки начисления комиссии, UC-RWD-013. Агент предлагает корректировку своей доли, руководитель одобряет, UC-RWD-014. ПИН корректирует размер реферального вознаграждения партнёра, UC-RWD-015. Настройка индивидуальных условий вознаграждения по области и сроку (акции и boost)
- События (2): Event Contract: pin.rewards.commission-distribution-adjusted (CommissionDistributionAdjustedKafkaEvent), Event Contract: pin.rewards.commission-money-received (CommissionMoneyReceivedKafkaEvent)
- Консюмеры (2): Consumer: CRM Deal Completed, Consumer: Referral Bond Sync (pin-rewards-referral-bonds)
- Саги (1): Saga «Commission Settlement» (финансовый контур вознаграждений)
- adr (4): ADR-0065 — контекст wallet (внутренние балансы и ledger), ADR-0067 — гейт получения денег (акт поступления средств), ADR-0068 — Scoped и time-bounded конфигурация комиссионных и реферальных ставок, ADR-0069: Co-broking — расщепление комиссии между несколькими агентами (дизайн, v-next)
- scenarios (1): UC-OVR-010. От завершённой сделки до выплаты комиссии и реферального вознаграждения
- index.md (1): Rewards Service
Связанные документы
rewards: commission-accrual.md (три строки, снапшот удержания), agency-commission-policy.md (вход — пул), reward-rule.md (ставка застройщика,CalculationBase), payout-order.md (видReferralReward).referral: README.md, domain/referral-bond.md, events/bond-created.md, events/bond-closed.md.- ADR: ADR-0005 (леджер),
ADR-0048 (денежная арифметика, RUB-only),
ADR-0052 (tenancy),
ADR-0056 (simplicity-first),
ADR-0065 (кошелёк — счета получателей),
ADR-0067 (гейт
MoneyReceived— условиеApprovedрефералки и всех выплат), ADR-0068 (многомерная scoped+time-bounded модель ставок и условий удержания:Developer/Project/LayoutGroup+ периоды).