Abstracting Shipping APIs
Key Takeaways
- Utilize an abstraction layer to integrate multiple shipping APIs (UPS, FedEx, USPS) into your e-commerce platform, enabling a unified interface for various shipping operations.
- Start by setting up a shipper account and obtaining necessary API keys and documentation, ensuring compliance with the shipper’s guidelines and procedures.
- Define and manage shipments and packages through standardized object classes (Shipment and Package) within your code, which simplifies the handling of different shipping parameters and requirements.
- Implement shipper plugins to interact with specific shipping APIs, allowing for fetching rates and creating shipping labels without altering the core application code.
- Handle errors gracefully and secure the abstraction layer to protect sensitive data, ensuring that your e-commerce platform provides reliable and secure shipping options to customers.
Getting Started with UPS
In this article I’ll focus on using the UPS API, but by writing a plugin for different shippers (such as FedEx or USPS), you can access their services as well with only negligible, if any, code changes to your core application. In order to get started with using UPS, you will need to sign up for an online account at www.ups.com using your existing shipper number. Make sure to pick a username and password that you will be comfortable using for a while as the API requires both of them for every call. Next, go to https://www.ups.com/upsdeveloperkit and register for access to the UPS API. This is where you will obtain your API key and are able to download documentation for the different API packages. (Note: There’s a known issue with this section of UPS’s site and Chrome will sometimes return a blank page. You may need to use an alternate browser.) Keep in mind that when you use the UPS API (or any shipper’s API for that matter), you agree to follow their rules and procedures. Make sure to review and follow them, especially including their instructions before using your code in production. Next download or clone the shipping abstraction layer package from GitHub at github.com/alexfraundorf-com/ship and upload it to your server that is running PHP 5.3 or later. Open theincludes/config.php
file. You’ll need to enter your UPS details here, and the field names should all be self-explanatory. Note that the UPS shipper address needs to match what UPS has on file for your account or an error will occur.
Defining Shipments and Packages
Now to define aShipment
object. At instantiation, it will accept an array containing the receiver’s information, and optionally a ship from address if it is different from the shipper’s information in our config file.
<?php
// create a Shipment object
$shipment = new ShipShipment($shipmentData);
Next we need some details about what we are shipping. Let’s create a Package
object which accepts the weight, package dimensions, and an optional array of some basic options such as a description, if a signature is required, and the insured amount. The newly instantiated Package
(s) are then added to the Shipment
object. Software imitating life just makes sense: Every package belongs to a shipment, and every shipment must have at least one package.
<?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);
Behind the Curtain: The Shipment Object
OpenAwsp/Ship/Shipment.php
and we’ll examine the Shipment
object, which basically will hold everything that our shipper plugins need to know about the shipment.
The constructor accepts an array of the shipment data (and stores it as an object property) which is the receiver’s information and optionally the ship from information if it differs from the shipper’s address. Next the constructor calls sanitizeInput()
to make sure that array is safe to use, and isShipmentValid()
to make sure that all required information has been provided.
Besides that, we have a public method get()
which accepts a field name (array key) and returns the corresponding value from the shipment data array, and the public functions addPackage()
and getPackages()
to, you guessed it, add a package to the shipment and retrieve the Package
objects that belong to the shipment.
Behind the Curtain: The Package Object(s)
OpenAwsp/Ship/Package.php
and we’ll examine the Package
object, which basically will hold everything that our shipper plugins need to know about the individual package. Package
objects are part of the Shipment
object, and the Shipment
can have as many Package
objects as needed.
The Package
constructor accepts the package weight, dimensions (in any order), and an optional array of options such as description, type, insured amount, and whether a signature is required. The weight and options are set in object properties and the dimensions are put in order from longest to shortest. We then assign them in order to the object properties $length
, $width
, and $height
. This is important to the shipper plugins because length must always be the longest dimension. It then uses isPackageValid()
to make sure all needed parameters are present and of the proper type. Finally calculatePackageSize()
is used to figure out the package’s size (length plus girth), which will be used by some shipper plugins.
Other public functions available from the Package
object are get()
which returns a property of the object, getOption()
which returns a specific option’s setting, and several helper functions for converting weight and length for the shipper plugins.
Shipper Plugins
We have a shipment with packages, and now we need to access the shipper plugin that we want to use. The plugin will accept theShipment
object along with the $config
array (defined in includes/config.php
).
<?php
// create the shipper object and pass it the shipment
// and config data
$ups = new ShipUps($shipment, $config);
Our Ups
object, or any other shipper plugin we create later, will implement the ShipperInterface
, our contract that allows us to guarantee that no matter which shipper we use, the public functions (interface) will always be the same. As shown in this excerpt from ShipperInterface.php
, all of our shipper plugins must have a setShipment()
method to set a reference to the Shipment
object, a setConfig()
method to set a copy of the config array, a getRate()
method to retrieve a shipping rate, and a createLabel()
method to create a shipping label.
<?php
interface ShipperInterface
{
public function setShipment(Shipment $Shipment);
public function setConfig(array $config);
public function getRate();
public function createLabel();
}
Fetching Shipping Rates
In order to calculate the shipping rates for our package, we’ll call thegetRate()
method of our Ups
object. Since it will be performing network calls, we’ll need to make sure to wrap it in a try/catch block in case something goes wrong.
Assuming that there are no errors with our data, the Ups
object organizes our information into a format that the UPS API recognizes, sends it off, and processes the response into a RateResponse
object that will be uniform for all the shippers we incorporate.
<?php
// calculate rates for shipment - returns an instance of
// RatesResponse
try {
$rates = $ups->getRate();
}
catch(Exception $e) {
exit('Error: ' . $e->getMessage());
}
We can loop through the services array then to display the available shipping options:
<!-- 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>
Behind the Curtain: The RateResponse Object
TheRateResponse
object is essentially a simple object that contains our rate data in a standardized format, so that no matter which shipper plugin we use, the object (and therefore how we interface with it) will always be the same. That is the true beauty of abstraction!
If you open Awsp/Ship/RateResponse.php
you will see that the object simply holds a property called $status
which will always be ‘Success’ or ‘Error’ and an array called $services
. There will be an element in this array for each shipping option returned by the shipper plugin, and each element will contain ‘messages’, ‘service_code’, ‘service_description’, ‘total_cost’, ‘currency’, ‘package_count’, and an array called ‘packages’ that holds the following data for each package: ‘base_cost’, ‘option_cost’, ‘total_cost’, ‘weight’, ‘billed_weight’ and ‘weight_unit’.
With the data contained in and easily extracted from the RateResponse
object, you should have everything you need to supply your customer with the shipping rate options.
Creating a Shipping Label
Because of the abstracted API, your customer was impressed by the customized shipping options you were able to provide, and they made a purchase. You have processed their order and are ready to create the shipping label. Ideally, all we need to do is callcreateLabel()
on the Shipper
object, and pass it the desired shipping option.
<?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());
}
Unless there was a problem with the data, a LabelResponse
object will be returned containing the status of the request, the total cost of the shipment, and an array containing a tracking number and base-64 encoded image of the label, and the type of image (GIF in the case of UPS) for each shipping label.
Behind the Curtain: The LabelResponse Object
Similar to theRateResponse
object, the LabelResponse
object is a simple object that contains our label data in a standardized format, so that no matter which shipper plugin we use, the object (and therefore how we interface with it) will always be the same. Abstraction is awesome!
If you open Awsp/Ship/LabelResponse.php
you will see that the object simply holds properties called $status
which will always be ‘Success’ or ‘Error’, $shipment_cost
which is the total cost of the shipment, and an array called $labels
. There will be an element in this array for each label, each of which is an array containing ‘tracking_number’ and ‘label_image’ which is the base-64 encoded label image, and ‘label_file_type’ indicating the type of image it is (our UPS labels are GIF images).
With the data contained in and easily extracted from the LabelResponse
object, you will have everything you need to extract, print and save your tracking number(s) and label(s).
Behind the Curtain: The UPS Shipper Plugin
The job of the shipper plugin,Awsp/Ship/Ups.php
in our case, is to take our standardized input in the Package
and Shipment
objects and convert it into a form that is understood by the shipper API. UPS offers their API in two flavors, SOAP and XML-RPC and updates them as needed in July and December of each year. This plugin uses the December 2012 version of the SOAP API and you will need to make sure that the SoapClient
class is enabled in your PHP installation.
After accepting and processing the Shipment
object, which contains the Package
object(s), and the $config
array (from includes/config.php
), the constructor sets some object properties and some values common to all API requests.
The other public functions getRate()
and createLabel()
handle the work of assembling all of that data into a complex array that UPS will understand. Each of these methods then calls on sendRequest()
to send the SOAP request to the UPS API and retrieve the response. An assortment of protected functions then do the dirty work of translating the SOAP response into our standardized RateResponse
or LabelResponse
objects depending on what was requested.
Conclusion
That was a lot to read, but you made it! With a simple set of calls, you can request rates and create labels through the UPS API, or any shipper API with a suitable plugin. All of the mystery and complexities of the APIs get abstracted away allowing us to keep it decoupled from the rest of the codebase and save a lot of future maintenance headaches. When the shipper updates their API, the only file you will need to change should be the shipper plugin. UPS has done a very good job of documenting their API’s, and there are MANY features and options that I have not included in this simple example for the sake of brevity. If you need to extend the shipper plugin, the UPS documentation should always be your first stop. Would you like to see a plugin for a different shipper? Please let me know in the comments below. If there is enough demand for it, we may do a follow up to this article. A little free advice, however: If you would like to integrate shipping through the U.S. Post Office, save yourself a BIG headache, and don’t waste your time using their official API. Visit stamps.com or another USPS approved vendor instead. Please feel free to download a copy or fork this abstraction library on my GitHub page at github.com/alexfraundorf-com/ship and submit issue reports for any bugs that you find. I’ll do my best to fix them and handle updates as quickly as possible. Thanks for reading, and happy PHPing! Image via FotoliaFrequently Asked Questions (FAQs) on Abstracting Shipping APIs
What is the purpose of abstracting shipping APIs?
Abstracting shipping APIs is a process that simplifies the integration of various shipping services into a single application. This process allows developers to interact with multiple shipping APIs through a unified interface, reducing the complexity of dealing with different API structures. It also saves time and resources as developers don’t need to understand the intricacies of each shipping API. Instead, they can focus on the core functionality of their application.
How does abstracting shipping APIs benefit businesses?
Businesses that rely on multiple shipping services can greatly benefit from abstracting shipping APIs. It allows them to integrate various shipping options into their systems seamlessly, providing their customers with a variety of choices. This can lead to improved customer satisfaction and potentially increased sales. Additionally, it can streamline operations by automating shipping processes, reducing manual work, and minimizing errors.
What are the challenges in abstracting shipping APIs?
Abstracting shipping APIs can be challenging due to the differences in the structure, functionality, and documentation of each shipping API. Developers need to understand these differences and create an abstraction layer that can handle them. This requires a deep understanding of each API and the ability to design a flexible and robust abstraction layer. Additionally, maintaining the abstraction layer can be challenging as shipping APIs may change over time.
How can I handle errors when abstracting shipping APIs?
Handling errors is a crucial aspect of abstracting shipping APIs. Developers should implement robust error handling mechanisms to ensure that the application can gracefully handle API errors. This can involve validating API responses, catching exceptions, and providing meaningful error messages to the users. Additionally, developers should monitor the application to identify and fix errors quickly.
Can I use third-party libraries to abstract shipping APIs?
Yes, there are third-party libraries available that can simplify the process of abstracting shipping APIs. These libraries provide pre-built abstraction layers that can handle the complexities of interacting with multiple shipping APIs. However, developers should carefully evaluate these libraries to ensure that they meet their specific requirements and are actively maintained.
How can I test the abstraction layer?
Testing is a critical part of developing an abstraction layer for shipping APIs. Developers should write unit tests to verify the functionality of the abstraction layer. They should also perform integration tests to ensure that the abstraction layer works correctly with the actual shipping APIs. Additionally, developers can use mock APIs to simulate the behavior of the shipping APIs during testing.
How can I handle rate limiting when abstracting shipping APIs?
Rate limiting is a common feature of APIs, including shipping APIs. Developers should implement mechanisms to handle rate limiting, such as retrying requests after a certain period or slowing down the rate of requests. They should also monitor the rate limit status to avoid exceeding the limit and causing disruptions to the application.
How can I secure the abstraction layer?
Securing the abstraction layer is essential to protect sensitive data, such as shipping details and API keys. Developers should implement security measures such as encrypting sensitive data, using secure communication protocols, and regularly updating the abstraction layer to fix security vulnerabilities.
How can I handle versioning when abstracting shipping APIs?
Shipping APIs may have different versions with varying functionalities. Developers should design the abstraction layer to handle different API versions. This can involve using version-specific endpoints or parameters, and providing a way for the application to specify the API version.
How can I keep the abstraction layer up-to-date with changes in the shipping APIs?
Keeping the abstraction layer up-to-date requires regular monitoring of the shipping APIs for any changes. Developers should subscribe to the API updates from the shipping services and update the abstraction layer accordingly. They should also have a process in place to test and deploy the updates quickly to minimize disruptions to the application.
Alex's interest in programming began at about age 9 when he started entering hundreds of lines of BASIC into his family's Radio Shack Color Computer 2 only to see it crash because it used up the available 16K of memory! Fast-forward to 2003 when he stumbled upon Kevin Yank's Build Your Own Database Driven Website using PHP & MySQL. Ever since then, Alex has been hooked on PHP and SitePoint's books. When he isn't busy coding, he can be found spending time with his wife, Angie, and their son, Daniel.
Published in
·APIs·CMS & Frameworks·Debugging & Deployment·Miscellaneous·Patterns & Practices·PHP·Programming·Web·August 4, 2014
Published in
·Debugging & Deployment·Patterns & Practices·PHP·Programming·Web·Web Security·March 10, 2014
Published in
·Design·HTML & CSS·Mobile·Mobile UX·Mobile Web Development·Responsive Web Design·July 2, 2014