Prices and Discounts on 1C-Bitrix
The b_catalog_price Table and How Not to Get Lost in It
A typical scenario: a marketer launches a "-20% on electronics" promotion, a manager manually sets a special price for a VIP client, and the loyalty program adds another 10%. The customer ends up seeing -44% instead of the planned -20%, and the product sells below cost. The root cause is misconfigured cart rule priorities in the sale module and conflicting price types in b_catalog_price. We set up pricing in Bitrix so that discounts don't stack chaotically and margins stay under control even with hundreds of active promotions.
Price Types
Bitrix stores prices in the b_catalog_price table, one row per price type for each product. Types are defined in b_catalog_group and linked to user groups via b_catalog_group2group.
| Price Type | Binding | How It Works |
|---|---|---|
| Retail | "All users" group | Primary price on the website |
| Wholesale | "Wholesale buyers" group | Automatically displayed after a wholesale client logs in |
| Dealer | "Dealers" group | Individual coefficient from the base price |
| Purchase | Internal use only | Cost price, not visible on the website |
| Old price | For strikethrough pricing | Displayed as "was X, now Y" |
| Regional | Via geo-binding | Prices adjusted for regional logistics |
For each type, we configure:
- Automatic calculation via markup/discount formulas from the base price (
CCatalogProductProvideror theOnGetOptimalPricehandler) - Currency and rounding rules in
b_catalog_rounding - Import/export via CSV and synchronization with 1C (standard CommerceML exchange)
Multi-Currency Support
Exchange rates via \Bitrix\Currency\CurrencyManager::updateCBRFRates() or manual entry in the b_catalog_currency table. Display in the user's currency — by geolocation via geoip or by profile settings. Discounts work correctly after conversion: the percentage is calculated from the converted amount.
Cart Rules — The Main Risk Zone
The sale module, "Cart rules" section (/bitrix/admin/sale_discount.php). A condition builder: no developer needed, but with the ability to break everything.
Common scenarios:
- Discount by amount:
BASKET_AMOUNT >= 5000 → DISCOUNT 10% - "3 for the price of 2": condition on quantity in the cart by catalog section
- Bundle discount: "Phone + case + screen protector = -15%" — via a rule with a multiple condition
PRODUCT_ID IN (...) - Timer: discount active from 23:00 to 07:00 via
ACTIVE_FROM/ACTIVE_TOfields - Group discount:
USER_GROUPcheck in the rule conditions
Priorities — where things usually go wrong:
Two 20% discounts don't equal 40%. When applied sequentially: 100 → 80 → 64, total -36%. When applied in parallel: 100 - 20 - 20 = 60, total -40%. And if you forget to set the priority — Bitrix may apply both as separate rules and give -36%. Or the opposite.
We configure:
- The
PRIORITYfield for the order of application - The
LAST_DISCOUNT = Yflag — "do not apply other discounts after this one" - A maximum percentage via a custom
OnBeforeSaleOrderFinalActionhandler - Exclusion of products/categories from rules via
EXCLUDEconditions
Cumulative Discounts
Loyalty Program
Four models — the choice depends on the business:
- Threshold-based — the discount grows with the total purchase amount. Simplest for the customer and for support
- Points-based — points earned on purchases, payments with points. More flexible, but harder to understand
- Tier-based — silver/gold/platinum. Gamification works for retention
-
Cashback — return to an internal account (
b_sale_user_account)
Threshold System Implementation
| Total Purchases | Level | Discount |
|---|---|---|
| 0 — 10,000 | Standard | 0% |
| 10,001 — 50,000 | Silver | 5% |
| 50,001 — 150,000 | Gold | 10% |
| 150,001+ | Platinum | 15% |
Technically: the OnSaleOrderPaid handler recalculates the total of paid orders via CSaleOrder::GetList() with the filter PAYED = Y, and updates the user group via CUser::SetUserGroup(). The group is linked to a price type — the discount is applied automatically on the next visit to the site.
Features:
- "You need 3,200 more to reach gold status" notification — via a custom component in the personal account
- Level validity period — annual (recalculated by a
CAgentagent) or permanent - Separate calculation by category — electronics purchases don't affect clothing status
Promo Codes
Managed via CSaleDiscount and a custom admin interface:
-
One-time — a unique code linked to a coupon (
b_sale_discount_coupon) -
Reusable — a shared code with a limit via
MAX_USE -
Personal — linked to
USER_ID -
Bulk generation —
CSaleDiscountCoupon::Add()in a loop, a thousand per minute if needed
Restrictions: minimum order amount, product categories, per-user limit, validity period, compatibility with other discounts. Analytics: who used it, when, and with what order total — via a report on b_sale_discount_coupon with a JOIN on b_sale_order.
Linking to UTM tags — you can see which blogger/channel actually drives conversions.
Wholesale Pricing (B2B)
Features not available out of the box:
- Automatic price type switching when quantity > N via the
OnGetOptimalPricehandler - Price scale — displayed in the product card via a custom component: "1-9 pcs: $10, 10-49: $9, 50-99: $8, 100+: $7"
- Personalized price lists — PDF/Excel generation from the personal account via PhpSpreadsheet
- Special price request via a form → lead in CRM
- Credit limit and deferred payment via
b_sale_user_accountand a custom payment handler
Promotions
- Scheduling via
ACTIVE_FROM/ACTIVE_TO— automatic start and end - Countdown timer — a JS component linked to the element's
ACTIVE_TO - Promotional product quantity limit via a
QUANTITY_LIMITproperty and a check in the cart handler - "Promotions" section — via a smart filter by the
IS_SALE = Yproperty
Types: clearance sale, product of the day (rotated by an agent), flash sale (FOMO mechanics), stock liquidation, seasonal.
Personalization
- VIP discounts via individual user group → personal price type
- Corporate terms: deferred payment, custom delivery
- Behavior-based segmentation via
b_sale_order(purchase history) → automatic discount assignment - Dynamic pricing — a custom module that adjusts prices based on demand, inventory, and competitor prices (data from the monitoring module)
Integration with 1C
- Price type import via CommerceML (standard exchange
bitrix:catalog.import.1c) - Discount card synchronization: card number → user group → price type
- Rounding rules and VAT — alignment between 1C and Bitrix so the price on the website matches the price on the invoice
- Scheduled updates (cron + agent) or real-time via REST API
Timelines
| Task | Timeline |
|---|---|
| Price type configuration | 2-3 days |
| Cart rules (basic) | 3-5 days |
| Cumulative discount system | 1-2 weeks |
| B2B pricing | 2-4 weeks |
| Promo code system | 1 week |
| Comprehensive pricing system | 4-8 weeks |
Properly configured pricing automates routine tasks and removes the human factor from discount calculations. Margins stay under control — even when hundreds of rules are running simultaneously across thousands of SKUs.







