Реализация ограничения скачиваний (по количеству/времени) для цифровых товаров

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация ограничения скачиваний (по количеству/времени) для цифровых товаров
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

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

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

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

  • 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

Реализация ограничения скачиваний (по количеству/времени) для цифровых товаров

Ограничения на скачивание — не просто защита от нелегального распространения, но и коммерческий инструмент: разные тарифы могут предоставлять разное количество скачиваний или срок доступа. Реализация должна обрабатывать пограничные случаи: одновременные клики, переподключения, закрытый браузер в середине загрузки.

Типы ограничений

Тип Описание Пример
По количеству N скачиваний на одну покупку 3 скачивания
По времени Доступ до определённой даты 30 дней после оплаты
Комбинированный И то, и другое 5 скачиваний или 90 дней
По IP Только с зарегистрированного IP Корпоративные лицензии
По устройствам Привязка к fingerprint Для ПО

Модель ограничений

Schema::create('digital_order_downloads', function (Blueprint $table) {
    $table->id();
    $table->foreignId('order_item_id')->constrained();
    $table->foreignId('digital_product_id')->constrained();
    $table->string('token', 64)->unique();
    $table->integer('downloads_count')->default(0);
    $table->integer('downloads_limit')->nullable(); // NULL = без лимита
    $table->timestamp('expires_at')->nullable();    // NULL = бессрочно
    $table->boolean('is_revoked')->default(false);  // ручная блокировка
    $table->timestamps();

    $table->index(['token', 'is_revoked']);
});

Сервис проверки ограничений

class DownloadLimitGuard
{
    /**
     * Проверить доступность скачивания и вернуть причину отказа (если есть)
     */
    public function check(DigitalOrderDownload $download): DownloadCheckResult
    {
        if ($download->is_revoked) {
            return DownloadCheckResult::denied('revoked', 'Доступ отозван');
        }

        if ($download->expires_at && $download->expires_at->isPast()) {
            return DownloadCheckResult::denied(
                'expired',
                'Срок действия ссылки истёк ' . $download->expires_at->format('d.m.Y'),
            );
        }

        if ($download->downloads_limit !== null
            && $download->downloads_count >= $download->downloads_limit) {
            return DownloadCheckResult::denied(
                'limit_reached',
                "Лимит скачиваний исчерпан ({$download->downloads_limit} из {$download->downloads_limit})",
            );
        }

        return DownloadCheckResult::allowed(
            remainingDownloads: $download->downloads_limit !== null
                ? $download->downloads_limit - $download->downloads_count
                : null,
            expiresAt: $download->expires_at,
        );
    }
}

Атомарный инкремент счётчика

Проблема: два одновременных запроса могут оба пройти проверку лимита до того, как счётчик обновится. Решение — пессимистическая блокировка строки:

class RecordDownloadAction
{
    public function execute(DigitalOrderDownload $download, Request $request): void
    {
        DB::transaction(function () use ($download, $request) {
            // Блокируем строку для обновления — SELECT ... FOR UPDATE
            $locked = DigitalOrderDownload::lockForUpdate()->findOrFail($download->id);

            // Повторная проверка лимита внутри транзакции
            if ($locked->downloads_limit !== null
                && $locked->downloads_count >= $locked->downloads_limit) {
                throw new DownloadLimitExceededException();
            }

            $locked->increment('downloads_count');

            DownloadEvent::create([
                'digital_order_download_id' => $locked->id,
                'ip_address'    => $request->ip(),
                'user_agent'    => $request->userAgent(),
                'referer'       => $request->header('Referer'),
                'downloaded_at' => now(),
            ]);
        });
    }
}

Обработка прерванных загрузок

HTTP Range requests — механизм докачки. Браузер или загрузчик отправляет заголовок Range: bytes=1048576-, запрашивая продолжение. Если каждый Range-запрос считать отдельным скачиванием, лимит исчерпается при одной загрузке крупного файла.

Решение: считать скачиванием только первый запрос без Range-заголовка или запрос с Range: bytes=0-:

public function download(string $token, Request $request): Response
{
    $download = DigitalOrderDownload::where('token', $token)->firstOrFail();

    $guard = app(DownloadLimitGuard::class);
    $result = $guard->check($download);

    if (!$result->isAllowed()) {
        return response()->view('digital.download-denied', ['reason' => $result->reason], 403);
    }

    $rangeHeader = $request->header('Range');
    $isFirstRequest = !$rangeHeader || $rangeHeader === 'bytes=0-';

    if ($isFirstRequest) {
        app(RecordDownloadAction::class)->execute($download, $request);
    }

    return $this->streamFile($download->digitalProduct);
}

Конфигурация лимитов по тарифу

Разные продукты могут иметь разные лимиты по умолчанию, а тариф при покупке переопределяет их:

// digital_products.download_limit — дефолтный лимит для продукта
// Переопределяется при создании DigitalOrderDownload в зависимости от тарифа

class CreateDigitalDownloadAction
{
    public function execute(OrderItem $item): DigitalOrderDownload
    {
        $dp    = $item->product->digitalProduct;
        $plan  = $item->plan_id ? Plan::find($item->plan_id) : null;

        $limit       = $plan?->download_limit ?? $dp->download_limit;
        $validityDays = $plan?->validity_days ?? $dp->validity_days;

        return DigitalOrderDownload::create([
            'order_item_id'      => $item->id,
            'digital_product_id' => $dp->id,
            'token'              => bin2hex(random_bytes(32)),
            'downloads_limit'    => $limit,
            'expires_at'         => $validityDays ? now()->addDays($validityDays) : null,
        ]);
    }
}

Административный контроль

В панели управления доступны действия:

  • Сбросить счётчик — восстановить лимит при технической проблеме у покупателя
  • Продлить срок — изменить expires_at вручную
  • Отозвать доступ — установить is_revoked = true
  • Добавить скачиваний — увеличить downloads_limit
// Пример: продление срока через артисан-команду или API
$download->update(['expires_at' => $download->expires_at->addDays(30)]);

Уведомления об истечении

За 3 дня до истечения срока отправляется email-напоминание:

$schedule->command('digital:notify-expiring --days=3')->dailyAt('10:00');

Сроки

Базовые ограничения (счётчик + срок) с атомарным инкрементом — 2–3 рабочих дня. Обработка Range-запросов, ограничения по IP/устройству, тарифные планы — ещё 2–3 дня.