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.

