[СДЕЛАЙ САМ] Интеграция по API калькулятора расчёта доставки от Logsis

Дальше идёт работающее решение для сайта blackbuffalo.ru/ (это чтобы посмотреть как работает), но оно мне не нравится изначально я хотел расширить класс доставки, как говорится «пацан к успеху шёл. не повезло. не фартануло». А теперь к делу. API сервиса простое, из нюансов могу отметить только то, что комиссия за наложенный платёж прибавляется только в личном кабинете Logsis, на сайт её не затащить, поэтому сумма на сайте будет отличаться от суммы в ЛК процентов на 8-10. Для более точных расчётов можно передавать параметр os (оценочная стоимость), но мой заказчик пожелал этого не делать. Переходим к коду.Начту на мой взгляд с самого узкого места это js. Во-первых проблему с автозаполнением полей в Chrome мне решить не удалось, там (в браузере) есть такая функция, которая заполняет поля пачками, например пользователь ранее заполнял уже где-то телефон и адрес, вот Chrome по телефону подтянет и адрес, при этом калькуляция не производится и если обновить страницу, то некоторые поля адреса пустеют. В общем, кто не понимает о чём речь, го на сайт по ссылке в самом верху заметки. А в целом я постарался максимально интегрироваться в родной js от minishop2 и правил исходники (знаю, нехорошо), но я правил так, чтобы при обновлении не затёрлось, благо Василий предусмотрел. Значит, делаем копию файла assets/components/minishop2/js/web/dafault.js и переименовываем, я назвал custom.js и в нём пишем примерно такую функцию
function deliveryCalc(){
        //собираем адрес в одну строку
        var region = $('input[name="region"]').val(),
            city = $('input[name="city"]').val(),
            street = $('input[name="street"]').val(),
            building = $('input[name="building"]').val(),
            address = region + ',' + city + ',' + street + ',' + building;
         
    //отправляем на сервер       
        $.post(document.location.href, {'action':'calculate', 'address':address}, function(response){
            response = JSON.parse(response);
            $('.delivery_cost').text(miniShop2.Utils.formatPrice(response.delivery_cost)); //показываем стоимость доставки
            $('#ms2_order_cost').text(miniShop2.Utils.formatPrice(response.total_cost)); //показываем общую стоимость
            if(!response.success){
                $('.delivery_error').text(response.message); //показываем ошибку если доставка не рассчиталась
            }else{
                $('.delivery_error').text(''); //удаляем текст ошибки если всё ок.
            }
        });
    }
Я написал «примерно такую» потому что логика работы с полученными после расчёта данными может отличаться, но главное функция должна передавать на сервер данные для расчёт доставки, а именно адрес. Вызов функции располагаем внутри метода getcost примерно на 458 строке файла custom.js
getcost: function () {
            var callbacks = miniShop2.Order.callbacks;
            callbacks.getcost.response.success = function (response) {
                $(miniShop2.Order.orderCost, miniShop2.Order.order).text(miniShop2.Utils.formatPrice(response.data['cost']));
                
                /* custom code */
                 deliveryCalc(); //вот тут
            };
            var data = {};
            data[miniShop2.actionName] = 'order/getcost';
            miniShop2.send(data, miniShop2.Order.callbacks.getcost, miniShop2.Callbacks.Order.getcost);
        },
Чтобы на сервере данные обработать, я написал сниппет ajaxReceiver, идея взята из этой заметки, располагаю его в самом верху страницы сразу после <!doctype html>.
<?php
//$modx->log(1, 'Request type ' . $_SERVER['REQUEST_METHOD']);
// Откликаться будет ТОЛЬКО на ajax запросы
if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}

// Сниппет будет обрабатывать не один вид запросов, поэтому работать будем по запрашиваемому действию
// Если в массиве POST нет действия - выход
//$modx->log(xPDO::LOG_LEVEL_ERROR, 'Массив ПОСТ ' . print_r($_POST, 1));
if (empty($_POST['action'])) {return;}

// А если есть - работаем
$res = '';
$action = $_POST['action'];
//$modx->log(xPDO::LOG_LEVEL_ERROR, 'Экшн ' . $action);
switch ($action) {
	case 'calculate':
        $res = $modx->runSnippet('getDeliveryCost');
	    break;	
	// А вот сюда потом добавлять новые методы prodFastView
}

// $modx->log(xPDO::LOG_LEVEL_ERROR, 'Результат ' . $res);
// Если у нас есть, что отдать на запрос - отдаем и прерываем работу парсера MODX
if (!empty($res)) {
	die($res);
}
Этот сниппет ловит нужный ajax и запускает другой сниппет, который я назвал getDeliveryCost.
<?php
$ms2 = $modx->getService('miniShop2');
$ms2->initialize($modx->context->key);
$cart =  $ms2->cart->get(); //нужна корзина чтобы получить общую стоимость
$data = $ms2->order->get();
//$modx->log(1, print_r($cart,1));
$cartTotal = $ms2->cart->status();
$cartTotalCost = $cartTotal['total_cost'];
$delivery_cost = 0;
// этот кусок кода нужен потому что в $data адрес приходит кусками, а иногда целиком, в общем я логику не уловил 
// и подстраховался - передаю адрес сам, если его вдруг нет, проверяю в $data.
if(isset($_POST['address'])){
    $address = $_POST['address'];
}else{
    $region = $data['region'];
    $city = $data['city'];
    $street = $data['street'];
    $building = $data['building'];
    $address = $region .','. $city .','. $street .','. $building;
}

$res = array(
    'success' => false,
    'message' => 'Стоимость доставки не расcчитана. Проверьте корректность адреса',
    'delivery_cost' => $delivery_cost,
    'total_cost' => $cartTotalCost + $delivery_cost
    );
//$modx->log(1, print_r($data,1));
//$modx->log(1, print_r($address,1));
if($address){    
    $dimension_side1 = 0;
    $dimension_side2 = 0;
    $dimension_side3 = 0;
            
    $total_weight = $cartTotal['total_weight'];
    $total_count = $cartTotal['total_count'];
    foreach($cart as $item){
        $prod = $modx->getObject('msProduct', $item['id']);
        $size = $prod->get('size');
        if($size){ //размеры нужно задавать в стандартном поле карточки товара в формате 10-20-30
            $size = explode('-',$size[0]); //разделитель можно изменить
            $dimension_side1 += $size[0]; //может быть = 0
            $dimension_side2 += $size[1]; //может быть = 0
            $dimension_side3 += $size[2]; //может быть = 0
        }
    }
   // 'os' => $cartTotalCost можно передавать для более точного расчёта
    $params = array(
        'apikey' => $modx->getOption('logsis_api'),
        'address' => $address,
        'weight' =>  $total_weight,
        'dimension_side1' => $dimension_side1,
        'dimension_side2' => $dimension_side2,
        'dimension_side3' => $dimension_side3
    );
                
    $response = $modx->runSnippet('integrateLogsis', array('url' => 'http://api.logsis.ru/api/v1/public/calculate', 'method' => 'post', 'params'=> $params));
    //$modx->log(1,'CALC ' . print_r($params,1));
    if($response['status'] == 200){ // коды ответов есть в документации http://api.logsis.ru/logsis-api/
        $delivery_cost = ceil($response['response']['total']);
        $res = array(
            'success' => true,
            'message' =>'Стоимость доставки расcчитана',
            'delivery_cost' => $delivery_cost,
            'total_cost' => $cartTotalCost + $delivery_cost
            );
    }
}
return json_encode($res); //кодируем в JSON и возвращаем ответ.
В предыдущем куске кода фигурирует сниппет integrateLogsis он собственно отправляет запрос и больше ничего
<?php
if(!$url){
    $modx->log(1, 'integrateLogsis: Не передан url для отправки запроса');
    return false;
}
if(!$params){
    $modx->log(1, 'integrateLogsis: Не переданы параметры запроса');
    return false;
}

$ch = curl_init($url);
if($method == 'post'){
    curl_setopt($ch, CURLOPT_POST, 1);
}
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params, '', '&'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$html = curl_exec($ch);
curl_close($ch);
$response = json_decode($html,1);
//$modx->log(1,'integrateLogsis ' .  print_r($response,1));
if($response['Error']){
    return $response['Error'];
}else{
    return $response;
}
Собственно по калькуляции это всё. Дальше задача стояла создать заказ на доставку в ЛК Logsis и получить трек-номер. Для создания заказа нужно пройти два этапа собственно создание и подтверждение. Для этого я написал плагин на событие msOnBeforeCreateOrder вот его код
</php
//ещё раз рассчитываем стоимость доставки, а то мало ли
$deliveryCalc = json_decode($modx->runSnippet('getDeliveryCost'),1); 
$msOrder->set('cost', $deliveryCalc['total_cost']); //меняем стоимость заказа
$msOrder->set('delivery_cost', ceil($deliveryCalc['delivery_cost'])); //устанавливаем стоимость доставки
$inner_n = $msOrder->get('num'); //Внутренний номер отправителя
$address = $msOrder->getOne('Address');
$products = $msOrder->getMany('Products'); 
$proerties = $address->get('properties');
$key = $modx->getOption('logsis_api');
$date = new DateTime($_POST['order_date']) ?: date();
$delivery_date =  $date->format('Y-m-d') ?: date('Y-m-d'); //Дата доставки (YYYY-mm-dd)
$delivery_time = $_POST['extfld_delivery_time']; //Интервалы: Москва и московская область и Санкт-Петербург
$target_name = $address->get('receiver');
$target_contacts = preg_replace('/[^0-9]/', '', $address->get('phone'));
$os =  $msOrder->get('cart_cost'); //Оценочная стоимость заказа
$np = 1; //Услуга 'Прием наложенного платежа' 
$price_client_delivery = $msOrder->get('delivery_cost'); //Стоимость доставки для конечного получателя 
$price_client = $msOrder->get('cost'); //Величина наложенного платежа, руб
$order_weight = $msOrder->get('weight'); //Вес отправления, кг
$places_count = 1; //Количество мест в заказе.
$dimension_side1 = 0; //Габарит заказа 1, в см. Не передавать 0!
$dimension_side2 = 0; //Габарит заказа 2, в см. Не передавать 0!
$dimension_side3 = 0; //Габарит заказа 3, в см. Не передавать 0!
$post_code = $address->get('index'); //Почтовый индекс
$addr = $address->get('region') . ',' .$address->get('city') . ',' .$address->get('street').','.$address->get('building');
$sms = $modx->getOption('logsis_sms'); //Признак услуги sms-информирования.
$open_option = 3; //Признак возможности вскрытия заказа.
$call_option = $modx->getOption('logsis_call'); //Дополнительный (тарифицируемый) звонок клиенту.
$docs_option = 0; //Возврат накладных / документов, вложенных в заказ (опция тарифицируется).
$partial_option = 0; //Признак возможности частичного выкупа заказа клиентом
$dress_fitting_option = 0; //Признак возможности примерки товаров
$lifting_option = 0; //Занос / подъем КГТ заказов (более 25 кг) до квартиры
$goods = array();
$k = 0;
// тут я просто не уверен какой ключ поэтому ключ создал отдельно
// предполагаю что можно было сделать так foreach($products as $k => $product){}
foreach($products as $product){
    $goods[$k]['articul'] = $product->get('article') ?: $product->get('name');  //Артикул товарной позиции / услуги
    $goods[$k]['artname'] = $product->get('name'); //Название товарной позиции / услуги
    $goods[$k]['count'] = $product->get('count'); //Количество, ед.
    $goods[$k]['weight'] = $product->get('weight'); //Вес единицы товара, кг
    $goods[$k]['price'] = $product->get('price'); //Цена единицы товара, руб
    $goods[$k]['nds'] = 2; //Вид налога на товар/услугу
   
    $prod = $modx->getObject('msProduct', $product->get('product_id'));
    $size = $prod->get('size');
    $size = explode('-',$size[0]);
    $dimension_side1 += $size[0];
    $dimension_side2 += $size[1];
    $dimension_side3 += $size[2];
    $k++;
}
$params = array(
    'key' => $key,
    'inner_n' => $inner_n,
    'delivery_date' => $delivery_date,
    'delivery_time' => $delivery_time,
    'target_name' => $target_name,
    'target_contacts' => $target_contacts,
    'os' => $os,
    'np' => $np,
    'price_client' => $price_client,
    'price_client_delivery' => $price_client_delivery,
    'order_weight' => $order_weight,
    'places_count' => $places_count,
    'dimension_side1' => $dimension_side1,
    'dimension_side2' => $dimension_side2,
    'dimension_side3' => $dimension_side3,
    'post_code' => $post_code,
    'addr' => $addr,
    'sms' => $sms,
    'open_option' => $open_option,
    'call_option' => $call_option,
    'docs_option' => $docs_option,
    'partial_option' => $partial_option,
    'dress_fitting_option' => $dress_fitting_option,
    'lifting_option' => $lifting_option,
    'goods' => $goods
    );
//$modx->log(1,'Get track number ' .  print_r($params,1)); 
//создаем заказ на доставку   
$response = $modx->runSnippet('integrateLogsis', array('url' => 'http://cab.logsis.ru/apiv2/createorder/', 'method' => 'post', 'params'=> $params));    
if($response['status'] == 200){
    $params = array(
            'key' => $key,
            'inner_n' => $inner_n,
        );
    //подтверждаем заказ на доставку   
   $response = $modx->runSnippet('integrateLogsis', array('url' => 'http://cab.logsis.ru/apiv2/confirmorder', 'method' => 'post', 'params'=> $params));  
    //записываем полученный трек номер в комментарии к заказу чтобы отправить пользователю в письме
    $msOrder->set('comment', $response['response']['order_id']); 
    $msOrder->save();
}
В коде выше есть дополнительное поле extfld_delivery_time оно создано для сохранения кода интервала доставки для Москвы и Питера, по-хорошему его бы надо соотносить с реальными интервалами и показывать в админке в карточке заказа, но в моём случае требовалось просто передавать его в Logsis, по сути можно было и дополнительное поле не прикручивать, а получать его просто из массива $_POST, но у меня есть проверенный и лёгкий способ взятый здесь Чанк письма с трек-номером думаю приводить не надо, приведу просто код для вывода комментария
<h3>Трек номер: {$comment}</h3>
Вот собственно и всё, надеюсь ничего не забыл. Буду рад если кому-то это пригодится, а также замечаниям по качеству кода и предложениям по его улучшению.
P.S. Техподдержка у сервиса Logsis г**но, я обращался я знаю)))
Артур Шевченко
01 ноября 2020, 16:44
modx.pro
675
0
Поблагодарить автора Отправить деньги

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

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