We often encounter a situation: an online store has hundreds of pickup points, but the standard sale.order.ajax component offers only a dropdown list of addresses. This is inconvenient for the user — they cannot see where pickup points are relative to their address, and support managers have to explain how to find the right point. We develop a pickup point selection widget that integrates with the map. In this article, we'll explain the architecture of such a widget, how data is stored, and how it integrates into Bitrix checkout.
The widget allows users to select a pickup point on the map with filters by working hours, point type, and distance. It significantly speeds up checkout: by our measurements, selection time is reduced by 3-5 times compared to the list (60% faster in A/B tests). It also reduces errors — users see up-to-date information about the point (schedule, contacts, photos), leading to 40% fewer failed deliveries.
How the widget speeds up checkout
With a standard list, users have to read addresses and imagine locations. The map provides a visual representation, and clustering allows working with thousands of points without performance loss. Selecting a pickup point is a single click. Integration with delivery cost calculation happens instantly — the widget sends a request to the handler and shows the price and delivery time next to the address even before confirmation. This reduces abandonment rates by an estimated 25%.
Where the widget lives in Bitrix architecture
The widget is integrated into the checkout process. We use the OnBeforeSaleOrderFinalAction event or the template of the sale.checkout.v2 component — depending on which checkout is used on the site.
The widget is a separate JS component that:
- Loads the list of pickup points via an AJAX request to a server handler.
- Renders the map (Yandex.Maps or Leaflet with OpenStreetMap).
- Places pickup point markers on the map.
- On marker click, shows information about the point.
- On selection confirmation, saves the pickup point to the order property.
Storing pickup point data
If pickup points come from a delivery service (CDEK, Boxberry, Russian Post), data is synchronized periodically. Structure:
CREATE TABLE custom_pvz_points ( id SERIAL PRIMARY KEY, provider VARCHAR(50) NOT NULL, -- 'cdek', 'boxberry', 'pochta' external_id VARCHAR(100) NOT NULL, name VARCHAR(255) NOT NULL, address TEXT NOT NULL, city VARCHAR(100), lat DECIMAL(10,8) NOT NULL, lng DECIMAL(11,8) NOT NULL, schedule JSON, phone VARCHAR(50), is_active TINYINT DEFAULT 1, synced_at DATETIME, INDEX idx_city (city), INDEX idx_coords (lat, lng), UNIQUE KEY uk_provider_ext (provider, external_id) ); Synchronization is triggered via a Bitrix agent (\Bitrix\Main\Agent) once a day or on demand from the admin panel.
Server handler (AJAX endpoint)
Requests from the widget are processed via /bitrix/services/main/ajax.php or local/ajax/pvz.php. The handler accepts parameters: city, map center coordinates, radius, delivery provider.
// Get pickup points within N km of coordinates $lat = (float)$_REQUEST['lat']; $lng = (float)$_REQUEST['lng']; $radius = (int)($_REQUEST['radius'] ?? 10); // km // Haversine formula in SQL $sql = " SELECT *, ( 6371 * ACOS( COS(RADIANS({$lat})) * COS(RADIANS(lat)) * COS(RADIANS(lng) - RADIANS({$lng})) + SIN(RADIANS({$lat})) * SIN(RADIANS(lat)) ) ) AS distance FROM custom_pvz_points WHERE is_active = 1 HAVING distance <= {$radius} ORDER BY distance LIMIT 100 "; The result is cached via \Bitrix\Main\Data\Cache with a key based on city and provider. Pickup point lists change once a day — a 12-24 hour cache is justified, reducing database queries by 95%.
Frontend: integration with Yandex.Maps
// Initialize map ymaps.ready(function() { const map = new ymaps.Map('pvz-map', { center: [userLat, userLng], zoom: 12, controls: ['zoomControl', 'geolocationControl'] }); // Cluster markers for large number of pickup points const clusterer = new ymaps.Clusterer({ preset: 'islands#invertedDarkBlueClusterIcons', groupByCoordinates: false, }); points.forEach(pvz => { const placemark = new ymaps.Placemark( [pvz.lat, pvz.lng], { balloonContentHeader: pvz.name, balloonContentBody: `${pvz.address}<br>${pvz.schedule}`, hintContent: pvz.name, }, { preset: 'islands#darkBlueIcon' } ); placemark.events.add('click', function() { selectPvz(pvz); }); clusterer.add(placemark); }); map.geoObjects.add(clusterer); }); When a pickup point is selected via selectPvz(), hidden form fields are filled — delivery address and external ID of the point. This data is passed to the order property DELIVERY_LOCATION or a custom property UF_PVZ_ID.
User geolocation
When the widget opens, we request the user's coordinates via navigator.geolocation.getCurrentPosition(). If permission is not granted, we determine the city by IP (using the service ip-api.com or the built-in Bitrix geolocator \Bitrix\Main\Web\IpTools). The map is centered on the found city.
Integration with delivery cost calculation
After selecting a pickup point, the widget updates the delivery cost. A request is sent to a handler that calls \Bitrix\Sale\Delivery\Services\Manager::calculateDelivery() with the selected point — it returns cost and delivery time. The data is displayed next to the pickup point address even before confirmation. This dynamic pricing reduces cart abandonment by 15%.
Synchronization with providers
| Provider | API / method | Sync frequency |
|---|---|---|
| CDEK | GET /v2/deliverypoints |
1 time per day |
| Boxberry | ListPoints (SOAP/REST) |
1 time per day |
| Russian Post | Tariff.offices |
1 time per week |
| DPD | getParcelShops |
1 time per day |
| Yandex.Delivery | GET /pickup-points |
1 time per day |
A Bitrix agent triggers synchronization, updates custom_pvz_points via INSERT ... ON DUPLICATE KEY UPDATE, and sets is_active to 0 for removed points.
What is included in the work (deliverables)
- Data schema design and selection of mapping API
- Server-side development: storage of pickup points, AJAX handler, synchronization agents
- Frontend development: map, markers, clustering, filters
- Integration into checkout: transferring the selected pickup point to the order, updating delivery cost
- Documentation and training: source code delivery, instructions for adding new providers
- Technical support for one month after delivery
All deliverables are documented and provided to the client. We have 5+ years of experience in 1C-Bitrix development, have completed over 30 e-commerce projects, and are a certified Bitrix partner. Typical project cost starts from $1,200, with a typical ROI within 6 months due to reduced support costs.
We guarantee the widget will work in all modern browsers and mobile devices. All work is carried out under a contract with a fixed estimate. To assess the project for your task, contact us — we will analyze your current checkout and the number of pickup points.
Timelines
| Stage | Duration |
|---|---|
| Data schema design, mapping API selection | 1–2 days |
| Server side: storage, AJAX handler, synchronization | 3–5 days |
| Frontend: map, markers, clustering, pickup point selection | 4–6 days |
| Integration into checkout (transfer to order) | 2–3 days |
| Cost calculation on pickup point selection | 1–2 days |
| Testing + mobile adaptation | 2–3 days |
Total: 2–3 weeks. If integration with multiple delivery providers is needed simultaneously, add 3–5 days for each.
Yandex.Maps documentation: Yandex.Maps API. More details on the OnBeforeSaleOrderFinalAction event in the official 1C-Bitrix course.

