trackerific 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +1 -2
- data/Gemfile +3 -22
- data/Gemfile.lock +33 -107
- data/README.rdoc +35 -61
- data/Rakefile +10 -36
- data/lib/trackerific/configuration.rb +3 -46
- data/lib/trackerific/details.rb +2 -71
- data/lib/trackerific/event.rb +5 -49
- data/lib/trackerific/services/base.rb +45 -0
- data/lib/trackerific/services/fedex.rb +75 -85
- data/lib/trackerific/services/mock_service.rb +43 -54
- data/lib/trackerific/services/ups.rb +94 -103
- data/lib/trackerific/services/usps.rb +182 -186
- data/lib/trackerific/services.rb +41 -0
- data/lib/trackerific/version.rb +3 -0
- data/lib/trackerific.rb +30 -73
- data/spec/fixtures/{fedex_error_response.xml → fedex/error.xml} +0 -0
- data/spec/fixtures/{fedex_success_response.xml → fedex/success.xml} +0 -0
- data/spec/fixtures/{ups_error_response.xml → ups/error.xml} +0 -0
- data/spec/fixtures/{ups_success_response.xml → ups/success.xml} +0 -0
- data/spec/fixtures/{usps_city_state_lookup_response.xml → usps/city_state_lookup.xml} +0 -0
- data/spec/fixtures/{usps_error_response.xml → usps/error.xml} +0 -0
- data/spec/fixtures/{usps_success_response.xml → usps/success.xml} +0 -0
- data/spec/lib/trackerific/configuration_spec.rb +8 -39
- data/spec/lib/trackerific/details_spec.rb +13 -78
- data/spec/lib/trackerific/error_spec.rb +1 -5
- data/spec/lib/trackerific/event_spec.rb +14 -43
- data/spec/lib/trackerific/services/base_spec.rb +13 -0
- data/spec/lib/trackerific/services/fedex_spec.rb +59 -62
- data/spec/lib/trackerific/services/mock_service_spec.rb +33 -46
- data/spec/lib/trackerific/services/ups_spec.rb +46 -66
- data/spec/lib/trackerific/services/usps_spec.rb +80 -94
- data/spec/lib/trackerific/services_spec.rb +37 -0
- data/spec/lib/trackerific/version_spec.rb +5 -0
- data/spec/lib/trackerific_spec.rb +15 -53
- data/spec/spec_helper.rb +2 -9
- data/spec/support/fixtures.rb +4 -18
- data/spec/support/test_services.rb +23 -0
- data/trackerific.gemspec +24 -102
- metadata +151 -185
- data/VERSION +0 -1
- data/changelog +0 -9
- data/examples/custom_service_spec.rb +0 -44
- data/lib/helpers/options_helper.rb +0 -23
- data/lib/trackerific/service.rb +0 -100
- data/spec/lib/helpers/options_helper_spec.rb +0 -82
- data/spec/lib/trackerific/service_spec.rb +0 -44
- data/spec/support/trackerific.rb +0 -21
@@ -1,101 +1,91 @@
|
|
1
1
|
require 'httparty'
|
2
|
+
require 'builder'
|
2
3
|
|
3
4
|
module Trackerific
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
module Services
|
6
|
+
class FedEx < Base
|
7
|
+
include ::HTTParty
|
8
|
+
|
9
|
+
format :xml
|
10
|
+
base_uri "https://gateway.fedex.com"
|
11
|
+
|
12
|
+
self.register :fedex
|
13
|
+
|
13
14
|
# An Array of Regexp that matches valid FedEx package IDs
|
14
15
|
# @return [Array, Regexp] the regular expression
|
15
16
|
# @api private
|
16
|
-
def package_id_matchers
|
17
|
+
def self.package_id_matchers
|
17
18
|
[ /^[0-9]{15}$/ ]
|
18
19
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# and :meter
|
23
|
-
# @api private
|
24
|
-
def required_parameters
|
25
|
-
[:account, :meter]
|
20
|
+
|
21
|
+
def initialize(options={})
|
22
|
+
@options = options
|
26
23
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
24
|
+
|
25
|
+
# Tracks a FedEx package
|
26
|
+
# @param [String] package_id the package identifier
|
27
|
+
# @return [Trackerific::Details] the tracking details
|
28
|
+
# @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
|
29
|
+
# @example Track a package
|
30
|
+
# fedex = Trackerific::FedEx.new :account => 'account', :meter => 'meter'
|
31
|
+
# details = fedex.track_package("183689015000001")
|
32
|
+
# @api public
|
33
|
+
def track(package_id)
|
34
|
+
# request tracking information from FedEx via HTTParty
|
35
|
+
http_response = self.class.post "/GatewayDC", body: build_xml_request
|
36
|
+
# raise any HTTP errors
|
37
|
+
http_response.error! unless http_response.code == 200
|
38
|
+
# get the tracking information from the reply
|
39
|
+
track_reply = http_response["FDXTrack2Reply"]
|
40
|
+
# raise a Trackerific::Error if there is an error in the reply
|
41
|
+
raise Trackerific::Error, track_reply["Error"]["Message"] unless track_reply["Error"].nil?
|
42
|
+
# get the details from the reply
|
43
|
+
details = track_reply["Package"]
|
44
|
+
# convert them into Trackerific::Events
|
45
|
+
events = []
|
46
|
+
details["Event"].each do |e|
|
47
|
+
date = Time.parse("#{e["Date"]} #{e["Time"]}")
|
48
|
+
desc = e["Description"]
|
49
|
+
addr = e["Address"]
|
50
|
+
loc = "#{addr["StateOrProvinceCode"]} #{addr["PostalCode"]}"
|
51
|
+
events << Trackerific::Event.new(date, desc, loc)
|
52
|
+
end
|
53
|
+
# Return a Trackerific::Details containing all the events
|
54
|
+
Trackerific::Details.new(
|
55
|
+
details["TrackingNumber"], details["StatusDescription"], events
|
59
56
|
)
|
60
57
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
rh.AccountNumber @options[:account]
|
90
|
-
rh.MeterNumber @options[:meter]
|
91
|
-
end
|
92
|
-
r.PackageIdentifier do |pi|
|
93
|
-
pi.Value @package_id
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Builds the XML request to send to FedEx
|
62
|
+
# @return [String] a FDXTrack2Request XML
|
63
|
+
# @api private
|
64
|
+
def build_xml_request
|
65
|
+
xml = ""
|
66
|
+
# the API namespace
|
67
|
+
xmlns_api = "http://www.fedex.com/fsmapi"
|
68
|
+
# the XSI namespace
|
69
|
+
xmlns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
70
|
+
# the XSD namespace
|
71
|
+
xsi_noNSL = "FDXTrack2Request.xsd"
|
72
|
+
# create a new Builder to generate the XML
|
73
|
+
builder = ::Builder::XmlMarkup.new(:target => xml)
|
74
|
+
# add the XML header
|
75
|
+
builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
|
76
|
+
# Build, and return the request
|
77
|
+
builder.FDXTrack2Request "xmlns:api"=>xmlns_api, "xmlns:xsi"=>xmlns_xsi, "xsi:noNamespaceSchemaLocation" => xsi_noNSL do |r|
|
78
|
+
r.RequestHeader do |rh|
|
79
|
+
rh.AccountNumber @options[:account]
|
80
|
+
rh.MeterNumber @options[:meter]
|
81
|
+
end
|
82
|
+
r.PackageIdentifier do |pi|
|
83
|
+
pi.Value @package_id
|
84
|
+
end
|
85
|
+
r.DetailScans true
|
94
86
|
end
|
95
|
-
|
87
|
+
xml
|
96
88
|
end
|
97
|
-
xml
|
98
89
|
end
|
99
|
-
|
100
90
|
end
|
101
91
|
end
|
@@ -1,62 +1,51 @@
|
|
1
|
-
require 'date'
|
2
|
-
|
3
1
|
module Trackerific
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module Services
|
3
|
+
# Provides a mock service for using in test and development
|
4
|
+
class MockService < Base
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
self.register :mock_service
|
8
|
+
|
9
9
|
# Regular expression matchers for mocked Trackerific service
|
10
10
|
# @return [Array, Regexp] the regular expression
|
11
11
|
# @api private
|
12
|
-
def package_id_matchers
|
13
|
-
|
14
|
-
return [ ]
|
12
|
+
def self.package_id_matchers
|
13
|
+
[ /XXXXXXXXXX/, /XXXxxxxxxx/ ]
|
15
14
|
end
|
16
|
-
|
17
|
-
#
|
18
|
-
# @
|
19
|
-
# @
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
),
|
51
|
-
Trackerific::Event.new(
|
52
|
-
:date => Date.today - 2,
|
53
|
-
:description => "Package picked up for delivery.",
|
54
|
-
:location => "LOS ANGELES, CA"
|
55
|
-
)
|
56
|
-
]
|
57
|
-
)
|
58
|
-
else
|
59
|
-
raise Trackerific::Error, "Package not found."
|
15
|
+
|
16
|
+
# Sets up a mocked package details
|
17
|
+
# @param [String] package_id the package identifier
|
18
|
+
# @return [Trackerific::Details] the tracking details
|
19
|
+
# @raise [Trackerific::Error] raised when the server returns an error
|
20
|
+
# @example Track a package
|
21
|
+
# service = Trackerific::Services::MockedService.new
|
22
|
+
# details = service.track("XXXXXXXXXX") # => valid response
|
23
|
+
# details = service.track("XXXxxxxxxx") # => throws a Trackerific::Error exception
|
24
|
+
# @api public
|
25
|
+
def track(id)
|
26
|
+
if id == "XXXXXXXXXX"
|
27
|
+
Trackerific::Details.new(id, "Your package was delivered.",
|
28
|
+
[
|
29
|
+
Trackerific::Event.new(
|
30
|
+
:date => Date.today,
|
31
|
+
:description => "Package delivered.",
|
32
|
+
:location => "SANTA MARIA, CA"
|
33
|
+
),
|
34
|
+
Trackerific::Event.new(
|
35
|
+
:date => Date.today - 1,
|
36
|
+
:description => "Package scanned.",
|
37
|
+
:location => "SANTA BARBARA, CA"
|
38
|
+
),
|
39
|
+
Trackerific::Event.new(
|
40
|
+
:date => Date.today - 2,
|
41
|
+
:description => "Package picked up for delivery.",
|
42
|
+
:location => "LOS ANGELES, CA"
|
43
|
+
)
|
44
|
+
]
|
45
|
+
)
|
46
|
+
else
|
47
|
+
raise Trackerific::Error, "Package not found."
|
48
|
+
end
|
60
49
|
end
|
61
50
|
end
|
62
51
|
end
|
@@ -1,120 +1,111 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'builder'
|
1
3
|
require 'date'
|
2
4
|
|
3
5
|
module Trackerific
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
when 'test','development' then 'https://wwwcie.ups.com/ups.app/xml'
|
6
|
+
module Services
|
7
|
+
# Provides package tracking support for UPS.
|
8
|
+
class UPS < Base
|
9
|
+
self.register :ups
|
10
|
+
|
11
|
+
include ::HTTParty
|
12
|
+
format :xml
|
13
|
+
|
14
|
+
base_uri case (ENV['RAILS_ENV'] || 'production')
|
14
15
|
when 'production' then 'https://www.ups.com/ups.app/xml'
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
else 'https://wwwcie.ups.com/ups.app/xml'
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(options={})
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
18
23
|
# An Array of Regexp that matches valid UPS package IDs
|
19
24
|
# @return [Array, Regexp] the regular expression
|
20
25
|
# @api private
|
21
|
-
def package_id_matchers
|
26
|
+
def self.package_id_matchers
|
22
27
|
[ /^.Z/, /^[HK].{10}$/ ]
|
23
|
-
end
|
24
|
-
# The required parameters for tracking a UPS package
|
25
|
-
# @return [Array] the required parameters for tracking a UPS package
|
26
|
-
# @api private
|
27
|
-
def required_parameters
|
28
|
-
[:key, :user_id, :password]
|
29
28
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
29
|
+
|
30
|
+
# Tracks a UPS package
|
31
|
+
# @param [String] package_id the package identifier
|
32
|
+
# @return [Trackerific::Details] the tracking details
|
33
|
+
# @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
|
34
|
+
# @example Track a package
|
35
|
+
# ups = Trackerific::UPS.new key: 'api key', user_id: 'user', password: 'secret'
|
36
|
+
# details = ups.track_package("1Z12345E0291980793")
|
37
|
+
# @api public
|
38
|
+
def track(id)
|
39
|
+
@package_id = id
|
40
|
+
# connect to UPS via HTTParty
|
41
|
+
http_response = self.class.post('/Track', body: build_xml_request)
|
42
|
+
# throw any HTTP errors
|
43
|
+
http_response.error! unless http_response.code == 200
|
44
|
+
# Check the response for errors, return a Trackerific::Error, or parse
|
45
|
+
# the response from UPS and return a Trackerific::Details
|
46
|
+
case http_response['TrackResponse']['Response']['ResponseStatusCode']
|
47
|
+
when "0" then raise Trackerific::Error, parse_error_response(http_response)
|
48
|
+
when "1" then return parse_success_response(http_response)
|
49
|
+
else raise Trackerific::Error, "Invalid response code returned from server."
|
50
|
+
end
|
52
51
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
:location => loc
|
82
|
-
)
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
# Parses the response from UPS
|
56
|
+
# @return [Trackerific::Details]
|
57
|
+
# @api private
|
58
|
+
def parse_success_response(http_response)
|
59
|
+
# get the activity from the UPS response
|
60
|
+
activity = http_response['TrackResponse']['Shipment']['Package']['Activity']
|
61
|
+
# if there's only one activity in the list, we need to put it in an array
|
62
|
+
activity = [activity] if activity.is_a? Hash
|
63
|
+
# UPS does not provide a summary, so we'll just use the last tracking status
|
64
|
+
summary = activity.first['Status']['StatusType']['Description']
|
65
|
+
events = []
|
66
|
+
activity.each do |a|
|
67
|
+
# the time format from UPS is HHMMSS, which cannot be directly converted
|
68
|
+
# to a Ruby time.
|
69
|
+
hours = a['Time'][0..1]
|
70
|
+
minutes = a['Time'][2..3]
|
71
|
+
seconds = a['Time'][4..5]
|
72
|
+
date = Date.parse(a['Date'])
|
73
|
+
date = DateTime.parse("#{date} #{hours}:#{minutes}:#{seconds}")
|
74
|
+
desc = a['Status']['StatusType']['Description']
|
75
|
+
loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
|
76
|
+
events << Trackerific::Event.new(date, desc, loc)
|
77
|
+
end
|
78
|
+
|
79
|
+
Trackerific::Details.new(@package_id, summary, events)
|
83
80
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
# Parses a UPS tracking response, and returns any errors
|
93
|
-
# @return [String] the UPS tracking error
|
94
|
-
# @api private
|
95
|
-
def parse_error_response(http_response)
|
96
|
-
http_response['TrackResponse']['Response']['Error']['ErrorDescription']
|
97
|
-
end
|
98
|
-
|
99
|
-
# Builds the XML request to send to UPS for tracking a package
|
100
|
-
# @return [String] the XML request
|
101
|
-
# @api private
|
102
|
-
def build_xml_request
|
103
|
-
xml = ""
|
104
|
-
builder = ::Builder::XmlMarkup.new(:target => xml)
|
105
|
-
builder.AccessRequest do |ar|
|
106
|
-
ar.AccessLicenseNumber @options[:key]
|
107
|
-
ar.UserId @options[:user_id]
|
108
|
-
ar.Password @options[:password]
|
81
|
+
|
82
|
+
# Parses a UPS tracking response, and returns any errors
|
83
|
+
# @return [String] the UPS tracking error
|
84
|
+
# @api private
|
85
|
+
def parse_error_response(http_response)
|
86
|
+
http_response['TrackResponse']['Response']['Error']['ErrorDescription']
|
109
87
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
88
|
+
|
89
|
+
# Builds the XML request to send to UPS for tracking a package
|
90
|
+
# @return [String] the XML request
|
91
|
+
# @api private
|
92
|
+
def build_xml_request
|
93
|
+
xml = ""
|
94
|
+
builder = ::Builder::XmlMarkup.new(:target => xml)
|
95
|
+
builder.AccessRequest do |ar|
|
96
|
+
ar.AccessLicenseNumber @options[:key]
|
97
|
+
ar.UserId @options[:user_id]
|
98
|
+
ar.Password @options[:password]
|
99
|
+
end
|
100
|
+
builder.TrackRequest do |tr|
|
101
|
+
tr.Request do |r|
|
102
|
+
r.RequestAction 'Track'
|
103
|
+
r.RequestOption 'activity'
|
104
|
+
end
|
105
|
+
tr.TrackingNumber @package_id
|
114
106
|
end
|
115
|
-
|
107
|
+
return xml
|
116
108
|
end
|
117
|
-
return xml
|
118
109
|
end
|
119
110
|
end
|
120
111
|
end
|