Friday, March 27, 2009

Perl UPS API Shootout

UPS, The United Parcel Service - AKA Big Brown - has been around since 1907.

Perl, the Practical Extraction and Reporting Language - AKA The Swiss Army Chainsaw - has been around since 1987.

I've been interacting with the UPS shipping tools since, well, since before they were converted to XML. About 3 weeks before, actually, and I had to rewrite that entire part of a client's website, for free, and the delay in getting paid nearly cost me my apartment.

Of course that would never have happened if I had instead used one of the available UPS modules on CPAN, because the modules' authors would have (probably) updated their code to work with the updated UPS interface for me.

So in an effort to save you some effort, I'll share my foray into the world of Perl/UPS integration options and let you know what I discover.


The first thing I noticed about Business::UPS is that the API seems a little...1999-ish.

 use Business::UPS;

my ($shipping,$ups_zone,$error) = getUPS(qw/GNDCOM 23606 23607 50/);
$error and die "ERROR: $error\n";
print "Shipping is \$$shipping\n";
print "UPS Zone is $ups_zone\n";

%track = UPStrack("z10192ixj29j39");
$track{error} and die "ERROR: $track{error};

# 'Delivered' or 'In-transit'
print "This package is $track{Current Status}\n";
Also, when I installed Business::UPS via CPAN, I got version 1.13 instead of 2.0. After manually downloading version 2.0 and doing the standard incantation...
perl Makefile.PL
make test
sudo make install
...I discovered that the file does not actually test anything except to make sure that the module does in fact compile.

I attempted to email the author - - and the email bounced.

The module might be filed under the Business:: namespace, but until the author Justin Wheeler adds some real unit tests to this distribution, I wouldn't trust Business::UPS to my business, that's for sure. The apparently abandoned status does not bode well, either.


This is a new distribution (as of March 26th, 2009) and offers a clean, object-oriented interface to some of the UPS API. Webservice::UPS is built on Mouse, a minimal implementation of Moose, the postmodern object system for Perl. This means that Webservice::UPS benefits from the features in Mouse, while remaining lightweight enough for web applications written in Apache2::ASP or Catalyst.

Unfortunately the module does not (yet) offer the ability to request shipping rates and methods for a package, but I've asked the author Kyle Brandt about adding that functionality.


Webservice::UPS looks fairly nice and just might be the right tool for your UPS package-tracking needs. However, the lack of support for rate and shipping method requests makes me continue the search for a total solution.


It appears to have been originally released in March of 2004, and the last release was in June of the same year. Sure, the UPS e-commerce tools don't appear to have changed that much since then, but when you download the documentation ( you see that the copyright is 2009. Something may have changed since 2004 - we don't know, because UPS doesn't give us a changelog with their UPS XML tools, but I have a hunch that we should have something a bit newer than 5 years old and apparently abandoned.

The module's author, Duane Hinkley of fame might make a fresh release soon, or transfer the module to someone else who will take care of it.


Don't use this module. Write your own. In fact, write your own and release it on CPAN.


It was only after looking through the changelog of Business::UPS that I discovered a reference to Business::Shipping:
@@ -1,3 +1,13 @@
+Fri Jun 20 09:27:24 2003 jwheeler
+ * Changed namespace to the original Business::UPS to make room for
+ Dan Browning's Business::Shipping modules.
+Tue Jun 10 13:28:37 2003 jwheeler
+ * Came across this module, was horribly outdated, rewrote it to work again.
+ * Renamed to Business::Shipping::UPS so as not to interfere with solomon's namespace.

Mon Feb 22 16:03:01 1999 msolomon

* Thanks to, changed a {} to ()
I also discovered that because of the way old modules come under the care of new authors, sometimes the person currently assigned blame of a distribution does not deserve it. (Yes, English is my first language - go figure.)

Business::Shipping was written by Dan Browning. Although there is a Google Code project page, no code has been checked into it.

The synopsis from the Business::Shipping CPAN page looks fairly straightforward:
 use Business::Shipping;

my $rate_request = Business::Shipping->rate_request(
shipper => 'UPS_Offline',
service => 'Ground Residential',
from_zip => '98683',
to_zip => '98270',
weight => 5.00,

$rate_request->execute() or die $rate_request->user_error();

print $rate_request->rate();
Installing Business::Shipping version 2.03 fails.

cpan[3]> install Business::Shipping
Running install for module 'Business::Shipping'
Running make for D/DB/DBROWNING/Business-Shipping-2.03.tar.gz
Has already been unwrapped into directory /home/john/.cpan/build/Business-Shipping-2.03-l3GrDY
Has already been made
Running make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/16_Pod-Coverage............skipped: (no reason given)
t/20_business_shipping.......skipped: (no reason given)
t/21_preload.................skipped: (no reason given)
t/30_USPS_Online.............skipped: (no reason given)
t/31_USPS_Online_Tracking....skipped: (no reason given)
t/40_UPS_Online..............skipped: (no reason given)
t/41_UPS_Online_Shop.........skipped: (no reason given)
t/42_UPS_Online_COD..........skipped: (no reason given)
t/45_UPS_Offline.............skipped: (no reason given)
t/61_Log4perl................1/? Global symbol "$INFO" requires explicit package name at t/61_Log4perl.t line 16.
Execution of t/61_Log4perl.t aborted due to compilation errors.
# Looks like your test exited with 255 just after 2.
t/61_Log4perl................ Dubious, test returned 255 (wstat 65280, 0xff00)
All 2 subtests passed
t/70_countries...............skipped: (no reason given)
t/80_usertag_sim.............skipped: (no reason given)

Test Summary Report
t/61_Log4perl (Wstat: 65280 Tests: 2 Failed: 0)
Non-zero exit status: 255
Files=15, Tests=42, 3 wallclock secs ( 0.06 usr 0.01 sys + 2.16 cusr 0.19 csys = 2.42 CPU)
Result: FAIL
Failed 1/15 test programs. 0/42 subtests failed.
make: *** [test_dynamic] Error 255
/usr/bin/make test -- NOT OK
//hint// to see the cpan-testers results for installing this module, try:
reports DBROWNING/Business-Shipping-2.03.tar.gz
Running make install
make test had returned bad status, won't install without force
Failed during this command:
DBROWNING/Business-Shipping-2.03.tar.gz : make_test NO
I did a "force install" since the error appeared to be a syntax error in a test (not a good sign).

I tried running the code from the synopsis and received the following error:
john@ubuntu-pc1:~/Desktop/ups$ perl
Use of uninitialized value $/ in join or string at /usr/local/share/perl/5.10.0/Config/ line 2117.
Can't use string ("service=XDM to_country=Australia") as an ARRAY ref while "strict refs" in use at /usr/local/share/perl/5.10.0/Business/Shipping/ line 172.
That error does not make any sense, since my test script was trying to ship something from Denver to Los Angeles, not Australia.


Do not use Business::Shipping, especially not for business. I tried to email Dan Browning at but my email bounced.


I started this post thinking "It's been a while since I did any UPS stuff with Perl - there's got to be some great tools out there now." Well maybe not so much, not today anyway.

We Perl hackers should have a nice interface to find out whether we can ship UPS Ground from Hawaii to Florida, or if Next Day Air is an option for shipping something 2 miles away. All it takes is 45 minutes of winding your way through the bizzare maze of broken links and password requests on the UPS website, plus the ability to make a few HTTP requests and send some HORRIBLY BROKEN XML to the UPS web services (more on that later).


Peter Newman said...


Firstly your comments form link seems to be broken, I had to hack URLs around to make a link that would work.

Thanks for the post, very interesting. I had a vague interest in automating the tracking of a few parcels and was slightly disappointed to find almost all the modules require a UPS API account, and it seems that requires a full blown UPS account, which is more hassle than it's worth as I generally only receive rather than send parcels.

However neither Business::UPS nor WWW::UPS::Detail require such accounts as they just scrape the UPS webpages to get the data you need. Unfortunately, similar to your comments, neither worked out the box, as UPS must have redesigned their site a bit, which is obviously the benefit of the XML route. Anyway FWIW/for other future searchers, I've created a patch for WWW::UPS::Detail, so I'd recommend that patched version to people who only need to track and don't have/want a UPS account. The relevant bug ticket is #77560, hopefully they'll integrate the patch shortly.