Cart and Checkout on 1C-Bitrix
Why sale.order.ajax Is Conversion's Worst Enemy
The standard sale.order.ajax is Bitrix's built-in checkout component. Multi-step: shipping → payment → confirmation. Each transition loses 10-15% of users. Three steps — and a third of shoppers who already added items to the cart are gone. Not because they changed their minds — because the interface tripped them up.
On top of that, sale.order.ajax throws a 500 error if at least one delivery handler isn't configured. Or hangs for 15 seconds calculating CDEK rates because the API request is synchronous with no timeout. Or demands a tax ID from an individual because the order property isn't split by payer type. Every case like this means direct losses.
We rebuild checkout with a single focus — conversion. Minimum steps, maximum convenience, reliable payment and delivery integrations.
Single-Step Checkout: Everything on One Screen
All fields on a single page. Logical grouping, no unnecessary transitions.
- Contact details — name, phone, email. Three fields. Not five, not ten, not "enter your date of birth for the loyalty program"
- Shipping — selected city → available methods with prices and delivery times. AJAX calculation via CDEK, Boxberry, Russian Post APIs. Parallel requests with a 3-second timeout — if one API hangs, the rest still show up
- Payment — methods filtered by selected shipping option. Cash on delivery for pickup? Not shown
- Promo code — field is visible, validation is instant, discount is reflected in the total immediately
- Total — dynamic recalculation on any change. Changed quantity → subtotal → shipping cost → total. No page reloads
Under the Hood
- Fully AJAX — zero page reloads. The component works through
Bitrix\Sale\Order::create()and REST, not the standardsale.order.ajax - Real-time validation: not "fill in the field correctly" but "phone number: +7 (__) --".
inputmaskmasking + server-side validation - Data preservation on accidental navigation —
sessionStoragekeeps entered data, everything's intact on return - Address autocomplete via DaData: start typing a street → full address with postal code, FIAS code, and coordinates. Fewer errors on the courier service side
- Order property support by payer type — individuals see one set of fields, legal entities see another. Toggle switch in the form
Guest Checkout: Kill Mandatory Registration
"I want to buy a USB cable for 300 rubles, and they're asking me to come up with a password of 8 characters with an uppercase letter and a special character." Mandatory registration kills 25-30% of conversion on small orders.
- Purchase without an account — process through
CSaleUser::GetAnonymousUserID()or create a user automatically with a random password - After checkout — an email with login credentials. Want to activate the account — great; don't want to — they'll still get their order
- Return visit — identify by email or phone, link to existing account
- Authentication right in checkout: SMS code instead of password — via
Bitrix\Main\Authentication\ShortCodeor SMS gateway integration
Cross-Sell: Upsells That Don't Annoy
In the Cart
Recommendations based on real data from b_sale_basket — "customers also bought" using association rules, not random products.
- Accessories: phone → case and screen protector. Linked via infoblock property
PROPERTY_ACCESSORIES - Bulk incentive: "Buy 3 — save 15%." Implemented through cart rules in
b_sale_discount - Free shipping threshold: "Add 500 more — free shipping." Simple widget that increases average order value by 10-20%
Admin Panel Management
A manager links recommended products manually or enables automatic algorithms. Display rules: category, price range, availability. A/B testing of different strategies — no developer needed.
Abandoned Carts: Recovering 15-20% of Lost Customers
Persistence
- Authorized users — cart in
b_sale_basket, accessible from any device - Guests — cookie with 30-day TTL.
FUSER_IDis tied to the cookie, the cart won't disappear after an hour - Synchronization: added from phone, checked out from laptop — single cart
Recovery
- Email series: 3 emails. After 1 hour — reminder. After 24 hours — "your item is running out." After 72 hours — personal promo code for 5-10%. Via
sale.basketcomponent+CEvent::Send()with deferred sending through agents - Browser push notifications —
Notification API, subscription via service worker - Retargeting — cart data is sent to Yandex.Direct via eCommerce events
Drop-Off Analytics
At which step do they leave? If at shipping selection — shipping price is shocking. If at payment — card is declined, 3D-Secure fails. Payment system errors are caught through YooKassa/CloudPayments callbacks and logged — we see the exact decline percentage by cause.
Promo Codes: Proper Implementation
| Type | Bitrix Mechanism | Gotcha |
|---|---|---|
| Fixed discount | CSaleDiscount, type "on order" |
Don't forget to set minimum order amount — otherwise a 500₽ discount on a 300₽ order |
| Percentage | CSaleDiscount, condition "coupon" |
Cap the maximum discount — otherwise on a 500K order, 50% discount = 250K |
| Free shipping | Cart rule + delivery service binding | Only works with specific services — can't give free "any" shipping |
| Gift | Auto-add product to cart via handler | Gift product must be in stock, otherwise the cart breaks |
Promo code UX:
- Field is visible but doesn't scream — doesn't distract those who don't have a code
- Instant validation: "Promo code expired" / "Minimum order 3000₽" — not "Error 422"
- Discount shown as a separate line in the order summary
- Can remove a promo code and apply a different one
UX Optimization: Small Details That Make the Difference
Desktop:
- Progress bar — user sees where they are
- Smart defaults — most popular shipping method is already selected (determined from
b_sale_orderstatistics) - Minimum required fields — only what's needed to place the order. Middle name? Optional. Comment? Optional
- Recalculation without 5-second loaders — 300ms debounce on AJAX requests
Mobile:
- Large buttons — no finger misses.
min-height: 48pxper Google guidelines - Correct keyboard types:
type="tel"for phone,inputmode="numeric"for quantity - "Place Order" button pinned to the bottom —
position: sticky - Collapsible sections — screen space on 375px is precious
Error handling:
- "Check your card number" instead of "Payment processing error"
- Auto-scroll to first error —
scrollIntoView({ behavior: 'smooth' }) - "Item out of stock" — handled in checkout without losing filled data. Suggest an alternative or remove from cart with recalculation
Integrations
- DaData — address, full name, tax ID. Suggestions as you type, FIAS validation
- Yandex.Maps — pickup point selection on map, geolocation for city detection
- CDEK, Boxberry, Russian Post — real-time API calculation of cost and delivery times
- YooKassa, CloudPayments, Tinkoff — payment processing, recurring charges, holds
- CRM — order automatically goes to Bitrix24, a deal is created linked to the contact
-
Warehouse — real-time stock check via
CCatalogStoreProduct::GetList()
Timelines
| Task | Timeline |
|---|---|
| Current checkout optimization | 1-2 weeks |
| Single-step checkout from scratch | 3-5 weeks |
| Promo code system | 1-2 weeks |
| Cross-sell in cart | 1 week |
| Abandoned cart mechanism | 2-3 weeks |
| Comprehensive overhaul | 6-10 weeks |
A 1-2% checkout conversion increase with stable traffic means revenue growth without increasing the ad budget. The fastest ROI in e-commerce.







