Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add USPS tracking capabilities #44

Closed
wants to merge 18 commits into from

6 participants

@Capncavedan

Accessed in same manner as UPS and FedEx tracking:

usps = USPS.new(:login => 'USPSlogin')
tracking_info = usps.find_tracking_info('9102901000462189604217')  

tracking_info.shipment_events.each do |event|
  puts "#{event.name} at #{event.location.city}, #{event.location.state} on #{event.time}. #{event.message}"
end
@mperham

Yes please!

@jbrowning

Yes please from me as well!

@Capncavedan

Sorry to bug you @csaunders - do you feel this is ready to pull in, or is there more you would like to see in it?

@csaunders
Collaborator

I need to go over the code a bit more thoroughly and ensure that it doesn't break any expectations. I'll try to go through it in more details soon.

If you could verify that it at least works on ruby 1.8.7 and 1.9.3 that's a start.

@Capncavedan

There we go. I hadn't tested with 1.8.7 (apologies). Found a unit test failure due to difference in default date-as-a-string formatting.

Now all unit tests and the USPS remote tests pass with 1.8.7p358 and 1.9.3p194.

@csaunders
Collaborator

Merged.

Thanks :)

@csaunders csaunders closed this
@csaunders csaunders reopened this
@csaunders
Collaborator

Disregard that, something came up. I'll have to look through it.

@csaunders
Collaborator

Could you try rebasing off of the current master? It seems like somethings changed that's causing all of your tests to fail. Then there's a time cop issue with @odorcicd is looking into.

test_find_tracking_info_should_parse_response_into_correct_number_of_shipment_events(USPSTest):
NoMethodError: undefined method `parameterize' for nil:NilClass
    /Users/csaunders/development/ruby/active_shipping/lib/active_shipping/shipping/tracking_response.rb:16:in `initialize'
    /Users/csaunders/development/ruby/active_shipping/lib/active_shipping/shipping/carriers/usps.rb:480:in `new'
    /Users/csaunders/development/ruby/active_shipping/lib/active_shipping/shipping/carriers/usps.rb:480:in `parse_tracking_response'
    /Users/csaunders/development/ruby/active_shipping/lib/active_shipping/shipping/carriers/usps.rb:122:in `find_tracking_info'
    /Users/csaunders/development/ruby/active_shipping/test/unit/carriers/usps_test.rb:34:in `test_find_tracking_info_should_parse_response_into_correct_number_of_shipment_events'
    /Users/csaunders/.rbenv/versions/1.9.3-p0/lib/ruby/gems/1.9.1/gems/mocha-0.10.5/lib/mocha/integration/mini_test/version_230_to_262.rb:28:in `run'
@Capncavedan

Will do.

@Capncavedan

All unit tests and all USPS remote tests passing now.

(do all remote tests currently pass for you @csaunders ? I have failures on all NZPost, some Shipwire)

@csaunders
Collaborator

Yeah, I got those off of master too. There's a good chance it has something to do with our test data.

@ayb

Has this integration been completed yet? I'm trying to test it in console now but looks like USPS does not respond to the method "find_tracking_info"..

@Capncavedan

No I don't believe it has. You can see my fork of active_shipping here: https://github.com/Capncavedan/active_shipping - it has the USPS tracking features.

@csaunders
Collaborator

I'm trying to rebase this into master, but it's actually not rebasing cleanly.

It looks like all the merges make this a pain to nicely merge into master. I think this can easily be managed by cherry picking all the commits, though I'm not entirely sure.

If you'd mind cherry picking and then turning that into a single commit, it would be awesome.

Thanks

@Capncavedan

@csaunders Sorry, been swamped with some other things. I will get to this in the next week or so.

@MrGossett

+1 for this.

@csaunders
Collaborator

@Capncavedan don't worry about it. Just took care of it. I'll be merging in a rebased and slightly cleaned up version soon.

@csaunders csaunders closed this
@csaunders
Collaborator

Code was merged via a2b378a

@Capncavedan

Thanks, @csaunders - sorry I didn't get to it.

@csaunders
Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 27, 2012
  1. Start of adding USPS tracking capability

    Dan Buettner authored
    * Currently using FedEx sample doc; needs replaced
    * Unit only - not remote yet
  2. passing unit tests for USPS tracking

    Dan Buettner authored
  3. add USPS timestamp tracking info

    Dan Buettner authored
Commits on Mar 12, 2012
  1. first group of changes for USPS tracking

    Dan Buettner authored
  2. merge in changes from my own branch locally

    Dan Buettner authored
  3. Merge remote-tracking branch 'upstream/master'

    Dan Buettner authored
Commits on Apr 26, 2012
  1. further development of USPS tracking

    Dan Buettner authored
Commits on Apr 27, 2012
  1. USPS tracking response working

    Dan Buettner authored
    To-do: parse tracking events for location, datetime, message
Commits on May 8, 2012
  1. Cleanup, additional testing, parsing of more USPS data

    Dan Buettner authored
Commits on May 9, 2012
  1. @mperham
Commits on May 10, 2012
  1. @Capncavedan

    Merge pull request #1 from TheClymb/master

    Capncavedan authored
    Handle tracking info not found error in the test environment
Commits on May 22, 2012
  1. general cleanup prior to pull request

    Dan Buettner authored
  2. more general cleanup prior to pull request

    Dan Buettner authored
  3. further cleanup prior to pull request

    Dan Buettner authored
Commits on Jun 18, 2012
  1. Address date-format unit test failure in 1.8.7

    Dan Buettner authored
Commits on Jul 6, 2012
  1. Merge remote-tracking branch 'upstream/master'

    Dan Buettner authored
  2. Add carrier name to USPS tracking response

    Dan Buettner authored
This page is out of date. Refresh to see the latest.
View
88 lib/active_shipping/shipping/carriers/usps.rb
@@ -31,12 +31,14 @@ class USPS < Carrier
API_CODES = {
:us_rates => 'RateV4',
:world_rates => 'IntlRateV2',
- :test => 'CarrierPickupAvailability'
+ :test => 'CarrierPickupAvailability',
+ :track => 'TrackV2'
}
USE_SSL = {
:us_rates => false,
:world_rates => false,
- :test => true
+ :test => true,
+ :track => false
}
CONTAINERS = {
:envelope => 'Flat Rate Envelope',
@@ -48,6 +50,7 @@ class USPS < Carrier
:matter_for_the_blind => 'Matter for the blind',
:envelope => 'Envelope'
}
+
PACKAGE_PROPERTIES = {
'ZipOrigination' => :origin_zip,
'ZipDestination' => :destination_zip,
@@ -112,6 +115,13 @@ class USPS < Carrier
"WS" => "Western Samoa"
}
+ def find_tracking_info(tracking_number, options={})
+ options = @options.update(options)
+ tracking_request = build_tracking_request(tracking_number, options)
+ response = commit(:track, tracking_request, (options[:test] || false))
+ parse_tracking_response(response, options)
+ end
+
def self.size_code_for(package)
if package.inches(:max) <= 12
'REGULAR'
@@ -167,9 +177,16 @@ def valid_credentials?
def maximum_weight
Mass.new(70, :pounds)
end
-
+
protected
-
+
+ def build_tracking_request(tracking_number, options={})
+ xml_request = XmlNode.new('TrackRequest', 'USERID' => @options[:login]) do |root_node|
+ root_node << XmlNode.new('TrackID', :ID => tracking_number)
+ end
+ URI.encode(xml_request.to_s)
+ end
+
def us_rates(origin, destination, packages, options={})
request = build_us_rate_request(packages, origin.zip, destination.zip, options)
# never use test mode; rate requests just won't work on test servers
@@ -420,6 +437,69 @@ def package_valid_for_max_dimensions(package,dimensions)
return valid
end
+
+ def parse_tracking_response(response, options)
+ xml = REXML::Document.new(response)
+ root_node = xml.elements['TrackResponse']
+
+ success = response_success?(xml)
+ message = response_message(xml)
+
+ if success
+ tracking_number, origin, destination = nil
+ shipment_events = []
+ tracking_details = xml.elements.collect('*/*/TrackDetail'){ |e| e }
+
+ tracking_summary = xml.elements.collect('*/*/TrackSummary'){ |e| e }.first
+ tracking_details << tracking_summary
+
+ tracking_number = root_node.elements['TrackInfo'].attributes['ID'].to_s
+
+ tracking_details.each do |event|
+ location = nil
+ timestamp = nil
+ description = nil
+ if event.get_text.to_s =~ /^(.*), (\w+ \d\d, \d{4}, \d{1,2}:\d\d [ap]m), (.*), (\w\w) (\d{5})$/i ||
+ event.get_text.to_s =~ /^Your item \w{2,3} (out for delivery|delivered) at (\d{1,2}:\d\d [ap]m on \w+ \d\d, \d{4}) in (.*), (\w\w) (\d{5})\.$/i
+ description = $1.upcase
+ timestamp = $2
+ city = $3
+ state = $4
+ zip_code = $5
+ location = Location.new(:city => city, :state => state, :postal_code => zip_code, :country => 'USA')
+ end
+ if location
+ time = Time.parse(timestamp)
+ zoneless_time = Time.utc(time.year, time.month, time.mday, time.hour, time.min, time.sec)
+ shipment_events << ShipmentEvent.new(description, zoneless_time, location)
+ end
+ end
+ shipment_events = shipment_events.sort_by(&:time)
+ end
+
+ TrackingResponse.new(success, message, Hash.from_xml(response),
+ :carrier => @@name,
+ :xml => response,
+ :request => last_request,
+ :shipment_events => shipment_events,
+ :destination => destination,
+ :tracking_number => tracking_number
+ )
+ end
+
+ def response_status_node(document)
+ document.elements['*/*/TrackSummary'] || document.elements['Error/Description']
+ end
+
+ def response_success?(document)
+ summary = response_status_node(document).get_text.to_s
+ !(summary =~ /There is no record of that mail item/ || summary =~ /This Information has not been included in this Test Server\./)
+ end
+
+ def response_message(document)
+ response_node = response_status_node(document)
+ response_status_node(document).get_text.to_s
+ end
def commit(action, request, test = false)
ssl_get(request_url(action, request, test))
View
3  test/fixtures/xml/usps/tracking_request.xml
@@ -0,0 +1,3 @@
+<TrackRequest USERID="xxxxxxxx">
+<TrackID ID="EJ958083578US"></TrackID>
+</TrackRequest>
View
13 test/fixtures/xml/usps/tracking_response.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<TrackResponse>
+<TrackInfo ID="9102901000462189604217">
+<TrackSummary>Your item is out for delivery at 8:13 am on January 27, 2012 in DES MOINES, IA 50311.</TrackSummary>
+<TrackDetail>Sorting Complete, January 27, 2012, 8:03 am, DES MOINES, IA 50311</TrackDetail>
+<TrackDetail>Electronic Shipping Info Received, January 27, 2012</TrackDetail>
+<TrackDetail>Arrival at Post Office, January 26, 2012, 11:21 am, DES MOINES, IA 50311</TrackDetail>
+<TrackDetail>Departed Shipping Partner Facility, January 24, 2012, 7:45 am, GRAND PRAIRIE, TX 75050</TrackDetail>
+<TrackDetail>Departed Shipping Partner Facility, January 23, 2012, 2:49 am, PHOENIX, AZ 85043</TrackDetail>
+<TrackDetail>Arrived Shipping Partner Facility, January 22, 2012, 5:00 pm, PHOENIX, AZ 85043</TrackDetail>
+<TrackDetail>Picked Up by Shipping Partner, January 22, 2012, 4:30 pm, PHOENIX, AZ 85043</TrackDetail>
+</TrackInfo>
+</TrackResponse>
View
3  test/fixtures/xml/usps/tracking_response_failure.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<TrackResponse><TrackInfo ID="ABC123XYZ"><TrackSummary>There is no record of that mail item. If it was mailed recently, it may not yet be tracked. Please try again later.</TrackSummary></TrackInfo>
+</TrackResponse>
View
8 test/fixtures/xml/usps/tracking_response_test_error.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<Error>
+ <Number>-2147219040</Number>
+ <Source>SOLServerTest;SOLServerTest.Track_Respond</Source>
+ <Description>This Information has not been included in this Test Server.</Description>
+ <HelpFile></HelpFile>
+ <HelpContext></HelpContext>
+</Error>
View
12 test/remote/usps_test.rb
@@ -8,6 +8,18 @@ def setup
@carrier = USPS.new(fixtures(:usps))
end
+ def test_tracking
+ assert_nothing_raised do
+ @carrier.find_tracking_info('EJ958083578US', :test => true)
+ end
+ end
+
+ def test_tracking_with_bad_number
+ assert_raises ResponseError do
+ response = @carrier.find_tracking_info('abc123xyz')
+ end
+ end
+
def test_zip_to_zip
assert_nothing_raised do
response = @carrier.find_rates(
View
87 test/unit/carriers/usps_test.rb
@@ -1,13 +1,95 @@
require 'test_helper'
class USPSTest < Test::Unit::TestCase
-
+
def setup
@packages = TestFixtures.packages
@locations = TestFixtures.locations
@carrier = USPS.new(:login => 'login')
+ @tracking_response = xml_fixture('usps/tracking_response')
+ @tracking_response_failure = xml_fixture('usps/tracking_response_failure')
+ end
+
+ def test_tracking_failure_should_raise_exception
+ @carrier.expects(:commit).returns(@tracking_response_failure)
+ assert_raises ResponseError do
+ @carrier.find_tracking_info('abc123xyz', :test => true)
+ end
+ end
+
+ def test_find_tracking_info_should_handle_not_found_error
+ @carrier.expects(:commit).returns(xml_fixture('usps/tracking_response_test_error'))
+ assert_raises ResponseError do
+ @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ end
+ end
+
+ def test_find_tracking_info_should_return_a_tracking_response
+ @carrier.expects(:commit).returns(@tracking_response)
+ assert_instance_of ActiveMerchant::Shipping::TrackingResponse, @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ end
+
+ def test_find_tracking_info_should_parse_response_into_correct_number_of_shipment_events
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal 7, response.shipment_events.size
end
+ def test_find_tracking_info_should_return_shipment_events_in_ascending_chronological_order
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal response.shipment_events.map(&:time).sort, response.shipment_events.map(&:time)
+ end
+
+ def test_find_tracking_info_should_have_correct_timestamps_for_shipment_events
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal ['2012-01-22 16:30:00 UTC',
+ '2012-01-22 17:00:00 UTC',
+ '2012-01-23 02:49:00 UTC',
+ '2012-01-24 07:45:00 UTC',
+ '2012-01-26 11:21:00 UTC',
+ '2012-01-27 08:03:00 UTC',
+ '2012-01-27 08:13:00 UTC'], response.shipment_events.map{ |e| e.time.strftime('%Y-%m-%d %H:%M:00 %Z') }
+ end
+
+ def test_find_tracking_info_should_have_correct_names_for_shipment_events
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217')
+ assert_equal ["PICKED UP BY SHIPPING PARTNER",
+ "ARRIVED SHIPPING PARTNER FACILITY",
+ "DEPARTED SHIPPING PARTNER FACILITY",
+ "DEPARTED SHIPPING PARTNER FACILITY",
+ "ARRIVAL AT POST OFFICE",
+ "SORTING COMPLETE",
+ "OUT FOR DELIVERY"], response.shipment_events.map(&:name)
+ end
+
+ def test_find_tracking_info_should_have_correct_locations_for_shipment_events
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal ["PHOENIX, AZ, 85043",
+ "PHOENIX, AZ, 85043",
+ "PHOENIX, AZ, 85043",
+ "GRAND PRAIRIE, TX, 75050",
+ "DES MOINES, IA, 50311",
+ "DES MOINES, IA, 50311",
+ "DES MOINES, IA, 50311"], response.shipment_events.map{|e| e.location}.map{|l| "#{l.city}, #{l.state}, #{l.postal_code}"}
+ end
+
+ def test_find_tracking_info_destination
+ # USPS API doesn't tell where it's going
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal response.destination, nil
+ end
+
+ def test_find_tracking_info_tracking_number
+ @carrier.expects(:commit).returns(@tracking_response)
+ response = @carrier.find_tracking_info('9102901000462189604217', :test => true)
+ assert_equal response.tracking_number, '9102901000462189604217'
+ end
+
def test_size_codes
assert_equal 'REGULAR', USPS.size_code_for(Package.new(2, [1,12,1], :units => :imperial))
assert_equal 'LARGE', USPS.size_code_for(Package.new(2, [12.1,1,1], :units => :imperial))
@@ -51,7 +133,6 @@ def test_parse_international_rate_response
e.response
end
-
expected_xml_hash = Hash.from_xml(fixture_xml)
actual_xml_hash = Hash.from_xml(response.xml)
@@ -203,4 +284,4 @@ def build_service_hash(options = {})
"MaxDimensions"=> options[:max_dimensions] ||
"Max. length 24\", Max. length, height, depth combined 36\""}
end
-end
+end
Something went wrong with that request. Please try again.