Developing a widget for selecting pickup points on a 1C-Bitrix map

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Development of Pickup Point Selection Widget on Map for 1C-Bitrix

When placing an order, users should select the nearest pickup point — on a map, with filters by opening hours, pickup type, distance from address. The standard sale.order.ajax component offers only a dropdown list with addresses. This works for 10 points, but becomes inconvenient with 500+. A widget with a map solves this problem.

Where the Widget Lives in Bitrix Architecture

The widget is embedded in the order placement process. We use the OnBeforeSaleOrderFinalAction event or the sale.checkout.v2 component template — depending on which checkout is used on the site.

The widget is a separate JS component that:

  1. Loads the PVZ list via AJAX request to a server handler.
  2. Renders the map (Yandex.Maps or Leaflet with OpenStreetMap).
  3. Places PVZ markers on the map.
  4. Shows PVZ information on marker click.
  5. On selection confirmation, writes PVZ to order property.

Storing Pickup Point Data

If PVZ comes 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 Bitrix agent (\Bitrix\Main\Agent) once a day or on request from the admin panel.

Server Handler (AJAX Endpoint)

Requests from the widget are handled via /bitrix/services/main/ajax.php or local/ajax/pvz.php. The handler accepts parameters: city, map center coordinates, radius, delivery provider.

// Get PVZ within N km radius from 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
";

Result is cached via \Bitrix\Main\Data\Cache with key by city and provider. PVZ list changes once a day — 12–24 hour cache is justified.

Frontend: Integration with Yandex.Maps

// Map initialization
ymaps.ready(function() {
    const map = new ymaps.Map('pvz-map', {
        center: [userLat, userLng],
        zoom: 12,
        controls: ['zoomControl', 'geolocationControl']
    });

    // Clustered markers for large number of PVZ
    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 selecting a PVZ via selectPvz(), hidden order form fields are filled — delivery address and external point ID. This data is passed to order property DELIVERY_LOCATION or custom property UF_PVZ_ID.

User Geolocation

When opening the widget, request user coordinates via navigator.geolocation.getCurrentPosition(). If permission is not granted — determine city by IP (service ip-api.com or built-in Bitrix geolocation \Bitrix\Main\Web\IpTools). Map centers on the found city.

Integration with Delivery Cost Calculation

After selecting a PVZ, the widget updates delivery cost. Request to handler calling \Bitrix\Sale\Delivery\Services\Manager::calculateDelivery() with selected point — returns cost and timeframe. Data is shown next to PVZ address before confirmation.

Synchronization with Providers

Provider API / Method Sync Frequency
CDEK GET /v2/deliverypoints Once a day
Boxberry ListPoints (SOAP/REST) Once a day
Russian Post Tariff.offices Once a week
DPD getParcelShops Once a day
Yandex.Delivery GET /pickup-points Once a day

Bitrix agent triggers synchronization, updates custom_pvz_points via INSERT ... ON DUPLICATE KEY UPDATE, removes is_active flag from disappeared points.

Timeline

Stage Duration
Data schema design, cartographic API selection 1–2 days
Server side: storage, AJAX handler, synchronization 3–5 days
Frontend: map, markers, clustering, PVZ selection 4–6 days
Integration into checkout (pass to order) 2–3 days
Cost calculation on PVZ selection 1–2 days
Testing + mobile adaptation 2–3 days

Total: 2–3 weeks. If integration with multiple delivery providers simultaneously — add 3–5 days for each.