Статьи

Абстрагирование API-интерфейсов доставки

У вас есть новый готовый магазин электронной коммерции. Осталось только выяснить, как рассчитать стоимость доставки для ваших клиентов. Вы не хотите использовать стандартную фиксированную ставку для каждого адреса, потому что знаете, что с одних клиентов вы будете платить больше, а главное — с других. Разве не было бы выгодно рассчитывать стоимость доставки на основании веса / размера предмета (ов) и пункта назначения? Может быть, вы могли бы даже предложить точную цену за доставку на ночь!

У вас есть учетная запись UPS, и вы проверили их API, но это выглядит довольно сложно. Если вы жестко запрограммируете свой сайт, чтобы использовать API, вам придется много работать, если вам нужно поменять грузоотправителей. Ваш двоюродный брат является торговым представителем в FedEx, и он клянется, что он может предложить вам лучшие цены с ними. Некоторые из ваших клиентов только нам почтовые ящики, поэтому эти предметы должны быть отправлены в почтовое отделение. Чем ты занимаешься?

Возможно, вы слышали об абстракции базы данных, практике, которая позволяет вам использовать множество различных баз данных с общим набором команд. Это именно то, что вы можете сделать здесь! Чтобы решить все эти проблемы, вы можете отделить задачу доставки от остальной части вашего кода и создать уровень абстракции. Когда вы закончите, не имеет значения, отправляете ли вы посылку UPS, FedEx или USPS. Функции, которые вызовет ваше основное приложение, будут одинаковыми, и это сделает вашу жизнь намного проще!

Начало работы с UPS

В этой статье я сконцентрируюсь на использовании API UPS, но, написав плагин для различных грузоотправителей (например, FedEx или USPS), вы сможете получить доступ к их услугам, а также с незначительными изменениями кода в вашем основном приложении, если таковые имеются.

Чтобы начать использовать UPS, вам необходимо зарегистрировать учетную запись на сайте www.ups.com, используя существующий номер отправителя. Убедитесь, что вы выбрали имя пользователя и пароль, которые вам будет удобно некоторое время использовать, поскольку API требует их обоих для каждого вызова. Затем перейдите на https://www.ups.com/upsdeveloperkit и зарегистрируйтесь для доступа к API UPS. Здесь вы получите ключ API и сможете загрузить документацию для различных пакетов API. (Примечание. В этом разделе сайта UPS есть известная проблема, и Chrome иногда возвращает пустую страницу. Возможно, вам придется использовать альтернативный браузер.)

Помните, что когда вы используете API UPS (или API любого грузоотправителя в этом отношении), вы соглашаетесь следовать их правилам и процедурам. Обязательно ознакомьтесь с ними и следуйте им, особенно с инструкциями, прежде чем использовать свой код в работе.

Затем загрузите или клонируйте пакет слоя абстракции доставки с GitHub по адресу github.com/alexfraundorf-com/ship и загрузите его на свой сервер, на котором работает PHP 5.3 или более поздняя версия. Откройте файл includes/config.php . Вам нужно будет ввести свои данные ИБП здесь, и названия полей должны быть самоочевидными. Обратите внимание, что адрес отправителя ИБП должен совпадать с тем, который ИБП имеет в файле для вашей учетной записи, иначе произойдет ошибка.

Определение отправлений и пакетов

Теперь, чтобы определить объект Shipment . При создании он принимает массив, содержащий информацию о получателе и, возможно, адрес отправителя, если он отличается от информации о грузоотправителе в нашем конфигурационном файле.

 <?php // create a Shipment object $shipment = new ShipShipment($shipmentData); 

Далее нам нужны некоторые детали о том, что мы отправляем. Давайте создадим объект Package который принимает вес, размеры пакета и необязательный массив некоторых основных параметров, таких как описание, если требуется подпись, и страховой суммы. Вновь созданные экземпляры Package затем добавляются к объекту Shipment . Программное обеспечение, имитирующее жизнь, имеет смысл: каждая посылка принадлежит отправке, и каждая посылка должна иметь как минимум одну посылку.

 <?php // create a Package object and add it to the Shipment (a // shipment can have multiple packages) // this package is 24 pounds, has dimensions of 10 x 6 x 12 // inches, has an insured value of $274.95, and is being // sent signature required $package1 = new ShipPackage( 24, array(10, 6, 12), array( 'signature_required' => true, 'insured_amount' => 274.95 ) ); $shipment->addPackage($package1); // weight and dimensions can be integers or floats, // although UPS always rounds up to the next whole number. // This package is 11.34 pounds and has dimensions of // 14.2 x 16.8 x 26.34 inches $package2 = new ShipPackage( 11.34, array(14.2, 16.8, 26.34) ); $shipment->addPackage($package2); 

За занавесом: объект отгрузки

Откройте Awsp/Ship/Shipment.php и мы рассмотрим объект Shipment , который в основном будет содержать все, что наши плагины отправителя должны знать о доставке.

Конструктор принимает массив данных об отправке (и сохраняет его как свойство объекта), который представляет собой информацию получателя и, при необходимости, информацию о доставке, если она отличается от адреса отправителя. Затем конструктор вызывает sanitizeInput() чтобы убедиться, что массив безопасен для использования, и isShipmentValid() чтобы убедиться, что вся необходимая информация была предоставлена.

Кроме того, у нас есть открытый метод get() который принимает имя поля (ключ массива) и возвращает соответствующее значение из массива данных отгрузки, а открытые функции addPackage() и getPackages() , как вы уже догадались, добавили упаковать в отгрузку и получить объекты Package которые принадлежат отгрузке.

За занавесом: объект (ы) пакета

Откройте Awsp/Ship/Package.php и мы рассмотрим объект Package , который в основном будет содержать все, что наши плагины отправителя должны знать об отдельном пакете. Объекты Package являются частью объекта Shipment , и Shipment может иметь столько объектов Package сколько необходимо.

Конструктор Package принимает вес пакета, размеры (в любом порядке) и необязательный массив параметров, таких как описание, тип, страховая сумма и необходимость подписи. Вес и параметры задаются в свойствах объекта, а размеры располагаются в порядке от самого длинного до самого короткого. Затем мы присваиваем их по порядку свойствам объекта $length , $width и $height . Это важно для плагинов отправителя, потому что длина всегда должна быть самым длинным измерением. Затем он использует isPackageValid() чтобы убедиться, что все необходимые параметры присутствуют и имеют правильный тип. И, наконец, метод calcPackageSize calculatePackageSize() используется для определения размера пакета (длина плюс обхват), который будет использоваться некоторыми подключаемыми модулями.

Другими открытыми функциями, доступными из объекта Package являются get() который возвращает свойство объекта, getOption() который возвращает настройку определенного параметра, и несколько вспомогательных функций для преобразования веса и длины для плагинов отправителя.

Плагины грузоотправителя

У нас есть груз с пакетами, и теперь нам нужно получить доступ к плагину грузоотправителя, который мы хотим использовать. Плагин будет принимать объект Shipment вместе с массивом $config (определен в includes/config.php ).

 <?php // create the shipper object and pass it the shipment // and config data $ups = new ShipUps($shipment, $config); 

Наш объект Ups или любой другой плагин грузоотправителя, который мы создадим позже, будет реализовывать ShipperInterface , наш контракт, который позволяет нам гарантировать, что независимо от того, какого грузоотправителя мы используем, публичные функции (интерфейс) всегда будут одинаковыми. Как показано в этом отрывке из ShipperInterface.php , все наши плагины отправителя должны иметь метод setShipment() для установки ссылки на объект Shipment , метод setConfig() для установки копии массива config, метод getRate() для получения ставки доставки и метод createLabel() для создания этикетки доставки.

 <?php interface ShipperInterface { public function setShipment(Shipment $Shipment); public function setConfig(array $config); public function getRate(); public function createLabel(); } 

Получение тарифов доставки

Чтобы рассчитать стоимость доставки для нашего пакета, мы вызовем метод getRate() нашего объекта Ups . Поскольку он будет выполнять сетевые вызовы, нам нужно обязательно обернуть его в блок try / catch на случай, если что-то пойдет не так.

Предполагая, что с нашими данными нет ошибок, объект Ups организует нашу информацию в формате, который распознает API UPS, отправляет ее и обрабатывает ответ в объект RateResponse который будет единообразным для всех включаемых нами грузоотправителей.

 <?php // calculate rates for shipment - returns an instance of // RatesResponse try { $rates = $ups->getRate(); } catch(Exception $e) { exit('Error: ' . $e->getMessage()); } 

Затем мы можем просмотреть массив служб, чтобы отобразить доступные варианты доставки:

 <!-- output rates response --> <dl> <dt><strong>Status</strong></dt> <dd><?php echo $rates->status; ?></dd> <dt><strong>Rate Options</strong></dt> <dd> <ul> <?php foreach ($rates->services as $service) { // display the service, cost, and a link to create the // label echo '<li>' . $service['service_description'] . ': ' . '$' . $service['total_cost'] . ' - <a href="?action=label&service_code=' . $service['service_code'] . '">Create Label</a></li>'; ?> <li>Service Message: <ul> <?php // display any service specific messages foreach($service['messages'] as $message) { echo '<li>' . $message . '</li>'; } ?> </ul> </li> <?php // display a breakdown of multiple packages if there are // more than one if ($service['package_count'] > 1) { ?> <li>Multiple Package Breakdown: <ol> <?php foreach ($service['packages'] as $package) { echo '<li>$' . $package['total_cost'] . '</li>'; } ?> </ol> </li> <?php } } ?> </ul> </dd> </dl> 

За занавесом: объект RateResponse

Объект RateResponse по сути, является простым объектом, который содержит наши данные о RateResponse в стандартизированном формате, так что независимо от того, какой плагин-грузоотправитель мы используем, объект (и, следовательно, как мы взаимодействуем с ним) всегда будет одинаковым. В этом истинная красота абстракции!

Если вы откроете Awsp/Ship/RateResponse.php вы увидите, что объект просто содержит свойство с именем $status которое всегда будет равно «Success» или «Error», и массив с именем $services . В этом массиве будет элемент для каждой опции доставки, возвращаемой подключаемым модулем, и каждый элемент будет содержать «messages», «service_code», «service_description», «total_cost», «currency», «package_count» и массив называется «пакеты», в котором содержатся следующие данные для каждого пакета: «base_cost», «option_cost», «total_cost», «weight», «billed_weight» и «weight_unit».

С данными, содержащимися и легко извлекаемыми из объекта RateResponse , у вас должно быть все необходимое, чтобы предоставить вашему клиенту варианты тарифа доставки.

Создание этикетки доставки

Из-за абстрактного API ваш клиент был впечатлен настроенными вариантами доставки, которые вы смогли предоставить, и они совершили покупку. Вы обработали их заказ и готовы создать этикетку доставки. В идеале все, что нам нужно сделать, это вызвать createLabel() для объекта Shipper и передать ему желаемую опцию доставки.

 <?php // set label parameters $params['service_code'] = '03'; // ground shipping // send request for a shipping label try { // return the LabelResponse object $label = $ups->createLabel($params); } catch (Exception $e){ exit('Error: ' . $e->getMessage()); } 

Если с данными не возникла LabelResponse будет возвращен объект LabelResponse , содержащий состояние запроса, общую стоимость доставки, а также массив, содержащий номер отслеживания и закодированное в LabelResponse -64 изображение этикетки, а также тип изображение (GIF в случае UPS) для каждой транспортной этикетки.

За занавесом: объект LabelResponse

Подобно объекту RateResponse объект LabelResponse — это простой объект, который содержит наши данные меток в стандартизированном формате, поэтому независимо от того, какой подключаемый модуль отправителя мы используем, этот объект (и, следовательно, то, как мы взаимодействуем с ним) всегда будет одинаковым. Абстракция потрясающая!

Если вы откроете Awsp/Ship/LabelResponse.php вы увидите, что объект просто содержит свойства с именем $status которые всегда будут иметь значение «Успех» или «Ошибка», $shipment_cost который является общей стоимостью доставки, и массив с именем $labels . В этом массиве будет элемент для каждой метки, каждый из которых представляет собой массив, содержащий «tracking_number» и «label_image», который является закодированным в base-64 изображением метки, и «label_file_type», указывающий тип изображения, которым оно является (наш ИБП метки — это GIF-изображения).

С данными, содержащимися в объекте LabelResponse и легко извлеченными из LabelResponse , у вас будет все, что нужно для извлечения, печати и сохранения своих номеров отслеживания и меток.

За кулисами: плагин грузоотправителя UPS

Работа плагина отправителя, в нашем случае Awsp/Ship/Ups.php , заключается в том, чтобы взять наш стандартизированный ввод в объектах Package и Shipment и преобразовать его в форму, понятную для API отправителя. UPS предлагает свои API в двух вариантах: SOAP и XML-RPC и обновляет их по мере необходимости в июле и декабре каждого года. Этот плагин использует версию SOAP API, SoapClient в декабре 2012 года, и вам необходимо убедиться, что класс SoapClient включен в вашей установке PHP.

После принятия и обработки объекта Shipment , который содержит объект (ы) Package и массив $config (из includes/config.php ), конструктор устанавливает некоторые свойства объекта и некоторые значения, общие для всех запросов API.

Другие открытые функции getRate() и createLabel() getRate() работу по сборке всех этих данных в сложный массив, который будет понятен UPS. Каждый из этих методов затем вызывает sendRequest() для отправки SOAP-запроса к API UPS и получения ответа. Затем набор защищенных функций выполняет грязную работу по преобразованию ответа SOAP в наши стандартизированные RateResponse или LabelResponse зависимости от того, что было запрошено.

Вывод

Это было много, чтобы прочитать, но вы сделали это! С помощью простого набора вызовов вы можете запрашивать тарифы и создавать метки через API UPS или любой API грузоотправителя с подходящим плагином. Все тайны и сложности API-интерфейсов устраняются, что позволяет нам отделить его от остальной части кодовой базы и избавить от многих будущих проблем с обслуживанием. Когда грузоотправитель обновляет свой API, единственным файлом, который вам нужно будет изменить, должен быть плагин грузоотправителя.

UPS проделала отличную работу по документированию своих API, и для краткости я не включил в этот простой пример МНОГИЕ функции и опции. Если вам необходимо расширить плагин грузоотправителя, документация по ИБП всегда должна быть вашей первой остановкой.

Хотите увидеть плагин для другого грузоотправителя? Пожалуйста, дайте мне знать в комментариях ниже. Если на это есть спрос, мы можем продолжить эту статью. Небольшой бесплатный совет: если вы хотите интегрировать доставку через почтовое отделение США, сохраните БОЛЬШУЮ головную боль и не тратьте время на использование их официального API. Посетите stamps.com или другого поставщика, утвержденного USPS.

Пожалуйста, не стесняйтесь загружать копию или разветвлять эту библиотеку абстракций на моей странице GitHub по адресу github.com/alexfraundorf-com/ship и отправлять отчеты о проблемах для любых найденных ошибок. Я сделаю все возможное, чтобы исправить их и обработать обновления как можно быстрее.

Спасибо за чтение и счастливого PHPing!

Изображение через Fotolia