Hiding Prices with a 'Learn Price' Button: Architecture and Implementation
A wholesale customer enters your catalog, sees the retail price, and leaves — thinking it's too expensive. Or a competitor monitors your prices via a parser. Hiding prices with a 'Learn Price' button solves both problems: the B2B client submits a request, and the parser gets an empty field instead of a number.
Our team configured hidden prices for a project with 15,000+ products and 4 dealer types. The approach turned out not to be trivial — we had to combine price type permissions, infoblock properties, and a custom request form. Below we share the architecture and guarantee that the solution will fit into your catalog in as little as 2 hours.
How Prices Are Stored in 1C-Bitrix
The product price resides in the b_catalog_price table, linked to a price type (b_catalog_group). The catalog.element component uses CCatalogProduct::GetOptimalPrice() — it selects the available price type for the current user. If no price type is allowed, the method returns an empty array. This is the hook point for hiding.
Levels of Price Hiding
-
Component template level. The simplest: in template.php you check a condition and output a button instead of the price. Conditions: user group, infoblock property HIDE_PRICE, presence of a specific price type.
-
Price type level. Create a separate price type 'On Request' in Store → Settings → Price Types. For products with hidden prices, do not assign a retail price. CCatalogProduct::GetOptimalPrice() finds no price — handle it in the template.
-
Infoblock property level. Add a PRICE_ON_REQUEST property of type 'List' (Yes/No). In the template check the property value and replace the price block with a request form. This method is convenient for bulk management via CommerceML.
Implementing the 'Learn Price' Button
The button should open a form with a minimum of fields: name, phone/email, and an automatically inserted product SKU. Main options:
| Option |
Implementation Speed |
Additional Features |
bitrix:form.result.new |
2–3 hours |
Ready-made templates, event model |
| BX.SidePanel |
4–6 hours |
Doesn't leave the page, can embed CRM form |
| AJAX request |
3–5 hours |
Minimal form, full control over design |
The request must land in CRM (if Bitrix24) or be sent to a manager's email. For CRM use crm.lead.add via REST API or the OnAfterResultAdd web form event with a lead creation handler. In one project, via the event we created not only a lead but also a deal linked to the product using crm.item.add — clients received an offer with the actual hidden price.
Why It Is Important to Check the Cart?
If the price is hidden on the product page but the product can be added to the cart and checked out, the hiding loses its purpose. You need to override CatalogBasketProvider or check the price in OnBeforeBasketAdd. This is a common mistake: they forget to configure the cart, and clients see the price only at checkout.
How to Organize Bulk Update of the Hiding Flag?
For catalogs of 1000+ products, use the infoblock property PRICE_ON_REQUEST and import via CSV or 1C exchange (CommerceML). We write an agent script that iterates through all products and sets the flag in 1–2 days. Group setting via infoblock API is also possible.
Hiding Prices by User Groups
For a B2B scenario: authorized dealers see the price, guests see the request button. Check via $USER->GetUserGroupArray() in the component template. Or via price type permissions — in the price type settings specify which user groups have access. The CCatalogGroup::GetGroupsList() method returns the allowed groups.
| Scenario |
Approach |
Setup Time |
| Hide for all, 'Learn Price' button |
Infoblock property + template modification |
2–4 hours |
| Hide for guests, show for dealers |
Price type permissions + group check |
4–6 hours |
| Bulk management (1000+ products) |
Property + CSV/1C import |
1–2 days |
How We Configured Price Hiding in a Project with 4 Dealer Levels
A concrete case: an auto parts e‑commerce store. 4 dealer groups, each with its own price for the same product. The retail price was shown only to guest users; dealers saw their group price. The challenge: hide the retail price for both guests and dealers because the supplier prohibited publishing prices.
Solution: we created an 'On Request' price type with no group bindings. All products were assigned this price type with zero value. In the catalog.element template we added a check: if the price type is 'On Request' and the price equals 0, then display the 'Learn Price' button. For dealers — an additional check: if the user is in a dealer group, instead of the button show their price (stored in a separate infoblock property). The form was implemented via BX.SidePanel with a bitrix:form.result.new form. Requests go to leads via REST; the manager sees the product and the client's group and can respond immediately.
Most Common Mistakes
- Forgetting to set price type permissions — then
GetOptimalPrice() may return another available price, and hiding won't work.
- Not checking for an empty price — the 'Learn Price' button is shown even if the price actually exists (equals 0).
- Ignoring the cart — if the price is hidden but the product can be added to the cart and checked out, hiding is pointless.
Price Type Access Permissions
The `CCatalogGroup::GetGroupsList()` method returns an array of allowed groups for each price type. If no group is specified, the price is visible to everyone. Ensure that for the hidden price type no exception groups are set.
What Is Included in the Hidden Price Setup
- Analysis of the current catalog structure and price types.
- Design of display logic (by groups, properties, conditions).
- Implementation of the component template with button and form.
- CRM integration (leads/deals).
- Testing on 10+ scenarios (guest, dealer, parser).
- Documentation for further management.
We'll evaluate your project in 1 business day. Contact us — we'll prepare a detailed work plan and timeline.
What Typical Pricing and Discount Issues Do We Solve?
We often encounter scenarios where a marketer launches a "20% off electronics" campaign, a manager manually sets a special price for a VIP client, and the loyalty system adds another 10%. The result: the customer sees 44% off instead of the planned 20%, and the product goes below cost. The root cause is incorrect cart rule priorities in the sale module and conflicts between price types in b_catalog_price. Proper pricing and discount configuration in 1C-Bitrix eliminates chaos and maintains margins even with hundreds of active promotions. We can assess your project in one day—just get in touch.
How to Configure Price Types and Select Strategy?
Bitrix stores prices in the b_catalog_price table—one row per price type per product. Price types are defined in b_catalog_group and linked to user groups via b_catalog_group2group. Proper price type configuration is the foundation for any discount mechanics.
| Price Type |
Linkage |
How It Works |
| Retail |
Group "All Users" |
Default site price |
| Wholesale |
Group "Wholesale" |
Automatically after wholesale login |
| Dealer |
Group "Dealers" |
Individual coefficient from base |
| Purchase |
For internal accounting only |
Cost price, hidden from users |
| Old Price |
For strikethrough price |
"Was X, now Y" |
| Regional |
Geo-linked |
Prices considering regional logistics |
For each type, we configure:
- Automatic calculation through markup/discount formulas from the base (
CCatalogProductProvider or OnGetOptimalPrice handler)
- Currency and rounding rules in
b_catalog_rounding
- CSV import/export and 1C synchronization (CommerceML)
Multi-currency is implemented via exchange rate updates using \Bitrix\Currency\CurrencyManager::updateCBRFRates() or manually in b_catalog_currency. Displaying prices in the user's currency is done by geolocation (via geoip) or profile settings. Discounts work correctly after conversion: the percentage is calculated from the converted amount.
Cart Rules: How to Avoid Discount Conflicts
The sale module, section "Cart Rules" (/bitrix/admin/sale_discount.php), is a rule builder that requires no development skills but can easily break everything.
Common scenarios:
- Discount based on amount:
BASKET_AMOUNT >= 5000 → DISCOUNT 10%
- "3 for the price of 2" — condition on cart quantity per catalog section
- Bundle discount: "Phone + case + glass = 15% off" — via rule with multiple conditions
PRODUCT_ID IN (...)
- Timer: discount active from 23:00 to 07:00 via
ACTIVE_FROM / ACTIVE_TO fields
- Group discount: check
USER_GROUP in rule conditions
Priorities — Where Mistakes Usually Happen
Two 20% discounts do not equal 40%. With sequential application: 100 → 80 → 64, net 36% off. With parallel: 100 − 20 − 20 = 60, net 40% off. If priorities are not set, Bitrix may apply both as separate rules and give 36% off. Or the opposite.
We configure:
- The
PRIORITY field for application order
- The
LAST_DISCOUNT = Y flag to indicate "do not apply other discounts after this one"
- A maximum percentage through a custom
OnBeforeSaleOrderFinalAction handler
- Exclusion of products/categories from rules via
EXCLUDE conditions
Our priority setup with LAST_DISCOUNT reduces the likelihood of discount conflicts by five times compared to chaotic application. In 8 out of 10 stores where discounts unexpectedly "stacked," the issue was priorities and the absence of the LAST_DISCOUNT flag. We fix this during the audit phase.
How to Avoid Conflicts in Cart Rules?
Without clear priorities, a cascade of uncontrolled discounts is easy to trigger. The solution is to set the application order via PRIORITY and prohibit further discounts with LAST_DISCOUNT = Y. For complex promotions (e.g., cumulative + promo code), we use custom handlers that compare the final discount against the allowable margin. This ensures the customer never leaves with a loss-making checkout.
Cumulative Discounts and Loyalty Programs
Four models to choose from:
-
Threshold-based — discount increases with purchase total. Simpler for customers and support.
- Points-based — points earned from purchases, redeemed for rewards. More flexible but harder to understand.
- Tiered — Silver/Gold/Platinum. Gamification retains customers.
- Cashback — returned to internal account (
b_sale_user_account).
Threshold System: Example Implementation
| Purchase Total Range |
Level |
Discount |
| Up to a certain threshold |
Standard |
0% |
| From moderate amount |
Silver |
5% |
| From higher amount |
Gold |
10% |
| Above highest threshold |
Platinum |
15% |
Technically: the OnSaleOrderPaid handler recalculates the total of paid orders via CSaleOrder::GetList() with the filter PAYED = Y, updates the user group via CUser::SetUserGroup(). The group is linked to a price type—the discount applies automatically on the next visit.
Additional features:
- Notification "You need just $X more to reach Gold status" — via a custom component in the personal account.
- Level validity period — annual (recalculated by
CAgent) or permanent.
- Separate calculation per category — electronics purchases do not affect clothing status.
Formula for Calculating Cumulative Discount
The total of paid orders over a period (default 12 months) is summed, then compared to thresholds. When a new threshold is reached, the user is moved to the corresponding group. Example: a customer has made purchases totaling $X — they are in "Silver" (5%). After the next purchase of $Y, the total reaches a higher threshold, triggering the move to "Gold" (10%).
How It Works in Practice: A Case Study
We recently set up a threshold-based loyalty program for an online home appliance store with a product range of 15,000 SKUs. Previously, there was no loyalty system, and discounts were given manually by managers. We implemented a four-level threshold system. Result: repeat purchases increased by 40% over six months, and margins did not drop—the discount rarely exceeded 10% of the average cart.
Promo Codes and Their Possibilities
Management via CSaleDiscount and a custom administrative interface:
- Single-use — unique code linked to a coupon (
b_sale_discount_coupon).
- Multi-use — shared code with a usage limit via
MAX_USE.
- Personal — linked to
USER_ID.
- Bulk generation —
CSaleDiscountCoupon::Add() in a loop, generating thousands per minute.
Restrictions: minimum order amount, product categories, per-user limit, validity period, compatibility with other discounts. Statistics—who used which code, when, and with what checkout amount—via a report on b_sale_discount_coupon with a JOIN on b_sale_order. Linking to UTM tags shows which channel actually drives conversions.
Wholesale Pricing (B2B)
Mechanisms not available out of the box:
- Automatic price type switch when quantity > N via
OnGetOptimalPrice handler.
- Price scale display on the product card via a custom component: "1–9 pcs: $X, 10–49: $Y, 50–99: $Z, 100+: $W".
- Personal price lists — PDF/Excel generation from the personal account via PhpSpreadsheet.
- Special price request form → lead in CRM.
- Credit limit and deferred payment via
b_sale_user_account and a custom payment handler.
Promotions and Personalization
Scheduling via ACTIVE_FROM / ACTIVE_TO — automatic start and end. Countdown timer — JS component linked to the item's ACTIVE_TO. Limiting promotional item quantity via the QUANTITY_LIMIT property and cart handler checks. A "Promotions" section — via smart filter on the IS_SALE = Y property.
Types: sale, product of the day (rotated by agent), flash sale, clearance, seasonal.
Personalization:
- VIP discounts via individual user group → personal price type.
- Corporate terms: deferred payment, custom delivery.
- Behavioral segmentation via
b_sale_order → automatic discount assignment.
- Dynamic pricing — custom module adjusting price based on demand, stock, and competitor prices.
Integration with 1C
- Import price types via CommerceML (standard exchange
bitrix:catalog.import.1c).
- Sync discount cards: card number → user group → price type.
- Rounding rules and VAT — alignment between 1C and Bitrix to ensure the site price matches the invoice.
- Scheduled updates (cron + agent) or real-time via REST API.
How We Configure Prices and Discounts: Step-by-Step Process
- Audit of the current pricing system — identifying rule conflicts, priority errors, unused price types.
- Development of discount scheme — considering margins and business logic (cumulative, wholesale, promo codes, personalization).
- Cart rule configuration — priorities, flags, exceptions.
- 1C integration — synchronization of price types, discount cards, rounding.
- Testing — load testing with 100+ active rules, conflict checks.
- Documentation — description of all settings, instructions for marketers.
- Manager training — how to create and disable promotions without risk.
- 30-day support — fix any anomalies after launch.
Timelines
| Task |
Timeline |
| Audit and price type setup |
2–3 days |
| Basic cart rules |
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 |
Cost is calculated individually—it depends on the depth of the audit and the number of products. Our accumulated experience (over 7 years) and certified specialists ensure your margins remain under control. Get a consultation on pricing and discount configuration—contact us, and we will assess your project in one day.