Розробка продажу цифрових товарів (файли, документи, шаблони) на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка продажу цифрових товарів (файли, документи, шаблони) на сайті
Середня
~5 робочих днів
Часті питання

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

Етапи розробки

Останні роботи

  • 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

Впровадження продажу цифрових товарів (файли, документи, шаблони) на сайті

Цифрові товари не потребують складу та доставки, але вимагають надійної системи доступу: файл повинен бути передан покупцю одразу після оплати та захищений від розповсюдження. Стандартні e-commerce движки або не підтримують цифрові товари, або реалізують їх примітивно — прямим посиланням на файл у public/, що не забезпечує захист.

Типи цифрових товарів

  • Документи — PDF, DOCX, юридичні шаблони, контракти, інструкції
  • Таблиці — XLSX-шаблони, фінансові моделі, планувальники
  • Дизайн-ресурси — PSD, Figma-шаблони, іконки, шрифти
  • Програмне забезпечення — дистрибутиви, плагіни, теми для CMS
  • Медіа — аудіо, відео, фотографії високої роздільної здатності
  • Освітній контент — курси у вигляді ZIP-архівів, електронні книги

Архітектура системи

Покупець оплачує → PaymentConfirmedEvent
  → CreateDigitalOrderJob
    → генерація унікального посилання для завантаження
    → відправка email з посиланням
    → запис у digital_order_downloads (обмеження)

Покупець клацає на посилання → DigitalDownloadController
  → перевірка токена (дійсний? не закінчився? ліміт не перевищений?)
  → потокова передача файлу через StreamedResponse (не через публічний URL)
  → запис у download_events

Моделі даних

Schema::create('digital_products', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_id')->constrained()->cascadeOnDelete();
    $table->string('storage_path'); // шлях у приватному сховищі (не public)
    $table->string('original_filename');
    $table->string('mime_type');
    $table->unsignedBigInteger('file_size_bytes');
    $table->string('version')->nullable(); // v1.2.0
    $table->integer('download_limit')->nullable(); // NULL = без обмежень
    $table->integer('validity_days')->nullable(); // NULL = безстроково
    $table->timestamps();
});

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();
    $table->timestamp('expires_at')->nullable();
    $table->timestamps();

    $table->index('token');
});

Schema::create('download_events', function (Blueprint $table) {
    $table->id();
    $table->foreignId('digital_order_download_id')->constrained();
    $table->string('ip_address', 45);
    $table->string('user_agent', 500)->nullable();
    $table->timestamp('downloaded_at');
    $table->index('downloaded_at');
});

Генерація посилання після оплати

class CreateDigitalDownloadAction
{
    public function execute(OrderItem $item): DigitalOrderDownload
    {
        $digitalProduct = $item->product->digitalProduct;

        if (!$digitalProduct) {
            throw new NotADigitalProductException($item->product_id);
        }

        $download = DigitalOrderDownload::create([
            'order_item_id'      => $item->id,
            'digital_product_id' => $digitalProduct->id,
            'token'              => bin2hex(random_bytes(32)), // 64 символи
            'downloads_count'    => 0,
            'downloads_limit'    => $digitalProduct->download_limit,
            'expires_at'         => $digitalProduct->validity_days
                ? now()->addDays($digitalProduct->validity_days)
                : null,
        ]);

        // Відправляємо email з посиланням
        Mail::to($item->order->email)
            ->send(new DigitalDownloadReadyMail($download));

        return $download;
    }
}

Контролер завантаження

Файл ніколи не відається безпосередньо з публічної директорії. Тільки через контролер з перевірками:

class DigitalDownloadController
{
    public function download(string $token): StreamedResponse
    {
        $download = DigitalOrderDownload::where('token', $token)->firstOrFail();

        // Перевіряємо термін дії
        if ($download->expires_at && $download->expires_at->isPast()) {
            abort(410, 'Посилання закінчилося');
        }

        // Перевіряємо ліміт завантажень
        if ($download->downloads_limit !== null
            && $download->downloads_count >= $download->downloads_limit) {
            abort(403, 'Ліміт завантажень вичерпаний');
        }

        $dp = $download->digitalProduct;

        // Перевіряємо існування файлу
        if (!Storage::disk('private')->exists($dp->storage_path)) {
            abort(404, 'Файл не знайдено');
        }

        // Фіксуємо завантаження
        DB::transaction(function () use ($download) {
            $download->increment('downloads_count');
            DownloadEvent::create([
                'digital_order_download_id' => $download->id,
                'ip_address'   => request()->ip(),
                'user_agent'   => request()->userAgent(),
                'downloaded_at' => now(),
            ]);
        });

        // Передаємо потоком — не створюємо тимчасову копію в пам'яті
        return Storage::disk('private')->download(
            $dp->storage_path,
            $dp->original_filename,
            ['Content-Type' => $dp->mime_type]
        );
    }
}

Приватне сховище

Файли зберігаються в директорії поза public/. У Laravel використовується диск private:

// config/filesystems.php
'private' => [
    'driver' => 'local',
    'root'   => storage_path('app/private'),
    'permissions' => [
        'file'      => ['public' => 0640, 'private' => 0640],
        'dir'       => ['public' => 0750, 'private' => 0750],
    ],
],

Для великих файлів або високого навантаження — S3 з попередньо підписаними посиланнями:

// Генерація тимчасового підписаного посилання S3 (15 хвилин)
$url = Storage::disk('s3-private')->temporaryUrl(
    $dp->storage_path,
    now()->addMinutes(15),
    ['ResponseContentDisposition' => 'attachment; filename="' . $dp->original_filename . '"'],
);

return redirect($url);

Особистий кабінет покупця

Розділ «Мої покупки» відображає:

  • Список куплених цифрових товарів
  • Кнопку завантаження (неактивна при вичерпанні ліміту або закінченні терміну)
  • Кількість залишених завантажень
  • Термін дії посилання

Захист від розповсюдження

  • Унікальний токен для кожної покупки — один токен не підходить для іншого замовлення
  • Ліміт завантажень за кількістю — стандартно 3–5 завантажень
  • Ліміт за часом — 30–90 днів
  • Логування IP при кожному завантаженні — для розслідування витоків

Терміни

Повна система продажу цифрових товарів (завантаження файлів в адмінці, прив'язка до товарів, відправка після оплати, сторінка завантаження, особистий кабінет) — 5–8 робочих днів.