Реализация продажи онлайн-курсов (доступ после оплаты) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация продажи онлайн-курсов (доступ после оплаты) на сайте
Сложная
~1-2 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация продажи онлайн-курсов (доступ после оплаты) на сайте

Платформа продажи курсов — это не «добавить товар в WooCommerce». Это связка из платёжного шлюза, системы управления доступом, видеохостинга и прогресс-трекинга. Каждый слой имеет собственную логику отказа, и ошибка в любом из них означает либо потерю денег, либо утечку контента к неоплатившим пользователям.

Архитектурный скелет

Типовая схема выглядит так:

[Пользователь] → [Checkout] → [Payment Gateway]
                                      ↓
                              [Webhook Handler]
                                      ↓
                         [Enrollment Service] → [DB: enrollments]
                                      ↓
                         [Access Control Layer]
                                      ↓
                    [LMS / Video Delivery / Downloads]

Критически важен webhook handler — именно он создаёт запись о доступе после подтверждения оплаты от шлюза. Попытка выдать доступ синхронно в момент редиректа после оплаты — классическая ошибка, приводящая к гонкам (race conditions) при сбоях сети.

Платёжные шлюзы и интеграция

Stripe — предпочтительный вариант для международных платежей. Используем stripe.checkout.sessions.create с mode: 'payment' или mode: 'subscription' для подписочной модели.

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{
    price_data: {
      currency: 'usd',
      product_data: { name: course.title },
      unit_amount: course.price_cents,
    },
    quantity: 1,
  }],
  metadata: { course_id: course.id, user_id: user.id },
  success_url: `${BASE_URL}/courses/${course.slug}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${BASE_URL}/courses/${course.slug}`,
});

После оплаты Stripe отправляет событие checkout.session.completed на webhook-эндпоинт. Там валидируем подпись (stripe.webhooks.constructEvent), извлекаем metadata.course_id и metadata.user_id, создаём enrollment.

Для рунета подключаем ЮKassa (YooKassa) или Robokassa. Оба работают через аналогичную схему: уведомление о платеже → проверка подписи → выдача доступа.

Idempotency: webhook может прийти дважды. Enrollment-запись создаётся с уникальным индексом по (user_id, course_id) или по payment_id, второй вызов просто возвращает существующую запись.

Управление доступом

Таблица 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();
});

Видеодоставка и защита контента

Прямые ссылки на видео нельзя давать пользователям — они расшарятся. Варианты:

Signed URLs (AWS S3 / CloudFront):

$url = $s3->createPresignedRequest(
    $s3->getCommand('GetObject', [
        'Bucket' => 'courses-bucket',
        'Key'    => "courses/{$courseId}/lesson-{$lessonId}.mp4",
    ]),
    '+2 hours'
)->getUri();

Ссылка живёт 2 часа и привязана к конкретному файлу. После истечения — 403.

Vimeo Private / Mux: для продакшена с большим трафиком лучше использовать специализированные видеоплатформы. Mux предоставляет адаптивный стриминг (HLS), аналитику просмотров и защиту через signed playback tokens:

const playbackId = lesson.mux_playback_id;
const token = await signMuxToken(playbackId, 'video', {
  expiration: '2h',
  params: { user_id: userId },
});
const src = `https://stream.mux.com/${playbackId}.m3u8?token=${token}`;

Для PDF/EPUB — аналогично: генерируем signed URL на скачивание, логируем каждую загрузку в content_access_logs.

Прогресс и сертификаты

Прогресс по урокам хранится в lesson_progress(user_id, lesson_id, completed_at, watch_percent). Фронтенд отправляет события завершения:

// Каждые 30 секунд или при pause/end
videoPlayer.on('timeupdate', debounce(() => {
  api.post('/progress', {
    lesson_id: lessonId,
    watch_percent: Math.round((player.currentTime / player.duration) * 100),
  });
}, 5000));

Сертификат генерируется автоматически, когда watch_percent >= 80 для всех уроков курса. Для генерации PDF используем puppeteer (рендер HTML-шаблона) или PDFKit для простых случаев.

Пробный доступ (preview)

Первые 1-2 урока обычно открыты без оплаты. Флаг is_free_preview на уровне урока, проверка в middleware:

if ($lesson->is_free_preview || Gate::allows('access-course', $course)) {
    return $next($request);
}
return redirect()->route('course.buy', $course);

Возвраты

При возврате платежа Stripe отправляет charge.refunded. Меняем enrollments.status = 'refunded', пользователь теряет доступ мгновенно. Для ЮKassa — аналогичный webhook payment.canceled.

Сроки реализации

Этап Время
Базовая интеграция Stripe + enrollment 2–3 дня
Защищённая видеодоставка (S3 signed URL) 1–2 дня
Прогресс-трекинг + UI прогресс-бара 1–2 дня
Сертификаты (PDF-генерация) 1 день
Интеграция ЮKassa / второй шлюз 1–2 дня
Административный дашборд (enrollments, revenue) 2–3 дня

Итого минимальная рабочая версия — 7–10 рабочих дней.

Типичные ошибки

Выдача доступа до подтверждения от шлюза. Пользователь нажал кнопку оплаты → попал на success-страницу → получил доступ. Платёж при этом мог не пройти. Доступ выдаётся только через webhook.

Хранение видео на том же сервере что и приложение. Bandwidth убьёт сервер при 50+ одновременных просмотрах. Видео — только в объектном хранилище или у специализированного провайдера.

Отсутствие логов доступа. При спорной ситуации с пользователем («я заплатил, доступ не дали») нет возможности восстановить цепочку событий. Логировать payment_id, webhook timestamp, enrollment_created_at обязательно.