Свой обработчик доставки (стоимость доставки по яндекс картам, готовое решение)

Доброейшего вечера/дня/ночи/утра!

Я продолжаю свой пусть, я продолжаю покорять minishop2

Забегая вперед, то что я пишу, больше для меня самого, но если вдруг, кому-то будет полезно, то я буду несказанно счастлив!

Небольшое вступление: и так, стояла задача, научить минишоп рассчитывать стоимость доставки, в зависимости от удаленности.
Т.е. к примеру, клиенту нужно сделать доставку. Удаленность от физического магазина 15 км. Очень хочется, что бы доставка рассчитывалась не руками — менеджером, а автоматически. Собственно до сегодняшнего дня так и было.

И так, мне нужно через яндекс карты построить маршрут, высчитать расстояние, рассчитать стоимость доставки и передать все это дело в минишоп. Звучит просто, если ты понимаешь в программировании. Но это не мой случай! Слава богу, что есть ИИ.
Важно, весь код, который будет здесь описан сгенерировал ИИ.

И так, перво наперво, нужно получить АПИ ключи от яндекса.
Сделать это можно здесь.

https://developer.tech.yandex.ru

Для дальнейшей работы требуется JavaScript API и HTTP Геокодер и API Геосаджеста.

Теперь подрубаем скрипты от яндекса. У меня это сделано в чанке tpl.msOrder

теперь, в этом же чанке я разместил вот такой вот яваскрипт

<script>
ymaps.ready(function () 
{
    // 1. НАХОДИМ ЭЛЕМЕНТ ПО ID
    var addressInput = document.getElementById('text_address');
    
    // 2. Координаты магазина (ЗАМЕНИ НА СВОИ!)
    const SHOP_COORDS = [55.104969, 37.027842];
    
    // 3. Тарифы доставки
    function calculatePrice(km) {
        if (km <= 3) return 200;
        if (km <= 5) return 350;
        if (km <= 10) return 450;
        if (km <= 20) return 550;
        if (km <= 40) return 1100;
        return 1100 + (Math.ceil(km) - 40) * 30;
    }
    
    // 4. ПЕРЕДАЕМ НЕ СТРОКУ, А САМ ЭЛЕМЕНТ
    var suggestView = new ymaps.SuggestView(addressInput, 
    {
        results: 5 // Количество подсказок
    });

    suggestView.events.add('select', function(e) 
    {
        var selectedItem = e.get('item');
        var selectedValue = selectedItem.value;
    
        // ВАЖНО: сначала предотвращаем стандартное поведение blur
        addressInput.addEventListener('blur', function preventBlurHandler(event) 
        {
            event.stopImmediatePropagation();
            event.preventDefault();
            console.log('Blur заблокирован');
        }, { once: true }); // Сработает только один раз
        
        // Устанавливаем значение
        addressInput.value = selectedValue;
        
        // Форсируем события
        ['change', 'input'].forEach(function(eventType) 
        {
            addressInput.dispatchEvent(new Event(eventType, { bubbles: true }));
        });
        
        // 5. РАСЧЕТ СТОИМОСТИ
        ymaps.geocode(selectedValue, { results: 1 }).then(function(res) {
            const geoObject = res.geoObjects.get(0);
            if (!geoObject) {
                console.error('Адрес не найден');
                return;
            }
            
            const userCoords = geoObject.geometry.getCoordinates();
            
            ymaps.route([SHOP_COORDS, userCoords]).then(function(route) {
                const distanceKm = route.getLength() / 1000;
                const price = calculatePrice(distanceKm); // ВАША ПЕРЕМЕННАЯ price
                
                console.log('Адрес выбран - добавляем ' + price + ' руб. к доставке');
                
                // 1. Сохраняем стоимость через штатный механизм MiniShop2
                if (typeof miniShop2 !== 'undefined') {
                    miniShop2.Order.add('delivery_extra_cost', price);
                }
                
                // 2. Показываем пользователю
                if ($('#extra_cost_display').length) {
                    $('#extra_cost_display').show();
                    $('#extra_cost_value').text(price);
                }
                
                // 3. Обновляем стоимость
                setTimeout(function() {
                    if (typeof miniShop2 !== 'undefined') {
                        miniShop2.Order.getcost();
                        console.log('✅ Стоимость отправлена, getcost() вызван');
                    }
                }, 300);
                
                // Дополнительно: показываем в блоке
                if (document.getElementById('distance_result')) {
                    document.getElementById('distance_result').innerHTML = 
                        'Стоимость доставки: ' + price + ' руб';
                }
                
            }).catch(function(error) {
                console.error('Ошибка расчета маршрута:', error);
            });
            
        }).catch(function(error) {
            console.error('Ошибка геокодирования:', error);
        });
    });
});
</script>
так же у меня в чанке присуствует вот такое:
<div id='distance_result' name='distance_result'></div>

Это нужно, если вы хотите дополнительно клиенту обозначить стоимость доставки, либо если произошла ошибка расчета. Т.е. это я к тому, что можно и не добавлять эту строчку.

И, так, на стороне клиента все готово, теперь переходим к серверу.
И если, этот яваскрипт на пару с дипсиком (ИИ), мы написали буквально за пару минут, то дальнейшие манипуляции отняли несколько дней.

https://docs.modx.pro/components/minishop2/development/services/connection

Вот ссылка на, насколько я понимаю, официальную документацию Минишоп2. Но, увы, это устаревшая документация, и об этом ни где ничего не сказано. Увы!

Ну да, обо всем по порядку.

Нужно подрубить свой собственный обработчик доставки.

Если сделать, все как описано в инструкции, то при заходе в настройки доставки, т.е. там где нужно выбрать свой обработчик, получаем ошибку 500, модикс так и пишет: «ошибка 500».
Собственно на устранение ошибки ушел целый рабочий день.
А вся проблема в том, что
в примере из руководства, собственный обработчик начинается с

if (!class_exists('msDeliveryInterface')) {
  require_once dirname(dirname(dirname(__FILE__))) . '/model/minishop2/msdeliveryhandler.class.php';
}
Но это не правильно. У меня версия минишопа minishop2-4.4.2-pl
а в это версии, и я думаю, что и относительно свежих предыдущих версиях, этот файл лежит в другом месте, а именно:
if (!class_exists('msDeliveryInterface')) {
    require_once MODX_CORE_PATH . 'components/minishop2/handlers/msdeliveryhandler.class.php';
}
И, так, вот полный скрипт обработчика, который у меня находится вот здесь:
core/components/minishop2/custom/delivery/my_msdeliveryhandler.class.php

<?php
if (!class_exists('msDeliveryInterface')) {
    require_once MODX_CORE_PATH . 'components/minishop2/handlers/msdeliveryhandler.class.php';
}

class my_msDeliveryHandler extends msDeliveryHandler implements msDeliveryInterface 
{
    public function getCost(msOrderInterface $order, msDelivery $delivery, $cost = 0.0) 
    {
        // 1. Берём базовую стоимость доставки (из настроек способа)
        $baseCost = parent::getCost($order, $delivery, $cost);
        
        // 2. Получаем доп. стоимость из сессии
        $extraCost = 0;
        $props = $order->get('properties');
        
        if (isset($props['delivery_extra_cost'])) {
            $extraCost = (float)$props['delivery_extra_cost'];
        } elseif (isset($_SESSION['minishop2']['order']['delivery_extra_cost'])) {
            $extraCost = (float)$_SESSION['minishop2']['order']['delivery_extra_cost'];
        }
        
        // 3. Складываем: базовая + доп. стоимость
        $totalCost = $baseCost + $extraCost;
        
        // Логируем для отладки
        $this->modx->log(modX::LOG_LEVEL_INFO, 
            "Доставка: базовая=$baseCost, доп=$extraCost, итого=$totalCost");
        
        return $totalCost;
    }
}
ну и собственно на этом почти все, согласно инструкции, нужно еще зарегистрировать свою службу в модиксе.
Я делал это из консоли (уж не знаю, как другие, а для меня это было открытием. Консоль — это доп пакет, который можно установить из магазина приложений модикса).

В консоли нужно выполнить следующий код:
if ($miniShop2 = $modx->getService('miniShop2')) {
  $miniShop2->addService('deivery', 'название своей службы',
      '{core_path}components/msprofile/model/msprofile/customeraccount.class.php');
}
Ну и дело за малым, нужно зайти в настройки минишопа, в раздел доставок, и в нужной выбрать свой обработчик.

Тадаааан! =)))

Вроде ничего не забыл!=)

Всех с наступающим!
Дмитрий
28 декабря 2025, 18:36
modx.pro
44
0

Комментарии: 0

Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
0