1C-Bitrix Promotions Management Module Development
The standard sale module includes a discount mechanism via b_sale_discount — rules based on cart conditions. It is a powerful tool, but it is designed for automatic order-level discounts. Marketing promotions with banners, active periods, participant counters, usage limits, and mechanics like "buy 2 get 3" — none of this is achievable with standard means, or it requires cumbersome workarounds. The promotions management module fills this gap.
Data Model
The vendor.promo module with the following tables:
-
b_vendor_promo_action— promotions: id, code, name, type (discount/gift/bundle/coupon/cashback), date_from, date_to, is_active, priority, usage_limit, usage_count, conditions (JSON), reward (JSON), banner_image_id, description -
b_vendor_promo_coupon— coupons: id, action_id, code, discount_type (percent/fixed), discount_value, usage_limit, usage_count, user_id (null = public), expires_at, is_active -
b_vendor_promo_usage— usage history: id, action_id, coupon_id, order_id, user_id, discount_amount, applied_at -
b_vendor_promo_gift— promotion gift products: id, action_id, product_id, quantity
Promotion Types
Conditional discount: "conditions": {"min_sum": 3000, "iblock_section_id": 12} — 10% discount on orders over 3,000 in the "Electronics" section.
Bundle: "type": "bundle" — when product A is added to the cart, product B is automatically added at a discount. Implemented via the OnSaleBasketItemAdd event.
Coupon: generation of unique codes in b_vendor_promo_coupon, verification at checkout.
Gift: when cart conditions are met — a product from b_vendor_promo_gift is automatically added at a price of 0.
Cashback: accrual of loyalty programme points instead of a discount.
Condition Checking and Application
class PromoChecker
{
public function check(PromoAction $action, \Bitrix\Sale\Order $order): CheckResult
{
$conditions = $action->getConditions();
$basket = $order->getBasket();
if (isset($conditions['min_sum']) && $basket->getPrice() < $conditions['min_sum']) {
return CheckResult::fail('Minimum order amount not reached');
}
if ($action->getUsageLimit() && $action->getUsageCount() >= $action->getUsageLimit()) {
return CheckResult::fail('Promotion usage limit exhausted');
}
return CheckResult::success();
}
}
Promotion Priority and Compatibility
Multiple promotions can be active simultaneously. The priority parameter determines the application order. The exclusive field in promotion settings means that no other promotions can be combined with it.
Logic: active promotions are sorted by descending priority. For each, CheckResult is evaluated. If a promotion is exclusive and passes — it is applied and the loop breaks.
Coupon Generator
Bulk generation of unique codes for email campaigns:
$generator = new CouponGenerator($action);
$coupons = $generator->generate(1000, [
'length' => 8,
'prefix' => 'SALE24-',
'charset' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789',
]);
// batch insert into b_vendor_promo_coupon + CSV export file
Characters that look like digits (O/0, I/1) are excluded from the charset to avoid manual entry errors.
Promotion Statistics
In the administrative interface, each promotion displays:
- Number of uses and percentage of the limit
- Total discount for the period
- Conversion: how many users added a coupon to an incomplete order vs completed payment
- Top products covered by the promotion
- Usage dynamics by day (chart)
Administrative Interface
- Promotion list with filter by status, type, date
- Condition and reward builder
- Coupon management: batch generation, deactivation, CSV export
- Promotion report broken down by order
Development Timeline
| Stage | Duration |
|---|---|
| ORM tables, promotion model | 1 day |
| Condition checking, discount application | 2 days |
| Gift and bundle mechanics | 2 days |
| Coupon generator, import/export | 1 day |
| Priorities and promotion compatibility | 1 day |
| Reports and statistics | 1 day |
| Administrative interface | 2 days |
| Testing | 1 day |
Total: 11 working days. Integration with an email platform for automatic coupon distribution — additional 1–2 days.







