В стандартной поставке Битрикса нет службы доставки CDEK. Чтобы исправить этот недостаток, предлагаем вашему вниманию класс обработчик CDeliveryCDEK.
Особенности расчета стоимости службы доставки CDEK являются:
- задание в качестве параметров к запросу кодов городов, которые приводятся в документации;
- необходимость задания весов товаров и габаритов или объема
Т.к. в документации приведена xls таблица городов, пользоваться ей из php невозможно. Поэтому мы сформируем таблицу в MySQL с именем `cdek_cities`, для удобного извлечения кодов городов. Таблица создается автоматически при первом обращении к обработчику, поэтому вам нужно дать пользователю MySQL права на создание таблиц.
Примечание. Битрикс не рекомендует пользоваться базой данных напрмямую в обход API битрикса, поэтому стандартные обработчики создают файлы городов на диске, а не в базе. Нам кажется это неудобным, поэтому мы пренебрежем этой рекомендацией битрикса. Если у вас в проекте уже существует таблица с именем cdek_cities, вам придется самим переписать имя таблицы в коде.
Для добавления обработчика CDEK в битрикс вам понадобятся три файла.
- /bitrix/php_interface/include/sale_delivery/delivery_cdek.php — файл обработчика;
- /bitrix/php_interface/include/sale_delivery/cdek_cities.sql — файл дампа базы данных городов, этот файл будет удален после создания базы данных. Файл можно скачать cdek_cities.rar и перед копированием разархивировать.
- /bitrix/modules/sale/lang/ru/delivery/delivery_cdek.php — файл региональных настроек, вы можете создать файл и для других языков, заменив строки своими.
Тексты скриптов:
/bitrix/php_interface/include/sale_delivery/delivery_cdek.php
<? /********************************************************************************* Delivery handler for CDEK (http://www.cdek.ru/) Files: - cdek_cities.sql - cdek city datebase *********************************************************************************/ CModule::IncludeModule("sale"); IncludeModuleLangFile('/bitrix/modules/sale/delivery/delivery_cdek.php'); class CDeliveryCDEK { static private $profiles = array('default'); static private $sql_create_table = ' CREATE TABLE IF NOT EXISTS `cdek_cities` ( `ID` int(10) unsigned NOT NULL DEFAULT "0", `Name` varchar(150) COLLATE utf8_unicode_ci NOT NULL DEFAULT "", `CityName` varchar(150) COLLATE utf8_unicode_ci NOT NULL DEFAULT "", `OblName` varchar(150) COLLATE utf8_unicode_ci NOT NULL DEFAULT "", `Center` char(1) COLLATE utf8_unicode_ci NOT NULL DEFAULT "", `PostCode` text COLLATE utf8_unicode_ci NOT NULL, `NalSumLimit` float NOT NULL, PRIMARY KEY (`ID`), KEY `CityName` (`CityName`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; static private $access = array( 'script' => 'http://api.edostavka.ru/calculator/calculate_price_by_json_request.php', 'version' => '1.0', ); private function getSecure($password) { return md5(date('Y-m-d').'&'.$password); } private function check_table(){ global $DB; if(!isset($DB) || !($res = $DB->Query('DESCRIBE `cdek_cities`', true))) return false; while ($r = $res->Fetch()) $table = $r; return isset($table); } private function create_table(){ global $DB; if(!isset($DB) || !$DB->Query(self::$sql_create_table) || !self::check_table() || !is_file($filename = dirname(__FILE__).'/cdek_cities.sql') || !($file = @file($filename))) return false; foreach($file as $sql){ if(!$DB->Query($sql)) { $res = $DB->Query('DROP TABLE `cdek_cities`', true); return false; } } @unlink($filename); return true; } private function isAviable(){ return(function_exists('json_encode') && extension_loaded('curl') && function_exists('http_build_query') ) && (self::check_table() || self::create_table()); } private function GetDescription(){ return GetMessage(self::isAviable() ? 'SALE_DH_CDEK_DESCRIPTION' : 'SALE_DH_CDEK_DESCRIPTION_ERROR'); } function Init() { $base_currency = ($arCurrency = CCurrency::GetByID('RUR')) ? 'RUR' : 'RUB'; $profiles = array(); foreach(self::$profiles as $profile){ $profiles[$profile] = array( 'TITLE' => GetMessage('SALE_DH_CDEK_'.strtoupper($profile).'_TITLE'), 'DESCRIPTION' => GetMessage('SALE_DH_CDEK_'.strtoupper($profile).'_DESCRIPTION'), 'RESTRICTIONS_WEIGHT' => array(0), 'RESTRICTIONS_SUM' => array(0), ); } return array( 'SID' => 'cdek', 'NAME' => GetMessage('SALE_DH_CDEK_NAME'), 'DESCRIPTION' => self::GetDescription(), 'DESCRIPTION_INNER' => GetMessage('SALE_DH_CDEK_DESCRIPTION_INNER'), 'BASE_CURRENCY' => $base_currency, 'HANDLER' => __FILE__, 'DBGETSETTINGS' => array(__CLASS__, 'GetSettings'), 'DBSETSETTINGS' => array(__CLASS__, 'SetSettings'), 'GETCONFIG' => array(__CLASS__, 'GetConfig'), 'COMPABILITY' => array(__CLASS__, 'Compability'), 'CALCULATOR' => array(__CLASS__, 'Calculate'), 'PROFILES' => $profiles ); } function GetConfig() { $arConfig = array( 'CONFIG_GROUPS' => array( 'all' => GetMessage('SALE_DH_CDEK_CONFIG_TITLE'), ), 'CONFIG' => array( 'cdek_login' => array( 'TYPE' => 'STRING', 'DEFAULT' => '', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_LOGIN'), 'GROUP' => 'all', 'VALUES' => '', ), 'cdek_password' => array( 'TYPE' => 'PASSWORD', 'DEFAULT' => '', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_PASSWORD'), 'GROUP' => 'all', ), 'cdek_tariffId' => array( 'TYPE' => 'STRING', 'DEFAULT' => '137', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_TARIFID'), 'GROUP' => 'all', ), 'cdek_modeId' => array( 'TYPE' => 'STRING', 'DEFAULT' => '3', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_MODEID'), 'GROUP' => 'all', ), 'cdek_volume' => array( 'TYPE' => 'STRING', 'DEFAULT' => '8000', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_VOLUME'), 'GROUP' => 'all', ), 'cdek_weight' => array( 'TYPE' => 'STRING', 'DEFAULT' => '1000', 'TITLE' => GetMessage('SALE_DH_CDEK_CONFIG_WEIGHT'), 'GROUP' => 'all', ) ) ); return $arConfig; } function GetSettings($strSettings) { return unserialize($strSettings); } function SetSettings($arSettings) { foreach ($arSettings as $key => $value){ if(strpos($key, 'cdek_') !== false) $arSettings[$key] = $value; else{ if($value != '') $arSettings[$key] = doubleval($value); else unset($arSettings[$key]); } } return serialize($arSettings); } function Calculate($profile, $arConfig, $arOrder, $STEP, $TEMP = false) { global $DB; if(!self::isAviable()) { return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_DESCRIPTION_ERROR') ); } if(empty($arConfig['cdek_tariffId']['VALUE']) || empty($arConfig['cdek_modeId']['VALUE']) || empty($arConfig['cdek_volume']['VALUE'])) { return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_NOSETTINGS') ); } if($arOrder['WEIGHT'] == 0 && $arConfig['cdek_weight']['VALUE'] == 0) { return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_WEIGHTISZERO') ); } if(!extension_loaded('curl')) { return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_NOCURL') ); } $arLocationFrom = CSaleLocation::GetByID($arOrder["LOCATION_FROM"]); if(($city = self::__getCity($arLocationFrom['CITY_NAME'])) === false) return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_NOCITY') ); $city_from_id = $city['ID']; $arLocationTo = CSaleLocation::GetByID($arOrder["LOCATION_TO"]); if(($city = self::__getCity($arLocationTo['CITY_NAME'])) === false) return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_NOCITY') ); $city_to_id = $city['ID']; $data = array(); if(!empty(self::$access['version'])) $data['version'] = self::$access['version']; $data['dateExecute'] = date('Y-m-d'); if(!empty($arConfig['cdek_login']['VALUE'])) $data['authLogin'] = $arConfig['cdek_login']['VALUE']; if(!empty($arConfig['cdek_password']['VALUE'])) $data['secure'] = self::getSecure($arConfig['cdek_password']['VALUE']); $data['senderCityId'] = $city_from_id; $data['receiverCityId'] = $city_to_id; $data['tariffId'] = intval($arConfig['cdek_tariffId']['VALUE']); $data['modeId'] = intval($arConfig['cdek_modeId']['VALUE']); $data['goods'][0] = array(); $weight = intval($arConfig['cdek_weight']['VALUE']); if($weight == 0) $weight = $arOrder['WEIGHT']; $data['goods'][0]['weight'] = $weight / 1000; $data['goods'][0]['volume'] = intval($arConfig['cdek_volume']['VALUE']) / 1000000; $bodyData = array ('json' => json_encode($data)); $data_string = http_build_query($bodyData); if(!($ch = @curl_init())){ return array( 'RESULT' => 'ERROR', 'TEXT' => GetMessage('SALE_DH_CDEK_ERROR_NOCURL') ); } curl_setopt($ch, CURLOPT_URL, self::$access['script']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded', 'Content-Length: '.strlen($data_string) ) ); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); $result = curl_exec($ch); curl_close($ch); $response = @json_decode($result, true); if(isset($response['result']) && !empty($response['result']) ) { $days = $response['result']['deliveryPeriodMin'] != $response['result']['deliveryPeriodMax'] ? $response['result']['deliveryPeriodMin'].'-'.$response['result']['deliveryPeriodMax'] : $response['result']['deliveryPeriodMin']; return array( "RESULT" => "OK", "VALUE" => $response['result']['price'], 'TRANSIT' => $days ); } if(isset($response['error'], $response['error'][0], $response['error'][0]['text'])){ return array( "RESULT" => "ERROR", "TEXT" => $response['error'][0]['text'] ); } return array( "RESULT" => "ERROR", "TEXT" => GetMessage('SALE_DH_CDEK_ERROR_SERVER'), ); } function Compability($arOrder, $arConfig) { if(!isset($arConfig['cdek_tariffId'], $arConfig['cdek_modeId'], $arConfig['cdek_volume']) || empty($arConfig['cdek_tariffId']['VALUE']) || empty($arConfig['cdek_modeId']['VALUE']) || empty($arConfig['cdek_volume']['VALUE']) || ($arOrder['WEIGHT'] == 0 && $arConfig['cdek_weight']['VALUE'] == 0) ) return array(); $arLocationFrom = CSaleLocation::GetByID($arOrder["LOCATION_FROM"]); if (!self::__IsRussian($arLocationFrom)) return array(); $cityFrom = self::__getCity($arLocationFrom['CITY_NAME']); if($cityFrom === false) return array(); if($arOrder["LOCATION_FROM"] != $arOrder["LOCATION_TO"]){ $arLocationTo = CSaleLocation::GetByID($arOrder["LOCATION_TO"]); if (!self::__IsRussian($arLocationTo)) return array(); $cityTo = self::__getCity($arLocationTo['CITY_NAME']); if($cityTo === false) return array(); if($cityTo['NalSumLimit'] > $arOrder['PRICE']) return array(); } else { if($cityFrom['NalSumLimit'] > $arOrder['PRICE']) return array(); } return array_keys(self::$profiles); } function __getCity($city_name) { global $DB; $res = $DB->Query('SELECT * FROM `cdek_cities` WHERE `CityName` LIKE "'.mysql_real_escape_string($city_name).'"'); while($r = $res->fetch()) $city = $r; return isset($city) ? $city : false; } function __IsRussian($arLocation) { return ( ToUpper($arLocation["COUNTRY_NAME_ORIG"]) == "РОССИЯ" || ToUpper($arLocation["COUNTRY_SHORT_NAME"]) == "РОССИЯ" || ToUpper($arLocation["COUNTRY_NAME_LANG"]) == "РОССИЯ" || ToUpper($arLocation["COUNTRY_NAME_ORIG"]) == "RUSSIA" || ToUpper($arLocation["COUNTRY_SHORT_NAME"]) == "RUSSIA" || ToUpper($arLocation["COUNTRY_NAME_LANG"]) == "RUSSIA" || ToUpper($arLocation["COUNTRY_NAME_ORIG"]) == "РОССИЙСКАЯ ФЕДЕРАЦИЯ" || ToUpper($arLocation["COUNTRY_SHORT_NAME"]) == "РОССИЙСКАЯ ФЕДЕРАЦИЯ" || ToUpper($arLocation["COUNTRY_NAME_LANG"]) == "РОССИЙСКАЯ ФЕДЕРАЦИЯ" || ToUpper($arLocation["COUNTRY_NAME_ORIG"]) == "RUSSIAN FEDERATION" || ToUpper($arLocation["COUNTRY_SHORT_NAME"]) == "RUSSIAN FEDERATION" || ToUpper($arLocation["COUNTRY_NAME_LANG"]) == "RUSSIAN FEDERATION" ); } } AddEventHandler("sale", "onSaleDeliveryHandlersBuildList", array('CDeliveryCDEK', 'Init')); ?>
/bitrix/modules/sale/lang/ru/delivery/delivery_cdek.php
<?php $MESS['SALE_DH_CDEK_NAME'] = 'CDEK'; $MESS['SALE_DH_CDEK_DESCRIPTION'] = 'CDEK - логистические решения'; $MESS['SALE_DH_CDEK_DESCRIPTION_INNER'] = 'Обработчик службы доставки CDEK. Работает на основе <a href="http://cdek.ru/integrator/" target="_blank">API онлайн калькулятора службы</a>. <br />'; $MESS['SALE_DH_CDEK_DESCRIPTION_ERROR'] = 'Служба недоступна: невозможно создать служебные таблицы или не обнаружены требуемые функции'; $MESS['SALE_DH_CDEK_DEFAULT_TITLE'] = 'Доставка посылкой на дом'; $MESS['SALE_DH_CDEK_DEFAULT_DESCRIPTION'] = ''; $MESS['SALE_DH_CDEK_CONFIG_TITLE'] = 'Параметры'; $MESS['SALE_DH_CDEK_ERROR_CONNECT'] = 'Не удалось рассчитать стоимость доставки: ошибка соединения'; $MESS['SALE_DH_CDEK_ERROR_NOCITY'] = 'Не удалось найти город для расчета доставки'; $MESS['SALE_DH_CDEK_ERROR_NOSETTINGS'] = 'В настройках не указаны тариф, режим доставки или объем посылки'; $MESS['SALE_DH_CDEK_ERROR_WEIGHTISZERO'] = 'Вес посылки равен 0'; $MESS['SALE_DH_CDEK_ERROR_NOCURL'] = 'Не подключена библиотека CURL'; $MESS['SALE_DH_CDEK_ERROR_CURL'] = 'Ошибка при инициализации соединения CURL'; $MESS['SALE_DH_CDEK_ERROR_SERVER'] = 'Не удалось рассчитать стоимость доставки: неверный ответ сервера'; $MESS['SALE_DH_CDEK_CONFIG_LOGIN'] = 'Логин на сайте cdek.ru'; $MESS['SALE_DH_CDEK_CONFIG_PASSWORD'] = 'Пароль на сайте cdek.ru'; $MESS['SALE_DH_CDEK_CONFIG_TARIFID'] = 'ID тарифа на доставку'; $MESS['SALE_DH_CDEK_CONFIG_MODEID'] = 'ID режима на доставку'; $MESS['SALE_DH_CDEK_CONFIG_VOLUME'] = 'Размер поcылки, куб.см (например 20x 20 x 20 см = 8000 куб.см)'; $MESS['SALE_DH_CDEK_CONFIG_WEIGHT'] = 'Средний вес посылки, граммы'; ?>
Требования для обработчика: наличие в системе функций json_encode
, http_build_query
и наличии расширения curl
.
Особенности скрипта:
- Т.к. сервис CDEK требует задания веса и габаритов (объема) посылки, то необходимо указывать вес товара.
- С другой стороны габариты товара по умолчанию не передаются в обработчик доставки, поэтому мы не можем их использовать для расчета стоимости. Для указания объема посылки вам необходимо указать ее в настройках обработчика.
- Там же можно указать вес посылки. Если в настройках указан вес = 0, то вес расcчитывается из веса товаров.
- Скрипт рассчитан на кодировку базы данных UTF8, если у вас другая кодировку, измените ее вручную.
Большое спасибо.. немного доработал, но в принципе очень выручил скрипт