Написание сервисов доставки СДЭК, ПР, EMS для MS2
Это первая часть статьи и она повещена целиком и полностью расчету СДЭК.
Вторая часть тут
Статья рассчитана на новичков, которые плохо понимают php и js код, старички могут сразу перейти на GitHub
Прежде всего хочу предупредить что я никого не хочу оскорбить или унизить, моя история началась пол года назад с покупки дополнений msCDEK и msRussianPost и в течении полугода мы теряли деньги, клиентов и решали проблемы до тех пор пока и мне и автору этих дополнений это не надоело.
Как говорит Василий «Критикуешь — предлагай» и под катом я расскажу как сделать самому всего в пару десятков строк кода эти сервисы доставки, а пока опишу проблемы, которые решает мой вариант реализации
Это не доработка решений вышеуказанных решений, а полноценная инструкция по созданию своих сервисов доставки на базе MS2. В общем не буду тянуть кота за хвост, за объяснением кода под кат
Для реализации расчета СДЭК мы будем использовать официальное API (благо для его использования не нужны никакие ключи), для автодополнения городов мы будем использовать тоже СДЭК и Select2. А для реализации Почты России мы воспользуемся сервисом poscalc, т.к. получить токен у самой Почты России — та еще задача и мы эту задачу не одолели и за неделю :).
1) Подгрузка списков городов и их автокомплит:
Поле с подгрузкой городов мне нужно не только на странице заказа но и в профиле пользователя, по этому я решил реализовать это отдельным решением. и оформил его на Codepen
Но для полной реализации задачи нам нужно несколько дополнительных ajax запросов и скриптов которые будут писать имя города и ID города СДЭК в сессию для дальнейшей обработки скрипта
Создадим скрипт записи города в сессию по пути /assets/components/customDelivery/
GitHub session.php
Для начала ознакомьтесь с официальной документации по расширению служб minishop 2 Раз и Два
Далее я подразумеваю что вы прочитали эти две короткие статьи и не объясняю очевидные вещи
Для начала создадим наш будущий контроллер по пути /core/components/customDelivery/model/cdek/CDEK.php в папке с этим контроллером создадим папку calc и закинем библиотеку для работы со СДЭК, взята она была с официального сайта, но ссылку искать лень, можете выкачать с моего GitHub в ней ничего не менялось.
Перейдем к коду нашего контроллера:
Уже на этом этапе все должно работать, но не выводится статус доставки при выборе нашего метода, для этого нам нужен еще один ajax запрос и php скрипт который будет извлекать из сессии наш статус:
/assets/components/customDelivery/sessionGetStatus.php
Там вроде бы все предельно понятно по аналогии со скриптом session.php, давайте сразу перейдем к js составляющей
GitHub main.js
На этом пока что все, я разбил статью на две части и во второй части я объясню как прикрутить к этому EMS и Почту России через сервис poscalc. Ну а тем, кому не терпится могут заглянуть на гитхаб, там уже все есть.
P.s. контроллеры тестировались на протяжении месяца и в течении месяца у нас было только два случая, когда нам вручную пришлось пересчитывать доставку, по сравнению с покупными решениями это колоссально мало и клиент доволен как слон. Ссылку на сайт где сейчас это работает выкладывать не буду, дабы не сочли рекламой
P.p.s. было бы отлично ввести спойлеры на modx.pro :)
Вторая часть тут
Статья рассчитана на новичков, которые плохо понимают php и js код, старички могут сразу перейти на GitHub
Прежде всего хочу предупредить что я никого не хочу оскорбить или унизить, моя история началась пол года назад с покупки дополнений msCDEK и msRussianPost и в течении полугода мы теряли деньги, клиентов и решали проблемы до тех пор пока и мне и автору этих дополнений это не надоело.
Как говорит Василий «Критикуешь — предлагай» и под катом я расскажу как сделать самому всего в пару десятков строк кода эти сервисы доставки, а пока опишу проблемы, которые решает мой вариант реализации
- msRussianPost у меня не считала доставку заказной бандероли и заказной бандероли 1 класса
- Если стоит метод доставки например самовывоз, то автодополнение городов не работает, а консоль начинает усыпать ошибками которые крашут вообще весь расчет доставки
- В заказ в админке не пишется город
- Надпись о времени и цене доставки не удаляется при смене метода доставки
- Дополнительная цена не плюсуется к расчету доставки, а плюсуется к итоговой цене
Это не доработка решений вышеуказанных решений, а полноценная инструкция по созданию своих сервисов доставки на базе MS2. В общем не буду тянуть кота за хвост, за объяснением кода под кат
Для реализации расчета СДЭК мы будем использовать официальное API (благо для его использования не нужны никакие ключи), для автодополнения городов мы будем использовать тоже СДЭК и Select2. А для реализации Почты России мы воспользуемся сервисом poscalc, т.к. получить токен у самой Почты России — та еще задача и мы эту задачу не одолели и за неделю :).
1) Подгрузка списков городов и их автокомплит:
Поле с подгрузкой городов мне нужно не только на странице заказа но и в профиле пользователя, по этому я решил реализовать это отдельным решением. и оформил его на Codepen
Но для полной реализации задачи нам нужно несколько дополнительных ajax запросов и скриптов которые будут писать имя города и ID города СДЭК в сессию для дальнейшей обработки скрипта
Создадим скрипт записи города в сессию по пути /assets/components/customDelivery/
GitHub session.php
<?php
//Инициализируем api modx, чтобы иметь доступ к нужной нам сессии в пространстве modx, иначе мы просто не достучимся до нее в нашем скрипте
define('MODX_API_MODE', true);
require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/index.php';
$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_ERROR);
$modx->setLogTarget('FILE');
//проверяем действительно ли это AJAX запрос, иначе редиректим на главную
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {
$modx->sendRedirect($modx->makeUrl($modx->getOption('site_start'),'','','full'));
}
//пишем в сессию
session_start();
$_SESSION['cityId'] = $_POST['cityId'];
$_SESSION['cityName'] = $_POST['cityName'];
//возвращаем записанные данные для отладки (необязательно)
echo $_SESSION['cityId'];
?>Тут вроде бы все просто и понятно, далее нам необходимо создать блок с классом «statusDelivery» для записи сообщения, но об этом чуть позже, просто держите это в памяти. А теперь перейдем к js составляющей: GitHub main.js$(document).ready(function() {
//инициализируем select2 на поле select с атрибутом name=city
$('select[name=city]').select2({
language: 'ru',
placeholder: 'Город не выбран',
//делаем ajax запрос прямиком к api сдэк. Select2 кладет в params.term символы которые вы ввели в select2. Нам нужно передать 2 параметра q и name_startsWith оба должны содержать то, что пользователь вводит в данный момент
ajax: {
url: 'http://api.cdek.ru/city/getListByTerm/jsonp.php?callback=?',
dataType: "jsonp",
data: function (params) {
return {
q: params.term,
name_startsWith: params.term,
};
},
//функция обработки ответа
processResults: function (resp) {
//подготавливаем массив городов
var citys = [];
//проверяем вернулся ли нам объект, чтобы не сыпались в консоль ошибки и не поломалось ничего
if (typeof(resp.geonames) != 'object') {
return false;
}
//перебираем пришедший объект чтобы подготовить массив нужных нам значений для select2
resp.geonames.forEach(function(item, i) {
var obj = {};
obj.id = item.name;
obj.text = item.name;
obj.cityName = item.cityName;
obj.regionName = item.regionName;
obj.post = item.postCodeArray;
obj.idCDEK = item.id;
citys.push(obj);
});
//возвращаем массив городов для автодополнения select2
return {
results: citys,
};
},
},
//задаем минимальную длину ввода
minimumInputLength: 3,
//функция выбора нужного select
templateSelection: function (data) {
//функция ajax запроса на наш session.php, передаем выбранный id города и имя выбранного города, имя нам понадобится для Почты России
setSession(data.idCDEK, data.cityName);
if (data.id === '') {
return 'Город не выбран';
}
if (data.idCDEK == undefined) {
return data.text;
}
return data.text;
}
});
//функция ajax установки сессии
function setSession(id, name) {
if (id == undefined) {
return false;
}
$.ajax({
type: "POST",
url: "assets/components/customDelivery/session.php",
data: {
cityId: id,
cityName: name
},
success: function(res) {
//сбрасываем выбранный способ доставки, потому что при выборе доставки нам нужно отправить запрос на контроллер чтобы узнать сроки доставки, а также очищаем блок со статусом. Это избавит нас от многих проблем, когда заказчик выбрав уже доставку и получив статус внезапно решает изменить город
$(document).find('input[name=delivery]').attr('checked', false);
var statusBlock = $('.statusDelivery');
statusBlock.text('');
}
});
}
})Вот практически и все по js части кода, просто, не правда ли? Теперь нам нужно создать свой класс — обработчик расчета доставки это еще проще, перейдем к коду:Для начала ознакомьтесь с официальной документации по расширению служб minishop 2 Раз и Два
Далее я подразумеваю что вы прочитали эти две короткие статьи и не объясняю очевидные вещи
Для начала создадим наш будущий контроллер по пути /core/components/customDelivery/model/cdek/CDEK.php в папке с этим контроллером создадим папку calc и закинем библиотеку для работы со СДЭК, взята она была с официального сайта, но ссылку искать лень, можете выкачать с моего GitHub в ней ничего не менялось.
Перейдем к коду нашего контроллера:
<?php
//проверяем на отсутствие классов и инициализируем классы msdeliveryhandler, CalculatePriceDeliveryCdek
if(!class_exists('msDeliveryInterface')) {
require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/minishop2/model/minishop2/msdeliveryhandler.class.php';
}
if(!class_exists('CalculatePriceDeliveryCdek')) {
require_once MODX_CORE_PATH.'components/customDelivery/model/cdek/calc/CalculatePriceDeliveryCdek.class.php';
}
//расширяем класс msDeliveryHandler и переписываем метод getCost
class msDeliveryHandlerCDEK extends msDeliveryHandler implements msDeliveryInterface {
public $modx;
public $ms2;
public function getCost(msOrderInterface $order, msDelivery $delivery, $cost = 0) {
//создаем объект
$calc = new CalculatePriceDeliveryCdek();
//получаем массив с данными текущей карточки
$cart = $order->ms2->cart->status();
//записываем вес с переменную
$weight = $cart['total_weight'];
//достаем из сессии id выбранного города
$cityId = $_SESSION['cityId'];
//устанавливаем id города из которого будет осуществляться доставка, у меня этот id записан в системной настройке "CdekSenderCity", вы же можете указать его напрямую, если кто сам не может найти в офф документации id всех тарифов и городов, то могу скинуть в комментах
$calc->setSenderCityId($this->modx->getOption('CdekSenderCity'));
//устанавливаем id города - получателя
$calc->setReceiverCityId($cityId);
//устанавливаем id тарифа отправки, он у меня опять же лежит в системной настройке CdekIdTariff
$calc->addTariffPriority($this->modx->getOption('CdekIdTariff'));
//устанавливаем id типа отправления, он у меня опять же лежит в системной настройке CdekIdType
$calc->setModeDeliveryId($this->modx->getOption('CdekIdType'));
//устанавливаем дату отправки, она тут для галочки и нам не нужна
$calc->setDateExecute('2012-08-20');
//Устанавливаем габариты отправления, не меняем, если нам не нужен расчет с учетом габаритов
$calc->addGoodsItemBySize($weight, '1', '1', '1');
//рассчитываем добавочную стоимость указанную в поле "дополнительная стоимость" в настройках метода доставки
$add_price = $delivery->get('price');
if (preg_match('/%$/', $add_price)) {
$add_price = str_replace('%', '', $add_price);
$add_price = $cost / 100 * $add_price;
}
//проверяем не выдал ли сдэк ошибку и обрабатываем ответ
if ($calc->calculate() === true) {
//получаем массив результатов
$res = $calc->getResult();
//пишем стоимость доставки
$priceDelivery = $res['result']['price'];
//минимальный и максимальный период за который дойдет посылка
$minPeriod = $res['result']['deliveryPeriodMin'];
$maxPeriod = $res['result']['deliveryPeriodMax'];
//считаем цену доставки с учетом добавочной цены
$deliveryCost = $priceDelivery + $add_price;
//пишем в сессию статус доставки
if ($minPeriod == $maxPeriod) {
$textDeliveryCdek = 'Стоимость доставки CDEK в '.$_SESSION['cityName'].' составит '.$deliveryCost.'р срок доставки составит '.$maxPeriod.' дня';
} else {
$textDeliveryCdek = 'Стоимость доставки CDEK в '.$_SESSION['cityName'].' составит '.$deliveryCost.'р срок доставки составит от '.$minPeriod.' дней до '.$maxPeriod.' дней';
}
session_start();
$_SESSION['statusDelivery'] = $textDeliveryCdek;
//прибавляем начальную цену товаров и стоимость доставки
return intval($deliveryCost) + intval($cost);
} else {
//если вернулась ошибка, обрабатываем ее
$err = $calc->getError();
//В лог пишем массив ошибки
$this->modx->log(1, 'ошибка в расчете сдэк: '.print_r($err,1));
//Записываем в статус то, что сдэк не смог расчитать доставку
$textDeliveryCdek = 'Ошибка в автоматическом расчете СДЭК, стоимость доставки посчитает для вас менеджер вручную';
session_start();
$_SESSION['statusDelivery'] = $textDeliveryCdek;
//возвращаем начальную цену + добавочную без стоимости доставки
return intval($cost) + intval($add_price);
}
}
}
?>Теперь подключим наш обработчик в minishop2 выполнив в консоле код:if ($miniShop2 = $modx->getService('miniShop2')) {
$miniShop2->addService('delivery', 'msDeliveryHandlerCDEK',
'{core_path}components/customDelivery/model/cdek/CDEK.php'
);
}В настройках minishop2 необходимо добавить новый метод доставки с нашим классом обработчиком msDeliveryHandlerCDEKУже на этом этапе все должно работать, но не выводится статус доставки при выборе нашего метода, для этого нам нужен еще один ajax запрос и php скрипт который будет извлекать из сессии наш статус:
/assets/components/customDelivery/sessionGetStatus.php
Там вроде бы все предельно понятно по аналогии со скриптом session.php, давайте сразу перейдем к js составляющей
GitHub main.js
//скрипт срабатывает на изменение метода доставки
$('#msOrder [type=radio]').change(function() {
//получаем id выбранного метода
var id = $(this).val();
//получаем класс блока со статусом доставки
var statusBlock = $('.statusDelivery');
//мои методы доставки находятся под id 45, 46 и 5, тут вам нужно подставить свои id методов
if (id == 45 || id == 46 || id == 5) {
//делаем задержку в полторы секунды, чтобы наш контроллер успел все обработать и записать делаем ajax запрос и записываем текст ответа в statusBlock
setTimeout(function() {
$.ajax({
type: "POST",
url: "assets/components/customDelivery/sessionGetStatus.php",
success: function(res) {
statusBlock.text(res);
}
});
}, 1500)
} else {
statusBlock.text('');
}
})Теперь наш контроллер доставки СДЭК считает все правильно и корректно выводит и очищает статус доставки. Работа со СДЭК закончена!На этом пока что все, я разбил статью на две части и во второй части я объясню как прикрутить к этому EMS и Почту России через сервис poscalc. Ну а тем, кому не терпится могут заглянуть на гитхаб, там уже все есть.
P.s. контроллеры тестировались на протяжении месяца и в течении месяца у нас было только два случая, когда нам вручную пришлось пересчитывать доставку, по сравнению с покупными решениями это колоссально мало и клиент доволен как слон. Ссылку на сайт где сейчас это работает выкладывать не буду, дабы не сочли рекламой
P.p.s. было бы отлично ввести спойлеры на modx.pro :)
Комментарии: 43
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
Есть свой набор скриптов, рассчитывающий стоимость доставки по IML, СДЭК, Деловые Линии, Почта РФ. А также хранящий локально и регулярно обновляющий пункты вывоза и даже стоимость доставки для каждого города в случае типизированных товаров/заказов (это позволяет не обращаться каждый раз к серверам служб доставки).
Всё это хозяйство работает с расширенными классами доставки, оплаты и корзины MS2.
Т.к. практически всегда в реально работающем магазине (более-менее сложном) есть свои тонкости, использовать модули невозможно. Это не недостаток какого-либо дополнения: принципиально невозможно предусмотреть все варианты для всех проектов.
С другой стороны, если будет компонент — выкладывать его — это значит неизбежно навлечь на себя кучу вопросов от народа по индивидуальным особенностям их проектов. Отвечать на такие уникальные вопросы крайне трудно и затратно по времени, сами понимаете ). Не отвечать — негатив.
Я стараюсь делиться с сообществом наработками в благодарность за его помощь, но этот вопрос слишком объёмен…
Вот что имеет смысл, так это описать сам принцип реализации, без кодов. Матёрым разрабам оно без надобности, а начинающим может помочь.
А тема интересная, последние 3 проекта требовали одновременной работы с несколькими службами доставки + почта.
Себе порешал так (заодно и ГЕО прикрутил):
1. Определяю город через GeoLocation
2. Отдаю город и вес сниппету msRussianPost, получаю цены и сроки
3. Отдаю id города (по плану нумерации SxGeo) собственноручно написанному сниппету, он отдает id по плану нумерации СДЭК
4. Отдаю этот id сниппету msCDEK, получаю цены и сроки
все.
Смысл в том, что отпадает необходимость ввода города, в частности на странице товара я показываю сразу определившийся город клиента и сразу стоимость доставки, это требование сеошника, по аналогии как на всемайки, если покупали, там точно такая же логика.
Ну и на странице корзины аналогично — клиент освобождается от целого лишнего шага при оформлении
Во вторых пользователю все равно нужно заполнить индекс, адрес, дом и т.д. тем что ты определишь город ты не сильно облегчишь задачу пользователю, таким же образом я могу сказать что в своем решении я тоже лишаю ЦЕЛОГО ШАГА ГУГЛЕНИЯ ИНДЕКСА, там что подгружаю индексы codepen
Закончить можно тем, что не все заказывают доставку в тот город, где находятся, и не всегда SxGeo определяет правильно даже центры регионов, я уже молчу о всякой переферии например Нальчик или Прохладный в Кабардино Балкарской Республике.
Я ж писал про самописный сниппет — он делает запрос в самописную таблицу БД -там есть все что нужно))) Там 833 города, клиенту за глаза!
На данный момент SxGeo отлично определяет 833 города — насколько я смог проанализировать, это именно наиболее крупные города. Нальчик отлично определяется, Прохладный — согласен, не определяется. Кстати, что это — Прохладный?)) Это не сарказм, а требование клиента — было принято решение отказаться от пары заказов из села Урюповка и сэкономленное на этом клиенте время отдать для клиентов, делающих половину от общего объема заказов! Про правило Руссето я уже писал в соседних темах. дело ваше, раздавать по всей России или сконцентрироваться.
Но в общем то ваше дело, главное, чтобы все были довольны) В соседней ветки про запросы я тоже отписался)
90% покупок было сделано клиентами из 1% городов!!! Беглый просмотр этого списка определил судьбу мелких деревенек. Вот и все. Если обоснуете клиенту необходимость деревенек, то велкам в его проект))) И главное обосновать стоимость зарплат менеджеров, которые собирают заказы…
Вы о чем ?)))
Почта России 3-4 дня и доставка всего 180р!!)) Итого 380р — явно меньше 750р!!))
В колонке id — это id города согласно плану нумерации SxGeo
В колонке sdek — id города согласно плану нумерации СДЭК
Там еще на будущее поля для почты россии и деловых линии, хотя почта россии охотно принимает название города, т.е. колонка name_ru
Таблица намерено ограничена городами России, id от СДЭК заполнены с привлечением нескольких запросов MySQL и небольшой ручной допилки — 833 города заполнены в течение 2 часов)))
Ушёл пить коньячеллу…
Недавно перешёл на api dadata. Позволяет посетителю по желанию сразу указать на сайте адрес, вплоть до дома. (отсюда сразу получаем верный индекс, кому надо). Полная база городов/адресов, в отличии от GeoLocation (насколько я помню, там далеко не все города).
Указание полного адреса — как раз убирает целый шаг в корзине.
На данный момент есть только 2 проблемы: мобильные (тут всё ясно) и с какого-то момента куча IP не верно определяется что dadata'ой, что sxGEO. Проверяли на некоторых московских адресах — полный трэш — то Чукотка, то чуть не Китай ) Думаю, временно, т.к. онлайн тестилки нормально определяют.
А вот если инсталлировать с методом msDeliveryHandlerCDEK, то все ок.
И в обработчиках упоминания про CDEKdelivery нет, а вот про msDeliveryHandlerCDEK как раз есть
Ситуация в следующем: добавляю сервис доставки через консоль, ни одному из методов доставки пока его не назначаю, но уже ломается заказ — его не оформить, ошибка 500 при обращении к action минишопа. А если новый метод назначить какому-нибудь из способов доставки, то шаблон вовсе крашится, выдает не обработанный феном, а в лог пишет «неправильно задан город-отправитель». Полдня бился, пока не удалил сервис обратно через консоль, заказы так и не оформлялись.
И еще, данное решение не предусматривает выбора пунктов выдачи, если сдек-самовывоз?
По поводу пунктов выдачи — нет, это просто демонстрация того, как просто можно сделать то, что стоит косарь, но мне уже по нему писали человек 6 в контакте мол все работает спасибо, так что ищите ошибку у себя :)
у меня в свое время после включения ошибок огромными черными буквами показало — не указан вес — все как бы сразу ясно!
мои слова такие: «необходимых не Модиксу», а ваша якобы цитата с ног на голову…
про плевок СДЭЕКа вообще не понятно — да не плюет он, а берет ваши данные и считает, то, что у вас все товары весят 1кг не говорит ни о чем, это сугубо ваша затея.