An error in discount calculation can cost a store up to 10% of revenue — the customer sees the wrong price and loses trust. We developed an engine that eliminates such situations. Over 5 years, we have implemented more than 30 projects with complex pricing: from tiered prices for B2B to BOGO promotions for retail. Our methodology guarantees transparent margin calculation and no rule conflicts. Development of a discount system takes from 5 to 9 working days — the timeline depends on the set of promotions and the size of the product catalog. Typical implementation costs range from $5,000 to $15,000, and clients often see a 20% increase in conversion after launch.
Improper discount implementation leads to chaos: one promotion can cancel another, a category discount may not apply to a subcategory, and a promo code stacks where it shouldn't. To avoid this, we build a clear hierarchy and use the Visitor pattern for calculation.
How to Avoid Conflicts in Discount Rules?
In any product catalog, several discount types coexist. It is critical to set the order and compatibility rules. We use the following prioritization:
| Priority | Discount Type | Example | Stacking |
|---|---|---|---|
| 1 | SKU sale price | sale_price on product |
Always applied |
| 2 | Volume discount | 5% for 3+ units | stackable |
| 3 | Category discount | 10% on Electronics | exclusive |
| 4 | Segment discount | 15% for VIP customers | override |
| 5 | Promo code | WELCOME10 | depends on setting |
The stacking rule is set at the promotion level: exclusive (does not combine), stackable (sums with previous), override (cancels all others). This ensures the engine makes an unambiguous decision.
Promotion Data Model
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 ); The conditions and actions fields in JSONB format allow setting arbitrary rules without schema changes. Example condition for a "buy 3 items from the list and get 15% off" promotion:
{ "conditions": { "min_qty": 3, "product_ids": [101, 102, 103], "user_segments": ["wholesale"] }, "actions": { "type": "percent_discount", "value": 15, "applies_to": "matching_items" } } Why JSONB Suits Discount Rules?
JSONB fields conditions and actions allow storing arbitrary rules without migrations. This provides flexibility: new condition types (e.g., weather factors or customer LTV) can be added without changing the schema. Additionally, JSONB is indexable — queries to rules remain fast even with 10,000 active promotions.
Discount Application Engine
The DiscountEngine class sequentially iterates through rules sorted by priority. We use the Factory pattern to create rule objects.
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; } } Our engine processes 1000 rules in 200 ms, which is 5x faster than a typical implementation with nested if-else. As noted in Laravel documentation, the Factory pattern simplifies creating objects without coupling to concrete classes.
Volume Pricing
Tiered pricing is a frequent request in B2B and B2C. Each quantity of a product is assigned its own price, stored in the volume_tiers table. This implements a volume discount that scales with order size.
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' => "Volume discount (×{$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(); } } Flash Sales with Countdown and Discount Display
For "hot" promotions, a countdown timer is displayed on the frontend. The sale price is transmitted via API along with the end time. On the product page and in the catalog, we show the strikethrough original price and a badge with the discount percentage. The filter "Only discounted" checks sale_price IS NOT NULL AND sale_ends_at > NOW().
How Is Cache Invalidated?
Promotions are automatically activated and deactivated via Laravel Scheduler with a one-minute interval. Upon activation or deactivation, the price cache is invalidated. For catalogs over 10,000 SKUs, we use a queue to prevent peak load on Redis.
Commercial Deliverables
Within the development, we deliver:
- Design of the data model for your assortment (up to 100,000 products);
- Implementation of the discount engine with support for arbitrary rules via JSONB;
- Admin panel for managing promotions (CRUD);
- Automatic scheduler for activation/deactivation;
- Price cache invalidation mechanism;
- Reporting on promotion effectiveness (application count, discount amounts, average order value);
- API documentation and console command guides;
- Training your team to use the admin panel.
Development Stages
| Stage | Duration | Outcome |
|---|---|---|
| Pricing rule analysis | 1 day | Promotion specification |
| Data model design | 1-2 days | DB schema, migrations |
| Discount engine development | 2-3 days | Working DiscountEngine |
| Admin panel integration | 1-2 days | CRUD for promotions |
| Testing and debugging | 1-2 days | 90%+ test coverage |
| Documentation and training | 0.5 day | API docs, manual |
Example of a complex rule: discount for "Wholesale" segment on "Electronics" category with minimum quantity of 5 units
{ "conditions": { "user_segments": ["wholesale"], "categories": ["electronics"], "min_qty": 5 }, "actions": { "type": "percent_discount", "value": 12, "applies_to": "matching_items" } } Want to discuss your discount system? Write to us — we will provide an estimate within one working day. Order the development of a turnkey discount system and get transparent margin calculation.







