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

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, 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),  -- cap для процентных скидок
    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), capped by 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)
  • Успешный промокод: зелёная метка, пересчёт итога, кнопка удалить
  • Ошибка: красный текст с причиной
  • Только один промокод одновременно (если иное не предусмотрено бизнес-логикой)

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

В CRM/admin отслеживаем: количество применений по дням, общую сумму скидок, конверсию с промокодом vs без, средний чек с промокодом. Это позволяет оценивать ROI конкретных кампаний.