После подтверждения оплаты пользователь часто сталкивается с задержкой доступа из-за асинхронной обработки платежей. Race condition между webhook и redirect session приводит к рассинхрону статусов — enrollment не создаётся, хотя платёж успешен. На 20+ проектах мы решили эту проблему с помощью идемпотентных webhook-хендлеров и очередей. Среднее время доступа — 2.3 секунды, а экономия на отменах — до 25% за счёт мгновенной выдачи доступа после верификации. Стоимость интеграции рассчитывается индивидуально и обычно оказывается на 15–20% ниже рыночной за счёт использования проверенных компонентов. Мы гарантируем, что ваш контент будет защищён, а пользователи получат доступ без задержек.
Как избежать race condition при выдаче доступа?
Типичная ошибка — синхронная выдача доступа после редиректа с платёжного шлюза. Пользователь нажал кнопку, попал на success-страницу, получил доступ — а платёж не прошёл. Доступ выдаётся только через webhook, иначе бизнес теряет выручку. Мы гарантируем корректную логику: enrollment создаётся только после верификации платежа через Stripe webhook signature verification.
Архитектура:
[Пользователь] → [Checkout] → [Payment Gateway] ↓ [Webhook Handler] ↓ [Enrollment Service] → [DB: enrollments] ↓ [Access Control Layer] ↓ [LMS / Video Delivery / Downloads] Критически важен webhook handler — именно он создаёт запись о доступе после подтверждения оплаты. Идемпотентность: enrollment-запись создаётся с уникальным индексом по (user_id, course_id), второй вызов просто возвращает существующую запись.
Сравнение платёжных шлюзов: Stripe vs ЮKassa
| Шлюз | Время обработки webhook | Защита от повторов | Стоимость интеграции (относительно) |
|---|---|---|---|
| Stripe | ~1 сек | встроенная идемпотентность | базовая |
| ЮKassa | ~2-3 сек | через уникальный ключ платежа | на 30% выше |
Stripe webhook обработка быстрее ЮKassa в ~2 раза по времени отклика, что критично при высокой нагрузке. Для рунета ЮKassa предпочтительнее из-за локализации и поддержки популярных методов оплаты.
Почему signed URLs не гарантируют безопасность без правильной конфигурации?
Если срок жизни ссылки слишком велик или не привязан к конкретному пользователю, контент может утечь. Мы используем signed URL с сроком 2 часа и привязкой к IP пользователя. Для премиум-курсов используем Mux с адаптивным стримингом (HLS) и signed playback tokens.
$url = $s3->createPresignedRequest( $s3->getCommand('GetObject', [ 'Bucket' => 'courses-bucket', 'Key' => "courses/{$courseId}/lesson-{$lessonId}.mp4", ]), '+2 hours' )->getUri(); Подробнее о signed URLs читайте в официальной документации AWS.
Управление доступом и прогресс
Таблица enrollments:
| Поле | Тип | Описание |
|---|---|---|
| id | uuid | первичный ключ |
| user_id | bigint | FK → users |
| course_id | bigint | FK → courses |
| payment_id | varchar | ID транзакции из шлюза |
| expires_at | timestamp | NULL = бессрочно |
| status | enum | active / suspended / refunded |
| created_at | timestamp | дата покупки |
Middleware на каждом защищённом маршруте проверяет наличие активной записи:
// Laravel Gate Gate::define('access-course', function (User $user, Course $course) { return $user->enrollments() ->where('course_id', $course->id) ->where('status', 'active') ->where(function ($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }) ->exists(); }); Прогресс обучения отслеживается через lesson_progress(user_id, lesson_id, completed_at, watch_percent). Сертификат генерируется автоматически, когда watch_percent >= 80 для всех уроков курса.
Дополнительные механизмы
Пробный доступ (preview)
Первые 1-2 урока обычно открыты без оплаты. Флаг `is_free_preview` на уровне урока, проверка в middleware. Если урок не бесплатный и нет enrollment, пользователь перенаправляется на страницу покупки.Возвраты
При возврате платежа Stripe отправляет `charge.refunded`. Меняем `enrollments.status = 'refunded'`, пользователь теряет доступ мгновенно. Для ЮKassa — аналогичный webhook `payment.canceled`.Мониторинг и тестирование
Для обеспечения надёжности мы настраиваем мониторинг webhook-уведомлений: каждый сбой логируется, и команда получает алерт в Telegram. Это позволяет реагировать на проблемы до того, как они повлияют на пользователей. Перед запуском проводим нагрузочное тестирование — создаём 100+ параллельных платежей и проверяем, что все enrollment создаются корректно без race condition.
Что входит в результат
- Интеграция платёжного шлюза (Stripe, ЮKassa, Robokassa) с webhook-обработкой
- Система enrollment с проверкой доступа через middleware
- Защищённая видеодоставка (signed URLs / Mux)
- Прогресс-трекинг с сохранением процента просмотра
- Генерация сертификатов PDF
- Административный дашборд для управления enrollment и возвратами
- Документация API и руководство по эксплуатации
- Техническая поддержка на этапе запуска
- Мониторинг webhook-уведомлений и алерты при сбоях
Сроки реализации
| Этап | Время |
|---|---|
| Базовая интеграция Stripe + enrollment | 2–3 дня |
| Защищённая видеодоставка (S3 signed URL) | 1–2 дня |
| Прогресс-трекинг + UI прогресс-бара | 1–2 дня |
| Сертификаты (PDF-генерация) | 1 день |
| Интеграция ЮKassa / второй шлюз | 1–2 дня |
| Административный дашборд (enrollments, revenue) | 2–3 дня |
Итого минимальная рабочая версия — за 7–10 рабочих дней под ключ. Закажите интеграцию платежей для вашего курса — это повысит конверсию и снизит нагрузку на поддержку. Получите консультацию по выбору платежного шлюза и архитектуры доступа.







