Розробка системи промокодів та купонів для інтернет-магазину

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка системи промокодів та купонів для інтернет-магазину
Середня
~3-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

Розроблення системи промокодів та купонів для інтернет-магазину

Промокоди — інструмент управління конверсією та лояльністю. За простим полем введення приховується нетривіальна логіка: обмеження по категоріях, мінімальні суми, ліміти використання, сумісність з іншими скидками. Неправильна реалізація ведить до конфліктів правил, некоректних итогів та дир у марж. Розроблення гнучкої системи промокодів займає 4–7 робочих днів.

Модель даних

CREATE TABLE coupons (
    id BIGSERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,
    type VARCHAR(20) NOT NULL, -- 'percent', 'fixed', 'free_shipping', 'buy_x_get_y'
    value NUMERIC(10,2),       -- відсоток чи сума скидки
    min_order_amount NUMERIC(12,2) DEFAULT 0,
    max_discount_amount NUMERIC(12,2),
    usage_limit INT,           -- NULL = безлімітний
    usage_per_user INT DEFAULT 1,
    used_count INT DEFAULT 0,
    starts_at TIMESTAMP,
    expires_at TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE,
    applies_to VARCHAR(20) DEFAULT 'all', -- 'all', 'categories', 'products', 'users'
    metadata JSONB DEFAULT '{}'
);

CREATE TABLE coupon_usages (
    id BIGSERIAL PRIMARY KEY,
    coupon_id BIGINT REFERENCES coupons(id),
    user_id BIGINT REFERENCES users(id),
    guest_email VARCHAR(255),
    order_id BIGINT REFERENCES orders(id),
    discount_amount NUMERIC(12,2),
    used_at TIMESTAMP DEFAULT NOW()
);

metadata у JSONB зберігає обмеження: застосовувані категорії, конкретні SKU, сегменти користувачів.

Типи промокодів

Тип Приклад Логіка
percent SAVE20 → −20% total * (value / 100), обмежено max_discount_amount
fixed MINUS500 → −500 ₽ Фіксована сума, не перевищуючи итог
free_shipping FREESHIP Обнуляє вартість доставки
buy_x_get_y BUY3GET1 Безплатний товар чи скидка на N-й товар
first_order FIRST10 10% для першого заказу аккаунту/email

Валідація промокода

Багаторівнева перевірка перед застосуванням:

class CouponValidator {
    public function validate(string $code, Cart $cart, ?User $user): CouponResult {
        $coupon = Coupon::where('code', strtoupper($code))->first();

        if (!$coupon || !$coupon->is_active) {
            return CouponResult::invalid('Промокод не знайдений');
        }

        if ($coupon->expires_at && $coupon->expires_at->isPast()) {
            return CouponResult::invalid('Строк дії промокода істік');
        }

        if ($coupon->starts_at && $coupon->starts_at->isFuture()) {
            return CouponResult::invalid('Промокод ще не активний');
        }

        if ($coupon->usage_limit && $coupon->used_count >= $coupon->usage_limit) {
            return CouponResult::invalid('Промокод вичерпаний');
        }

        if ($cart->subtotal < $coupon->min_order_amount) {
            return CouponResult::invalid("Мінімальна сума заказу: {$coupon->min_order_amount} ₽");
        }

        if ($user && $coupon->usage_per_user) {
            $userUsages = CouponUsage::where('coupon_id', $coupon->id)
                ->where('user_id', $user->id)
                ->count();
            if ($userUsages >= $coupon->usage_per_user) {
                return CouponResult::invalid('Ви вже використовували цей промокод');
            }
        }

        return CouponResult::valid($coupon, $this->calculateDiscount($coupon, $cart));
    }
}

Розрахунок скидки по категоріях

Якщо промокод застосовується тільки до товарів певних категорій:

private function calculateDiscount(Coupon $coupon, Cart $cart): float {
    $applicableItems = $cart->items;

    if ($coupon->applies_to === 'categories') {
        $categoryIds = $coupon->metadata['category_ids'] ?? [];
        $applicableItems = $cart->items->filter(
            fn($item) => in_array($item->product->category_id, $categoryIds)
        );
    }

    $applicableTotal = $applicableItems->sum(fn($i) => $i->price * $i->quantity);

    $discount = match($coupon->type) {
        'percent' => $applicableTotal * ($coupon->value / 100),
        'fixed'   => min($coupon->value, $applicableTotal),
        default   => 0,
    };

    if ($coupon->max_discount_amount) {
        $discount = min($discount, $coupon->max_discount_amount);
    }

    return round($discount, 2);
}

Атомарне застосування та лічильник використання

При оформленні заказу застосування промокода повинне бути атомарним, з перевіркою used_count через lockForUpdate:

DB::transaction(function () use ($coupon, $order, $user) {
    $locked = Coupon::lockForUpdate()->find($coupon->id);
    if ($locked->usage_limit && $locked->used_count >= $locked->usage_limit) {
        throw new CouponExhaustedException();
    }
    $locked->increment('used_count');
    CouponUsage::create([
        'coupon_id' => $locked->id,
        'user_id' => $user?->id,
        'order_id' => $order->id,
        'discount_amount' => $order->discount_amount,
    ]);
});

Генерація масових купонів

Для маркетингових кампаній генерування унікальних кодів у кількостях тисяч штук:

Artisan::call('coupons:generate', [
    '--count'   => 1000,
    '--prefix'  => 'PROMO24',
    '--type'    => 'percent',
    '--value'   => 15,
    '--expires' => '2024-12-31',
    '--limit'   => 1,
]);

Коди формуються як PROMO24-{XXXXXXXX} — 8 випадкових символів з [A-Z0-9] без неоднозначних (O, 0, I, 1).

UX у корзині

Поле введення промокода — другорядний елемент, не конкурує з кнопкою «Оформити». Рекомендоване поведінка:

  • Поле згорнуте за замовчуванням, розгортається кліком на «Є промокод?»
  • Після введення — миттєва перевірка (debounce 500ms)
  • Успішний промокод: зелена мітка, перерахунок итогу, кнопка видалення
  • Помилка: червоний текст з причиною
  • Тільки один промокод одночасно (якщо іншен не передбачено бізнес-логікою)

Аналітика ефективності

Відслідковуємо: кількість застосувань по днях, загальну суму скидок, конверсію з промокодом vs без, середній чек з промокодом. Це дозволяє оцінювати ROI конкретних кампаній.