forked from travishaynes/trackerific
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ups.rb
144 lines (129 loc) · 5.85 KB
/
ups.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
52
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'date'
module Trackerific
require 'httparty'
# Provides package tracking support for UPS.
class UPS < Trackerific::Service
# setup HTTParty
include ::HTTParty
format :xml
# use the test site for Rails development, production for everything else
base_uri defined?(Rails) ? case Rails.env
when 'test','development' then 'https://wwwcie.ups.com/ups.app/xml'
when 'production' then 'https://www.ups.com/ups.app/xml'
end : 'https://www.ups.com/ups.app/xml'
class << self
# An Array of Regexp that matches valid UPS package IDs
# @return [Array, Regexp] the regular expression
# @api private
def package_id_matchers
[ /^.Z/, /^[HK].{10}$/ ]
end
# The required parameters for tracking a UPS package
# @return [Array] the required parameters for tracking a UPS package
# @api private
def required_parameters
[:key, :user_id, :password]
end
end
# Tracks a UPS package
# @param [String] package_id the package identifier
# @return [Trackerific::Details] the tracking details
# @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
# @example Track a package
# ups = Trackerific::UPS.new key: 'api key', user_id: 'user', password: 'secret'
# details = ups.track_package("1Z12345E0291980793")
# @api public
def track_package(package_id)
super
# connect to UPS via HTTParty
http_response = self.class.post('/Track', :body => build_xml_request)
# throw any HTTP errors
http_response.error! unless http_response.code == 200
# Check the response for errors, return a Trackerific::Error, or parse
# the response from UPS and return a Trackerific::Details
case http_response['TrackResponse']['Response']['ResponseStatusCode']
when "0" then raise Trackerific::Error, parse_error_response(http_response)
when "1" then return parse_success_response(http_response)
else raise Trackerific::Error, "Invalid response code returned from server."
end
end
protected
# Parses the response from UPS
# @return [Trackerific::Details]
# @api private
def parse_success_response(http_response)
# get estimated delivery date
deliveryDateString = http_response['TrackResponse']['Shipment']['ScheduledDeliveryDate']
if deliveryDateString.present?
deliveryDateYear = deliveryDateString[0..3]
deliveryDateMonth = deliveryDateString[4..5]
deliveryDateDay = deliveryDateString[6..7]
deliveryDate = Date.new(deliveryDateYear.to_i, deliveryDateMonth.to_i, deliveryDateDay.to_i)
end
# get the activity from the UPS response
activity = http_response['TrackResponse']['Shipment']['Package']['Activity']
# if there's only one activity in the list, we need to put it in an array
activity = [activity] if activity.is_a? Hash
# UPS does not provide a summary, so we'll just use the last tracking status
summary = activity.first['Status']['StatusType']['Description'].titleize
events = []
activity.each do |a|
# the time format from UPS is HHMMSS, which cannot be directly converted
# to a Ruby time.
hours = a['Time'][0..1]
minutes = a['Time'][2..3]
seconds = a['Time'][4..5]
date = Date.parse(a['Date'])
date = DateTime.parse("#{date} #{hours}:#{minutes}:#{seconds}")
desc = a['Status']['StatusType']['Description'].titleize
code = a['Status']['StatusType']['Code']
loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
address = a['ActivityLocation']['Address']
events << Trackerific::Event.new(
:date => date,
:code => code,
:description => desc,
:location => Trackerific::Location.new(:city => address["City"], :state => address["StateProvinceCode"], :country => address["CountryCode"]),
)
end
origin = http_response['TrackResponse']['Shipment']["Shipper"]["Address"]
destination = http_response['TrackResponse']['Shipment']["ShipTo"]["Address"]
Trackerific::Details.new(
:package_id => @package_id,
:summary => summary,
:origin => Trackerific::Location.new(:address => origin["AddressLine1"], :city => origin["City"], :state => origin["StateProvinceCode"], :country => origin["CountryCode"]),
:destination => Trackerific::Location.new(:address => destination["AddressLine1"], :city => destination["City"], :state => destination["StateProvinceCode"], :country => destination["CountryCode"]),
:events => events,
:service_code => http_response["TrackResponse"]["Shipment"]["Service"]["Code"],
:service => http_response["TrackResponse"]["Shipment"]["Service"]["Description"],
:estimated_delivery_date => deliveryDate
)
end
# Parses a UPS tracking response, and returns any errors
# @return [String] the UPS tracking error
# @api private
def parse_error_response(http_response)
http_response['TrackResponse']['Response']['Error']['ErrorDescription']
end
# Builds the XML request to send to UPS for tracking a package
# @return [String] the XML request
# @api private
def build_xml_request
xml = ""
builder = ::Builder::XmlMarkup.new(:target => xml)
builder.AccessRequest do |ar|
ar.AccessLicenseNumber @options[:key]
ar.UserId @options[:user_id]
ar.Password @options[:password]
end
builder.TrackRequest do |tr|
tr.Request do |r|
r.RequestAction 'Track'
r.RequestOption 'activity'
end
tr.TrackingNumber @package_id
end
return xml
end
end
end