Модуль MLM (Партнеры, Рефералы, Комиссии, Ранги)
Обзор
Модуль MLM (Multi-Level Marketing) является основным бизнес-движком платформы IWM. Он управляет партнерской сетью, отслеживанием рефералов, расчетом комиссий, продвижением по рангам и обработкой выплат. Этот модуль реализует унилевел-структуру компенсации, где партнеры зарабатывают комиссии от своих прямых рефералов и нижестоящей сети.
Зоны ответственности
- Регистрация партнеров и управление жизненным циклом
- Генерация реферальных ссылок и отслеживание атрибуции
- Управление структурой сетевого дерева (closure table)
- Квалификация и продвижение по рангам
- Расчет и распределение комиссий
- Управление балансом (ожидающий, доступный)
- Обработка запросов на выплату
Доменные сущности
Partner
Центральная сущность, представляющая участника MLM сети.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор партнера |
| user_id | UUID | Ссылка на core.users |
| sponsor_id | UUID | Прямой спонсор (вышестоящий партнер) |
| referral_code | VARCHAR(20) | Уникальный реферальный код |
| status | ENUM | Статус партнера |
| current_rank_id | UUID | Текущий ранг |
| highest_rank_id | UUID | Наивысший достигнутый ранг |
| direct_referrals_count | INT | Количество прямых рефералов |
| total_network_size | INT | Общий размер сети |
| tree_depth | INT | Глубина в дереве от корня |
| joined_at | TIMESTAMP | Время регистрации |
| activated_at | TIMESTAMP | Первое квалификационное действие |
PartnerTreePath
Closure table для эффективных запросов к дереву.
| Поле | Тип | Описание |
|---|---|---|
| ancestor_id | UUID | Вышестоящий партнер |
| descendant_id | UUID | Нижестоящий партнер |
| depth | INT | Расстояние между узлами |
ReferralLink
Отслеживаемые реферальные ссылки для атрибуции.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор ссылки |
| partner_id | UUID | Партнер-владелец |
| code | VARCHAR(20) | Уникальный код ссылки |
| name | VARCHAR(100) | Название ссылки (Instagram, YouTube) |
| target_url | VARCHAR(500) | Целевая страница |
| utm_source | VARCHAR(100) | UTM source параметр |
| utm_medium | VARCHAR(100) | UTM medium параметр |
| utm_campaign | VARCHAR(100) | UTM campaign параметр |
| clicks_count | INT | Всего кликов |
| registrations_count | INT | Регистраций по ссылке |
| conversions_count | INT | Конверсий (покупки/инвестиции) |
| is_active | BOOLEAN | Статус активности ссылки |
| expires_at | TIMESTAMP | Опциональное время истечения |
ReferralAttribution
Отслеживает источник реферала для пользователей.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор атрибуции |
| user_id | UUID | Приглашенный пользователь |
| partner_id | UUID | Приглашающий партнер |
| link_id | UUID | Исходная ссылка (опционально) |
| attribution_type | ENUM | FIRST_TOUCH или LAST_TOUCH |
| first_touch_at | TIMESTAMP | Первое взаимодействие |
| last_touch_at | TIMESTAMP | Последнее взаимодействие |
| converted_at | TIMESTAMP | Время конверсии |
| cookie_id | VARCHAR(100) | ID отслеживающей cookie |
Rank
Определения рангов MLM.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор ранга |
| name | VARCHAR(100) | Отображаемое название |
| code | VARCHAR(50) | Уникальный код |
| level | INT | Уровень ранга (1 = низший) |
| description | TEXT | Описание ранга |
| badge_url | VARCHAR(500) | Изображение значка ранга |
| color_code | VARCHAR(20) | Цвет бренда |
| is_active | BOOLEAN | Ранг доступен |
CommissionPlan
Правила расчета комиссий.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор плана |
| name | VARCHAR(100) | Название плана |
| code | VARCHAR(50) | Уникальный код |
| source_type | ENUM | INVESTMENT, PRODUCT, ALL |
| max_levels | INT | Максимальная глубина для комиссий |
| is_active | BOOLEAN | План активен |
| valid_from | TIMESTAMP | Начало действия |
| valid_to | TIMESTAMP | Окончание действия |
CommissionTransaction
Индивидуальная запись о комиссии.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор транзакции |
| partner_id | UUID | Получающий партнер |
| source_type | ENUM | ORDER, INVESTMENT, BONUS, RANK_BONUS |
| source_id | UUID | ID сущности-источника |
| source_partner_id | UUID | Партнер, сгенерировавший комиссию |
| level_depth | INT | Уровень сети |
| plan_id | UUID | Примененный план комиссий |
| gross_amount | DECIMAL | Сумма до вычетов |
| net_amount | DECIMAL | Итоговая сумма |
| career_points | DECIMAL | Заработанные очки |
| status | ENUM | Статус транзакции |
| period_id | VARCHAR(20) | Учетный период |
| idempotency_key | VARCHAR(255) | Предотвращение дубликатов |
PartnerBalance
Финансовое состояние партнера.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор баланса |
| partner_id | UUID | Ссылка на партнера |
| available_balance | DECIMAL | Сумма для вывода |
| pending_balance | DECIMAL | Ожидает одобрения |
| total_earned | DECIMAL | Заработано за все время |
| total_withdrawn | DECIMAL | Выведено за все время |
| career_points_total | DECIMAL | Очки за все время |
| career_points_period | DECIMAL | Очки текущего периода |
| currency | VARCHAR(3) | Валюта баланса |
| version | INT | Версия для оптимистичной блокировки |
PayoutRequest
Запрос на вывод средств от партнера.
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор запроса |
| partner_id | UUID | Запрашивающий партнер |
| amount | DECIMAL | Запрошенная сумма |
| currency | VARCHAR(3) | Код валюты |
| payout_method_type | ENUM | BANK_CARD, BANK_TRANSFER, EWALLET |
| payout_details | JSONB | Зашифрованная платежная информация |
| status | ENUM | Статус запроса |
| rejection_reason | TEXT | Причина отклонения |
| processed_by | UUID | Администратор, обработавший запрос |
| external_reference | VARCHAR(255) | Ссылка платежного провайдера |
Жизненный цикл партнера
Процесс регистрации
Поток статусов партнера
Критерии активации
Партнер активируется (PENDING -> ACTIVE) при:
- Первой покупке продукта (минимальная сумма заказа)
- Первом участии в инвестиции
- Ручной активации администратором
Реферальная система
Генерация ссылок
Партнеры могут создавать множество реферальных ссылок для разных каналов:
Отслеживание UTM параметров
Ссылки поддерживают полное UTM отслеживание:
| Параметр | Пример | Назначение |
|---|---|---|
| utm_source | Источник трафика | |
| utm_medium | social | Маркетинговый канал |
| utm_campaign | summer_2024 | Идентификатор кампании |
Атрибуция на основе cookie
Правила атрибуции:
- Время жизни cookie: 30 дней
- Тип атрибуции: Last-touch (побеждает последний реферер)
- Переопределение: Новый клик по реферальной ссылке обновляет атрибуцию
- Сохранение: Атрибуция фиксируется при конверсии (покупка/инвестиция)
Структура дерева
Паттерн Closure Table
Партнерская сеть использует closure table для эффективных иерархических запросов:
Ключевые запросы
Получить прямых рефералов (Уровень 1):
SELECT descendant_id FROM partner_tree_paths
WHERE ancestor_id = :partner_id AND depth = 1Получить всю нижестоящую сеть:
SELECT descendant_id, depth FROM partner_tree_paths
WHERE ancestor_id = :partner_id AND depth > 0
ORDER BY depthПолучить вышестоящую цепочку:
SELECT ancestor_id, depth FROM partner_tree_paths
WHERE descendant_id = :partner_id AND depth > 0
ORDER BY depthПолучить сеть на определенных уровнях (для комиссий):
SELECT descendant_id FROM partner_tree_paths
WHERE ancestor_id = :partner_id AND depth BETWEEN 1 AND 10Система рангов
Иерархия рангов
Правила квалификации
Ранги имеют несколько типов требований:
| Тип требования | Описание |
|---|---|
| CAREER_POINTS_TOTAL | Карьерные очки за все время |
| CAREER_POINTS_PERIOD | Очки в текущем периоде |
| PERSONAL_VOLUME | Личные покупки/инвестиции |
| GROUP_VOLUME | Общий объем сети |
| DIRECT_REFERRALS | Количество прямых рефералов |
| ACTIVE_REFERRALS | Активные прямые рефералы |
| TEAM_SIZE | Общий размер сети |
| REFERRALS_AT_RANK | Прямые рефералы определенного ранга |
Пример квалификации (Ранг Gold)
| Требование | Порог | Период |
|---|---|---|
| Карьерные очки | 50,000 | LIFETIME |
| Личный объем | 10,000 RUB | MONTHLY |
| Групповой объем | 100,000 RUB | MONTHLY |
| Прямые рефералы | 10 | LIFETIME |
| Активные рефералы | 5 | MONTHLY |
Поддержание ранга
- Ранги необходимо поддерживать ежемесячно
- Невыполнение требований приводит к понижению
- Наивысший достигнутый ранг сохраняется для расчета бонусов
- Льготный период: предупреждение за 1 месяц до понижения
Преимущества рангов
| Ранг | Бонус к комиссии | Эксклюзивные преимущества |
|---|---|---|
| Bronze | +2% на уровнях 1-3 | Доступ к базовому обучению |
| Silver | +3% на уровнях 1-5 | Ежемесячные вебинары |
| Gold | +5% на всех уровнях | Квартальные мероприятия |
| Platinum | +7% на всех уровнях | Ежегодный ретрит |
| Diamond | +10% на всех уровнях | Совет лидеров |
Расчет комиссий
Унилевел модель
Комиссии распределяются вверх по сети от партнера-источника:
Уровни комиссий
| Уровень | Базовый процент | С рангом Gold |
|---|---|---|
| 1 | 5% | 10% |
| 2 | 3% | 6% |
| 3 | 2% | 4% |
| 4 | 1% | 2% |
| 5-10 | 0.5% | 1% |
Процесс расчета
Триггеры комиссий
| Триггер | Тип источника | Описание |
|---|---|---|
| Order Completed | ORDER | Покупка продукта доставлена |
| Investment Activated | INVESTMENT | Инвестиция профинансирована |
| Rank Achievement | RANK_BONUS | Единовременный бонус за повышение ранга |
| Period Bonus | BONUS | Месячный/квартальный бонус за результаты |
Поток статусов комиссии
Управление балансом
Типы баланса
Ожидающий vs Доступный
| Баланс | Описание | Можно вывести |
|---|---|---|
| Pending | Комиссии, ожидающие одобрения | Нет |
| Available | Одобрено, доступно для вывода | Да |
Заморозки
Комиссии могут быть заморожены для:
- Расследования мошенничества
- Разрешения споров
- Проверки соответствия
Безопасность параллелизма
Операции с балансом используют оптимистичную блокировку с номерами версий:
// Example: Withdraw from balance
const result = await updateBalanceOptimistic(
partnerId,
amount,
'WITHDRAW',
expectedVersion
);
if (!result.success) {
if (result.error_message.includes('Version mismatch')) {
// Retry with fresh version
}
}Система выплат
Процесс запроса выплаты
Правила выплат
- Минимальная сумма: Минимальный вывод 100 RUB
- Один ожидающий: Только один ожидающий запрос на партнера
- Требуется KYC: Требуется уровень STANDARD KYC
- Списание баланса: Сумма списывается сразу при запросе
- Возврат при ошибке: Баланс восстанавливается при неудачном платеже
Методы выплат
| Метод | Провайдер | Время обработки |
|---|---|---|
| Bank Card | Stripe/YooKassa | 1-3 рабочих дня |
| Bank Transfer | Вручную | 3-5 рабочих дней |
| E-Wallet | Различные | В тот же день |
Рабочий процесс одобрения
Публикуемые события
| Событие | Триггер | Payload |
|---|---|---|
| PartnerRegistered | Создан новый партнер | partnerId, userId, sponsorId |
| PartnerActivated | Первое квалификационное действие | partnerId, activationType |
| PartnerStatusChanged | Переход статуса | partnerId, fromStatus, toStatus |
| ReferralLinkCreated | Создана новая ссылка | linkId, partnerId, code |
| ReferralLinkClicked | Переход по ссылке | linkId, partnerId, visitorInfo |
| ReferralAttributed | Пользователь привязан к партнеру | userId, partnerId, linkId |
| CommissionCalculated | Создана комиссия | transactionId, partnerId, amount |
| CommissionApproved | Комиссия одобрена | transactionId, partnerId, amount |
| CommissionPaid | Комиссия переведена в доступный баланс | transactionId, partnerId, amount |
| CommissionReversed | Комиссия отменена | transactionId, partnerId, reason |
| BalanceUpdated | Изменение баланса | partnerId, type, amount, newBalance |
| PayoutRequested | Подан запрос на выплату | payoutId, partnerId, amount |
| PayoutApproved | Выплата одобрена | payoutId, partnerId |
| PayoutCompleted | Выплата обработана | payoutId, partnerId, reference |
| PayoutRejected | Выплата отклонена | payoutId, partnerId, reason |
| RankChanged | Повышение/понижение ранга | partnerId, fromRank, toRank, type |
| RankAchieved | Новый наивысший ранг | partnerId, rankId, isFirst |
Потребляемые события
| Событие | Источник | Обработчик |
|---|---|---|
| UserRegistered | Core | Создать партнера, если есть реферальный код |
| OrderCompleted | Commerce | Рассчитать комиссии |
| OrderRefunded | Commerce | Отменить комиссии |
| InvestmentActivated | Investment | Рассчитать комиссии |
| InvestmentWithdrawn | Investment | Обработать штрафы за досрочный вывод |
Бизнес-правила и инварианты
Правила партнеров
- Один партнер на пользователя (связь 1:1)
- Партнер не может быть своим спонсором
- Спонсор не может быть изменен после регистрации
- Только партнеры со статусом ACTIVE получают комиссии
- Партнеры со статусом TERMINATED не могут быть реактивированы
Правила комиссий
- Комиссии рассчитываются только для вышестоящих партнеров со статусом ACTIVE
- Максимальная глубина комиссий: 10 уровней
- Ключ идемпотентности предотвращает дублирование комиссий
- Комиссии требуют одобрения администратора (настраиваемый порог)
- Отмена/возврат источника отменяет все связанные комиссии
Правила баланса
- Доступный баланс не может быть отрицательным
- Ожидающий баланс не может быть отрицательным
- Версия должна совпадать для обновления (оптимистичная блокировка)
- Всего заработано >= всего выведено + доступный + ожидающий
Правила выплат
- Только один ожидающий/обрабатываемый запрос на партнера
- Минимальная сумма вывода обязательна
- Требуется верификация KYC
- Партнер должен быть в статусе ACTIVE для запроса выплаты
Точки интеграции
Предоставляет другим модулям
| Интерфейс | Потребители | Назначение |
|---|---|---|
| IPartnerLookupService | Commerce, Investment | Получить партнера по реферальному коду |
| ICommissionService | Commerce, Investment | Запустить расчет комиссий |
| IAttributionService | Commerce, Investment | Получить реферальную атрибуцию для пользователя |
Потребляет от других модулей
| Интерфейс | Провайдер | Назначение |
|---|---|---|
| IUserLookupService | Core | Получить данные профиля пользователя |
| IKycStatusService | Core | Проверить уровень KYC для выплат |