Розробка системи знижок та акцій для інтернет-магазину
Помилка в розрахунку знижок може коштувати магазину до 10% виручки — клієнт бачить неправильну ціну і втрачає довіру. Ми розробили движок, який виключає такі ситуації. За 5 років ми реалізували понад 30 проєктів зі складним ціноутворенням: від ступінчастих цін для B2B до BOGO-акцій для рітейлу. Наша методологія гарантує прозорий розрахунок маржі та відсутність конфліктів правил. Розробка системи знижок займає від 5 до 9 робочих днів — термін залежить від набору акцій та обсягу товарної бібліотеки.
Неправильна реалізація знижок призводить до хаосу: одна акція може скасувати іншу, знижка на категорію не застосується до підкатегорії, а промокод підсумовується там, де не повинен. Щоб цього уникнути, ми будуємо чітку ієрархію та використовуємо патерн Visitor для розрахунку.
Як уникнути конфліктів правил знижок?
У будь-якій товарній бібліотеці співіснують кілька типів знижок. Критично задати порядок і правила сумісності. Ми використовуємо таку пріоритизацію:
| Пріоритет | Тип знижки | Приклад | Стекінг |
|---|---|---|---|
| 1 | Акційна ціна SKU | sale_price на товарі |
Завжди застосовується |
| 2 | Знижка за обсяг | 5% при покупці від 3 одиниць | stackable |
| 3 | Знижка за категорією | 10% на Electronics | exclusive |
| 4 | Знижка за сегментом | 15% для VIP-клієнтів | override |
| 5 | Промокод | WELCOME10 | залежить від налаштування |
Правило стекінгу задається на рівні акції: exclusive (не суміщається), stackable (підсумовується з попередніми), override (відміняє всі інші). Завдяки цьому движок приймає однозначне рішення.
Модель даних акцій
CREATE TABLE promotions ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, type VARCHAR(30) NOT NULL, -- 'product_discount', 'category_discount', 'volume_discount', 'bundle', 'bogo', 'tiered' 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 дозволяють задавати довільні правила без зміни схеми. Приклад умови для акції «купи 3 товари зі списку та отримай знижку 15%»:
{ "conditions": { "min_qty": 3, "product_ids": [101, 102, 103], "user_segments": ["wholesale"] }, "actions": { "type": "percent_discount", "value": 15, "applies_to": "matching_items" } } Чому JSONB підходить для правил знижок?
JSONB-поля conditions та actions дозволяють зберігати довільні правила без міграцій. Це дає гнучкість: можна додати нові типи умов (наприклад, погодні фактори або LTV клієнта) без зміни схеми бази. Крім того, JSONB індексується — запити до правил залишаються швидкими навіть при 10 000 активних акцій.
Движок застосування знижок
Клас DiscountEngine послідовно обходить правила, відсортовані за пріоритетом. Ми використовуємо патерн Factory для створення об'єктів правил.
class DiscountEngine { /** @var PromotionRule[] */ 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; } } Наш движок обробляє 1000 правил за 200 мс, що в 5 разів швидше за типову реалізацію на вкладених if-else. Як зазначається в документації Laravel, патерн Factory спрощує створення об'єктів без прив'язки до конкретних класів.
Знижка за обсяг (volume pricing)
Ступінчасті ціни — частий запит у B2B та B2C. Кожній кількості товару призначається своя ціна, що зберігається в таблиці volume_tiers.
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(); } } Акційні періоди з таймером та відображення знижок
Для «гарячих» акцій на фронтенді відображається таймер зворотного відліку. Акційна ціна передається з API разом із часом закінчення. На сторінці товару та в каталозі показуємо закреслену оригінальну ціну та бейдж із розміром знижки. Фільтр «Тільки акційні» перевіряє sale_price IS NOT NULL AND sale_ends_at > NOW().
Як відбувається інвалідація кешу?
Акції запускаються та завершуються автоматично через Laravel Scheduler з інтервалом у хвилину. При активації або деактивації акції інвалідується кеш актуальних цін. Для каталогів від 10 000 SKU використовуємо чергу — це запобігає піковому навантаженню на Redis.
Що входить в роботу
В рамках розробки ми:
- проектуємо модель даних під ваш асортимент (до 100 000 товарів);
- реалізуємо движок з підтримкою довільних правил на JSONB;
- налаштовуємо адміністративну панель для керування акціями;
- підключаємо автоматичний планувальник активації/деактивації;
- забезпечуємо інвалідацію кешу цін;
- створюємо звітність по ефективності акцій (кількість застосувань, сума знижок, середній чек).
По закінченню надаємо документацію по API та консольним командам, а також навчаємо вашу команду роботі з панеллю.
Звітність по акціях
Адміністративна панель показує по кожній акції: кількість застосувань, загальну суму знижок, середній чек з акцією та без, топ товарів за виручкою. Ці дані — мінімальна основа для об'єктивної оцінки та прийняття рішень про повторний запуск.
Етапи розробки
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз правил ціноутворення | 1 день | Специфікація акцій |
| Проектування моделі даних | 1-2 дні | Схема БД, міграції |
| Розробка движка знижок | 2-3 дні | Робочий DiscountEngine |
| Інтеграція з адмін-панеллю | 1-2 дні | CRUD для акцій |
| Тестування та налагодження | 1-2 дні | Покриття тестами 90%+ |
| Документація та навчання | 0.5 дня | API docs, інструкція |
Приклад складного правила: знижка для сегменту "Wholesale" на товари з категорії "Electronics" з мінімальною кількістю 5 одиниць
{ "conditions": { "user_segments": ["wholesale"], "categories": ["electronics"], "min_qty": 5 }, "actions": { "type": "percent_discount", "value": 12, "applies_to": "matching_items" } } Хочете обговорити вашу систему знижок? Напишіть нам — ми підготуємо оцінку за один робочий день. Замовте розробку системи знижок під ключ і отримайте прозорий розрахунок маржі.







