Как настроить автозаполнение местоположения в заказе bitrix:sale.order.ajax

Внимание! Работает режим премодерации. Все сообщения публикуются после проверки!
Страницы: 1
Ответить
RSS
Как настроить автозаполнение местоположения в заказе bitrix:sale.order.ajax
Автозаполнение местоположения пользователя в bitrix:sale.order.ajax: определяем город и подставляем его в заказ

В интернет-магазине каждый лишний клик снижает конверсию. Один из самых  раздражающих моментов — выбор города в форме оформления заказа. В Bitrix  bitrix:sale.order.ajax это свойство типа LOCATION.  В статье показываю, как автоматически подставлять местоположение  покупателя в это поле: сначала — полный рабочий код (он ниже), затем  разберём логику, тонкости и еще один более надёжный, «битриксовый»  апгрейд.
Изменено: Валерий Макеев - 29.09.2025 14:33:35
Автоматически подставляет город в поле LOCATION при оформлении заказа, определяя его по IP-адресу клиента через встроенный GeoIP-менеджер Битрикс, но только если пользователь ещё не выбрал город вручную.
Код
<?php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;
use Bitrix\Main\Service\GeoIp\Manager;
use Bitrix\Sale\Location\LocationTable;

AddEventHandler('sale', 'OnSaleComponentOrderProperties', 'autoFillLocationByGeoIp');

function autoFillLocationByGeoIp(&$arUserResult, $request, &$arParams, &$arResult)
{
    foreach ($arUserResult['ORDER_PROP'] as $propertyId => $value) {
        $property = \CSaleOrderProps::GetList([], ['ID' => $propertyId])->Fetch();
        if ($property['TYPE'] === 'LOCATION' && empty($request->getPost("ORDER_PROP_{$propertyId}"))) {
            static $cityCode = null;
            if ($cityCode === null) {
                $ip = $_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'];
                $geoData = Manager::getDataResult($ip, LANGUAGE_ID);
                if ($geoData->isSuccess() && $cityName = $geoData->getGeoData()->cityName) {
                    $location = LocationTable::getList([
                        'select' => ['CODE'],
                        'filter' => [
                            '=NAME.NAME' => $cityName,
                            'LANGUAGE_ID' => LANGUAGE_ID,
                            '=TYPE.CODE' => 'CITY'
                        ],
                        'limit' => 1
                    ])->fetch();
                    $cityCode = $location['CODE'] ?? false;
                } else {
                    $cityCode = false;
                }
            }
            if ($cityCode) {
                $arUserResult['ORDER_PROP'][$propertyId] = $cityCode;
            }
        }
    }
}
Обновленная версия скрипта
Код
EventManager::getInstance()->addEventHandlerCompatible(
    'sale',
    'OnSaleComponentOrderCreated',
    [SaleOrderEvents::class, 'fillLocation']
);

class SaleOrderEvents
{
    public static function fillLocation(
        Order $order,
        array &$arUserResult,
        HttpRequest $request,
        array &$arParams,
        array &$arResult,
        array &$arDeliveryServiceAll,
        array &$arPaySystemServiceAll
    ): void {
        if (!Loader::includeModule('sale')) {
            return;
        }

        $propertyCollection = $order->getPropertyCollection();
        $locationProperty = null;
        $locationPropertyId = 0;

        foreach ($propertyCollection as $property) {
            if ($property->isUtil()) {
                continue;
            }

            $propertyData = $property->getProperty();
            if (($propertyData['TYPE'] ?? '') === 'LOCATION') {
                $locationProperty = $property;
                $locationPropertyId = (int) $propertyData['ID'];
                break;
            }
        }

        if (!$locationProperty || $locationPropertyId <= 0) {
            return;
        }

        if (self::hasPostedLocation($request, $locationPropertyId)) {
            return;
        }

        $locationCode = self::resolveUserLocationCode();
        if (!$locationCode) {
            return;
        }

        $locationRow = self::getLocationByCode($locationCode);
        if (!$locationRow) {
            return;
        }

        $locationProperty->setValue($locationCode);

        $locationId = (int) $locationRow['ID'];
        if ($locationId > 0) {
            $arUserResult['ORDER_PROP'][$locationPropertyId] = $locationId;
            $arUserResult['DELIVERY_LOCATION'] = $locationId;
        }

        $arUserResult['RECREATE_ORDER'] = 'Y';
        $arUserResult['CALCULATE_PAYMENT'] = 'Y';
    }

    private static function hasPostedLocation(HttpRequest $request, int $propertyId): bool
    {
        $flatValue = trim((string) $request->getPost('ORDER_PROP_' . $propertyId));
        if ($flatValue !== '') {
            return true;
        }

        $orderPost = $request->getPost('order');
        if (is_array($orderPost)) {
            $nestedValue = trim((string) ($orderPost['ORDER_PROP_' . $propertyId] ?? ''));
            if ($nestedValue !== '') {
                return true;
            }
        }

        return false;
    }

    private static function resolveUserLocationCode(): ?string
    {
        $codeFromProfile = self::getLocationCodeFromUserProfile();
        if ($codeFromProfile) {
            return $codeFromProfile;
        }

        $ip = self::getClientIp();
        if (!$ip) {
            return null;
        }

        $geoData = self::getCityByIp($ip);
        if (!$geoData || empty($geoData['city'])) {
            return null;
        }

        $location = self::findCityLocationByName((string) $geoData['city']);
        if (!$location) {
            return null;
        }

        return (string) $location['CODE'];
    }

    private static function getLocationCodeFromUserProfile(): ?string
    {
        global $USER;

        if (!is_object($USER) || !$USER->IsAuthorized()) {
            return null;
        }

        $userId = (int) $USER->GetID();
        if ($userId <= 0) {
            return null;
        }

        $userData = UserTable::getList([
            'select' => ['ID', 'PERSONAL_CITY'],
            'filter' => ['=ID' => $userId],
            'limit' => 1,
        ])->fetch();

        if (!$userData || empty($userData['PERSONAL_CITY'])) {
            return null;
        }

        $personalCity = trim((string) $userData['PERSONAL_CITY']);
        if ($personalCity === '') {
            return null;
        }

        if (ctype_digit($personalCity)) {
            $location = LocationTable::getList([
                'filter' => [
                    '=ID' => (int) $personalCity,
                    '=NAME.LANGUAGE_ID' => LANGUAGE_ID,
                ],
                'select' => ['ID', 'CODE', 'NAME_RU' => 'NAME.NAME'],
                'limit' => 1,
            ])->fetch();

            if ($location && !empty($location['CODE'])) {
                return (string) $location['CODE'];
            }
        }

        $location = self::findCityLocationByName($personalCity);
        if ($location && !empty($location['CODE'])) {
            return (string) $location['CODE'];
        }

        return null;
    }

    private static function findCityLocationByName(string $cityName): ?array
    {
        $cityName = self::normalizeCityName($cityName);
        if ($cityName === '') {
            return null;
        }

        $location = LocationTable::getList([
            'filter' => [
                '=NAME.LANGUAGE_ID' => LANGUAGE_ID,
                '=TYPE.CODE' => ['CITY', 'VILLAGE'],
                '=NAME.NAME' => $cityName,
            ],
            'select' => [
                'ID',
                'CODE',
                'NAME_RU' => 'NAME.NAME',
                'TYPE_CODE' => 'TYPE.CODE',
            ],
            'limit' => 1,
        ])->fetch();

        if ($location) {
            return $location;
        }

        $location = LocationTable::getList([
            'filter' => [
                '=NAME.LANGUAGE_ID' => LANGUAGE_ID,
                '=TYPE.CODE' => ['CITY', 'VILLAGE'],
                '%NAME.NAME' => $cityName,
            ],
            'select' => [
                'ID',
                'CODE',
                'NAME_RU' => 'NAME.NAME',
                'TYPE_CODE' => 'TYPE.CODE',
            ],
            'limit' => 1,
        ])->fetch();

        return $location ?: null;
    }

    private static function getLocationByCode(string $code): ?array
    {
        $row = LocationTable::getByCode($code, [
            'filter' => [
                '=NAME.LANGUAGE_ID' => LANGUAGE_ID,
            ],
            'select' => [
                'ID',
                'CODE',
                'NAME_RU' => 'NAME.NAME',
            ],
        ])->fetch();

        return $row ?: null;
    }

    private static function getClientIp(): ?string
    {
        $request = Application::getInstance()->getContext()->getRequest();
        $ip = trim((string) $request->getRemoteAddress());

        return $ip !== '' ? $ip : null;
    }

    private static function getCityByIp(string $ip): ?array
    {
        $httpClient = new HttpClient([
            'socketTimeout' => 3,
            'streamTimeout' => 3,
        ]);

        $response = $httpClient->get('http://ip-api.com/json/' . urlencode($ip) . '?lang=ru');
        if (!$response) {
            return null;
        }

        $data = json_decode($response, true);
        if (!is_array($data)) {
            return null;
        }

        if (($data['status'] ?? '') !== 'success') {
            return null;
        }

        return $data;
    }

    private static function normalizeCityName(string $cityName): string
    {
        $cityName = trim($cityName);
        $cityName = preg_replace('/\s+/u', ' ', $cityName);

        return trim((string) $cityName);
    }
}
Страницы: 1
Ответить
Форма ответов
Текст сообщения*
Перетащите файлы
Ничего не найдено
Файл
Загрузить картинки
 

Стоимость услуг по разработке и сопровождению сайтов на 1C-Битрикс

Интернет-магазин на готовом решении

от 7 дней

от 40 000 рублей
запуск сайта в максимально короткие сроки

* указана минимальная стоимость. Стоимость выбранной лицензии «1С-Битрикс» оплачивается отдельно.

Перенос сайтов на «1С-Битрикс»

сайты на платформе «1С-Битрикс» — это удобство, надежность и высокая посещаемость

от 12 000 рублей
Перенос сайтов с любых CMS и статичных страниц на платформу «1С-Битрикс», с учетом дизайна, верстки и урл-адресов. С сохранением всей информации и структуры сайта.

* зависит от объема выполняемых работ.

Лендинг

от 3 дней

от 25 000 рублей

Разработка одностраничного сайта на платформе Битрикс

* стоимость зависит от наличия верстки, использования готового решения и т.д.