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

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, 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

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

Система скидок — це не просто «зачеркнута ціна». Це рухавик, який управляє багаторівневими правилами: акційними цінами, скидками по об'єму, часовими розпродажами, комбінованими умовами. Неправильна реалізація ведить до конфліктів правил, некоректних итогів та дир у марж. Розроблення повноцінної системи скидок займає 5–9 робочих днів.

Типи скидок та їх пріоритет

У будь-якому магазині сосуіснують кілька видів скидок. Порядок застосування критично важливий:

  1. Акційна ціна товару (sale_price на рівні SKU) — базова, застосовується завжди
  2. Скидка по категорії — відсоток на всі товари категорії в період акції
  3. Скидка за об'єм — при покупці N одиниць ціна знижується
  4. Скидка по сегменту користувача — VIP-клієнти, оптовики
  5. Промокод — поверх вже застосованих скидок (чи замість них — залежить від правила)

Правило стекірування задається на рівні кожної акції: exclusive (не сумісується з іншими), stackable (сумується), override (скасовує остальні).

Модель даних акцій

CREATE TABLE promotions (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(30) NOT NULL,
    priority INT DEFAULT 0,
    stackable BOOLEAN DEFAULT FALSE,
    conditions JSONB DEFAULT '{}',
    actions JSONB DEFAULT '{}',
    starts_at TIMESTAMP,
    ends_at TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE
);

conditions та actions у JSONB дозволяють задавати довільні правила без зміни схеми:

{
  "conditions": {
    "min_qty": 3,
    "product_ids": [101, 102, 103],
    "user_segments": ["wholesale"]
  },
  "actions": {
    "type": "percent_discount",
    "value": 15,
    "applies_to": "matching_items"
  }
}

Рухавик застосування скидок

Клас DiscountEngine послідовно застосовує правила до корзини:

class DiscountEngine {
    private array $rules = [];

    public function __construct(Collection $activePromotions) {
        foreach ($activePromotions->sortByDesc('priority') as $promo) {
            $this->rules[] = PromotionRuleFactory::make($promo);
        }
    }

    public function apply(Cart $cart): DiscountResult {
        $result = new DiscountResult($cart);
        foreach ($this->rules as $rule) {
            if (!$rule->matches($cart)) continue;
            if (!$rule->isStackable() && $result->hasDiscount()) continue;
            $result->addDiscount($rule->calculate($cart));
            if ($rule->isExclusive()) break;
        }
        return $result;
    }
}

Скидка за об'єм

Ступінчасті ціни за кількість — частий запит у B2C та особливо у B2B:

class VolumePricingRule implements PromotionRule {
    public function calculate(Cart $cart): array {
        $discounts = [];
        foreach ($cart->items as $item) {
            $tier = $this->getTier($item->product_id, $item->quantity);
            if ($tier) {
                $discounts[] = [
                    'item_id'  => $item->id,
                    'amount'   => ($item->price - $tier->price) * $item->quantity,
                    'label'    => "Скидка за об'єм (×{$item->quantity})",
                ];
            }
        }
        return $discounts;
    }

    private function getTier(int $productId, int $qty): ?VolumeTier {
        return VolumeTier::where('product_id', $productId)
            ->where('min_qty', '<=', $qty)
            ->orderByDesc('min_qty')
            ->first();
    }
}

Акційні періоди з таймером

Для «гарячих» акцій на фронтенді відображається таймер зворотного отліку. Поточна акційна ціна передається з часом закінчення:

interface PriceData {
  regular_price: number;
  sale_price: number | null;
  sale_ends_at: string | null;
}

Компонент таймера:

const SaleTimer = ({ endsAt }: { endsAt: string }) => {
  const [remaining, setRemaining] = useState(differenceInSeconds(parseISO(endsAt), new Date()));

  useEffect(() => {
    const interval = setInterval(() => {
      setRemaining(prev => Math.max(0, prev - 1));
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  const hours = Math.floor(remaining / 3600);
  const minutes = Math.floor((remaining % 3600) / 60);
  const seconds = remaining % 60;

  return (
    <div className="flex gap-1 font-mono text-red-600">
      <span>{String(hours).padStart(2, '0')}</span>:
      <span>{String(minutes).padStart(2, '0')}</span>:
      <span>{String(seconds).padStart(2, '0')}</span>
    </div>
  );
};

BOGO (Buy One Get One)

Акція «купи 2, отримай 1 в подарок» реалізується окремим правилом:

class BogoRule implements PromotionRule {
    public function calculate(Cart $cart): array {
        $qualifying = $cart->items->filter(fn($i) => in_array($i->product_id, $this->productIds));
        $totalQty = $qualifying->sum('quantity');
        $freeQty = intdiv($totalQty, $this->buyQty);

        $cheapestPrice = $qualifying->min('price');
        return [['amount' => $cheapestPrice * $freeQty, 'label' => 'Акція 2+1']];
    }
}

Відображення скидок на сторінці товара та в каталозі

  • Зачеркнута оригінальна ціна рядом з акційною
  • Бейдж «−20%» чи «−500 ₽» — залежно від типу скидки
  • Для volume pricing — таблиця ступінчастих цін на карточці товара
  • Фільтр «Тільки акційні» в каталозі — SQL-запит з WHERE sale_price IS NOT NULL AND sale_ends_at > NOW()

Планувальник акцій

Акції стартують та завершуються автоматично через Laravel Scheduler:

$schedule->command('promotions:activate')->everyMinute();
$schedule->command('promotions:expire')->everyMinute();

При активації акції кеш актуальних цін інвалідується. Для великих каталогів (10k+ SKU) інвалідація виконується через чергу, а не синхронно.

Відчітність по акціях

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