161 lines
8.4 KiB
Markdown
161 lines
8.4 KiB
Markdown
# Trial Choice & Payment Integration
|
||
|
||
## Проблемы которые решены
|
||
|
||
### 1. Связь выбора Trial Choice → Payment
|
||
**Проблема**: Пользователь выбирает вариант триала на экране TrialChoice, но на экране Payment всегда отображался первый вариант.
|
||
|
||
**Решение**:
|
||
- Создан `TrialVariantSelectionContext` для хранения выбранного `variantId`
|
||
- `TrialChoiceTemplate` сохраняет выбор пользователя в контекст
|
||
- `TrialPaymentTemplate` читает выбранный вариант из контекста и использует его вместо первого
|
||
|
||
### 2. Общая логика загрузки и кеширование
|
||
**Проблема**:
|
||
- Каждый экран (TrialChoice, Payment) делал отдельный запрос к API
|
||
- TrialChoice показывал неправильные данные до завершения загрузки
|
||
- Payment уже имел loader, но TrialChoice — нет
|
||
|
||
**Решение**:
|
||
- Создан `PaymentPlacementProvider` для кеширования загруженных placement данных
|
||
- `usePaymentPlacement` hook теперь использует кеш из контекста
|
||
- Повторные запросы к API не выполняются если данные уже загружены
|
||
- `TrialChoiceTemplate` показывает loader (как Payment) до загрузки данных
|
||
|
||
## Архитектура
|
||
|
||
### Контексты
|
||
|
||
#### PaymentPlacementProvider
|
||
- **Путь**: `src/entities/session/payment/PaymentPlacementProvider.tsx`
|
||
- **Назначение**: Кеширует результаты `loadFunnelPaymentById` чтобы избежать повторных запросов
|
||
- **Ключ кеша**: `${funnelKey}:${paymentId}`
|
||
- **Состояние**: `{ placement, isLoading, error }`
|
||
|
||
#### TrialVariantSelectionProvider
|
||
- **Путь**: `src/entities/session/payment/TrialVariantSelectionContext.tsx`
|
||
- **Назначение**: Хранит выбранный пользователем `variantId` для передачи между экранами
|
||
- **Состояние**: `{ selectedVariantId, setSelectedVariantId }`
|
||
|
||
### Обновленные компоненты
|
||
|
||
#### TrialChoiceTemplate
|
||
- **Изменения**:
|
||
- Добавлен loader (Spinner) до загрузки placement
|
||
- Сохраняет выбор в `TrialVariantSelectionContext`
|
||
- Использует `usePaymentPlacement` с кешированием
|
||
- Инициализирует локальный `selectedId` из `selectedVariantId` контекста
|
||
|
||
#### TrialPaymentTemplate
|
||
- **Изменения**:
|
||
- Читает `selectedVariantId` из `TrialVariantSelectionContext`
|
||
- Использует выбранный вариант, если доступен: `placement?.variants?.find((v) => v.id === selectedVariantId)`
|
||
- Fallback на первый вариант если выбор отсутствует
|
||
- Использует `usePaymentPlacement` с кешированием
|
||
|
||
#### SpecialOfferTemplate
|
||
- **Изменения**: Нет (специально)
|
||
- **Поведение**: Продолжает использовать первый вариант `placement?.variants?.[0]`
|
||
- **Причина**: Использует другой `paymentId` (`"main_secret_discount"`) и не должен зависеть от выбора Trial
|
||
|
||
### usePaymentPlacement Hook
|
||
- **Изменения**:
|
||
- Теперь использует `PaymentPlacementContext` для получения/загрузки данных
|
||
- Упрощена логика: нет локального state, все в контексте
|
||
- Автоматически триггерит загрузку если данных нет
|
||
|
||
### Layout Integration
|
||
- **Файл**: `src/app/[funnelId]/layout.tsx`
|
||
- **Изменения**: Обернут в два новых провайдера:
|
||
```tsx
|
||
<PaymentPlacementProvider>
|
||
<TrialVariantSelectionProvider>
|
||
{children}
|
||
</TrialVariantSelectionProvider>
|
||
</PaymentPlacementProvider>
|
||
```
|
||
|
||
## Потоки данных
|
||
|
||
### Поток 1: Воронка С экраном Trial Choice
|
||
```
|
||
1. User открывает Trial Choice
|
||
→ PaymentPlacementProvider загружает "main" placement (если еще нет в кеше)
|
||
→ TrialChoiceTemplate показывает loader
|
||
→ После загрузки отображаются варианты
|
||
|
||
2. User выбирает вариант (например, id="plan-123")
|
||
→ setSelectedVariantId("plan-123") в TrialVariantSelectionContext
|
||
|
||
3. User переходит на Payment
|
||
→ PaymentPlacementProvider возвращает "main" placement из кеша (без запроса)
|
||
→ TrialPaymentTemplate читает selectedVariantId="plan-123"
|
||
→ Использует вариант с id="plan-123" вместо первого
|
||
```
|
||
|
||
### Поток 2: Воронка БЕЗ экрана Trial Choice
|
||
```
|
||
1. User сразу открывает Payment
|
||
→ PaymentPlacementProvider загружает "main" placement
|
||
→ TrialPaymentTemplate показывает loader
|
||
→ selectedVariantId === null
|
||
→ Использует первый вариант (дефолт)
|
||
```
|
||
|
||
### Поток 3: Special Offer
|
||
```
|
||
1. User открывает Special Offer
|
||
→ PaymentPlacementProvider загружает "main_secret_discount" placement
|
||
→ SpecialOfferTemplate показывает loader
|
||
→ Всегда использует первый вариант (не зависит от Trial Choice)
|
||
```
|
||
|
||
## Тестирование
|
||
|
||
### Сценарий 1: Trial Choice → Payment
|
||
1. Открыть воронку с Trial Choice
|
||
2. Дождаться загрузки вариантов
|
||
3. Выбрать второй вариант (не первый)
|
||
4. Нажать Continue
|
||
5. **Ожидается**: На Payment отображается выбранный (второй) вариант
|
||
|
||
### Сценарий 2: Прямой переход на Payment
|
||
1. Открыть воронку без Trial Choice или перейти прямо на Payment
|
||
2. **Ожидается**: На Payment отображается первый вариант (дефолт)
|
||
|
||
### Сценарий 3: Кеширование
|
||
1. Открыть Trial Choice → загрузка placement
|
||
2. Перейти на Payment
|
||
3. **Ожидается**: Payment загружается мгновенно (из кеша), без повторного API запроса
|
||
|
||
### Сценарий 4: Special Offer
|
||
1. Открыть Special Offer
|
||
2. **Ожидается**: Всегда показывается первый вариант, независимо от выбора в Trial Choice
|
||
|
||
## API изменения (Backend)
|
||
|
||
### session.controller.ts
|
||
- **Изменения**: Добавлены `title` и `accent` поля в варианты
|
||
- **Поведение**: Возвращает все планы (не ограничивает до 4)
|
||
- **Дефолты**: `title` использует `["Basic", "Standard", "Popular", "Premium"]` с fallback `"Premium"`
|
||
|
||
## Файлы созданы/изменены
|
||
|
||
### Новые файлы
|
||
- `src/entities/session/payment/PaymentPlacementProvider.tsx`
|
||
- `src/entities/session/payment/TrialVariantSelectionContext.tsx`
|
||
- `src/entities/session/payment/index.ts`
|
||
|
||
### Измененные файлы
|
||
- `src/hooks/payment/usePaymentPlacement.ts` - использует контекст вместо локального state
|
||
- `src/components/funnel/templates/TrialChoiceTemplate/TrialChoiceTemplate.tsx` - loader + сохранение выбора
|
||
- `src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx` - использует выбранный вариант
|
||
- `src/app/[funnelId]/layout.tsx` - обернут в провайдеры
|
||
|
||
## Будущие улучшения
|
||
|
||
1. **Персистентность**: Сохранять выбор в localStorage/sessionStorage для сохранения при перезагрузке
|
||
2. **Аналитика**: Трекинг выбора вариантов для A/B тестирования
|
||
3. **Валидация**: Проверять что выбранный variant все еще существует в placement
|
||
4. **Типизация**: Усилить типы для garantie что только валидные paymentId используются
|