binarylogic-shippinglogic 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +128 -0
- data/Rakefile +45 -0
- data/VERSION.yml +4 -0
- data/lib/shippinglogic.rb +3 -0
- data/lib/shippinglogic/fedex.rb +64 -0
- data/lib/shippinglogic/fedex/attributes.rb +118 -0
- data/lib/shippinglogic/fedex/error.rb +47 -0
- data/lib/shippinglogic/fedex/rates.rb +199 -0
- data/lib/shippinglogic/fedex/request.rb +47 -0
- data/lib/shippinglogic/fedex/response.rb +68 -0
- data/lib/shippinglogic/fedex/service.rb +40 -0
- data/lib/shippinglogic/fedex/track.rb +66 -0
- data/lib/shippinglogic/fedex/validation.rb +32 -0
- data/spec/fedex/attributes_spec.rb +41 -0
- data/spec/fedex/error_spec.rb +26 -0
- data/spec/fedex/rates_spec.rb +22 -0
- data/spec/fedex/service_spec.rb +14 -0
- data/spec/fedex/track_spec.rb +20 -0
- data/spec/fedex/validation_spec.rb +10 -0
- data/spec/fedex_credentials.example.yaml +4 -0
- data/spec/fedex_spec_no.rb +11 -0
- data/spec/lib/interceptor.rb +13 -0
- data/spec/responses/basic_rate.xml +7 -0
- data/spec/responses/basic_track.xml +7 -0
- data/spec/responses/blank.xml +0 -0
- data/spec/responses/failed_authentication.xml +7 -0
- data/spec/responses/malformed.xml +8 -0
- data/spec/responses/unexpected.xml +1 -0
- data/spec/spec_helper.rb +84 -0
- metadata +113 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# An interface to the rate services provided by FedEx. Allows you to get an array of rates from fedex for a shipment,
|
4
|
+
# or a single rate for a specific service.
|
5
|
+
#
|
6
|
+
# == Accessor methods / options
|
7
|
+
#
|
8
|
+
# * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
9
|
+
# * <tt>shipper_city</tt> - city part of the address.
|
10
|
+
# * <tt>shipper_state_</tt> - state part of the address, use state abreviations.
|
11
|
+
# * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
12
|
+
# * <tt>shipper_country</tt> - country code part of the address, use abbreviations, ex: 'US'
|
13
|
+
# * <tt>shipper_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
14
|
+
# * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
15
|
+
# * <tt>recipient_city</tt> - city part of the address.
|
16
|
+
# * <tt>recipient_state</tt> - state part of the address, use state abreviations.
|
17
|
+
# * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
18
|
+
# * <tt>recipient_country</tt> - country code part of the address, use abbreviations, ex: 'US'
|
19
|
+
# * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
20
|
+
# * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all
|
21
|
+
# available services. (default: nil)
|
22
|
+
# * <tt>packaging_type</tt> - one of PACKAGE_TYPES. (default: YOUR_PACKAGING)
|
23
|
+
# * <tt>packages</tt> - an array of packages included in the shipment. This should be an array of hashes with the following keys:
|
24
|
+
# * <tt>:weight</tt> - the weight
|
25
|
+
# * <tt>:weight_units</tt> - either LB or KG. (default: LB)
|
26
|
+
# * <tt>:length</tt> - the length.
|
27
|
+
# * <tt>:width</tt> - the width.
|
28
|
+
# * <tt>:height</tt> - the height.
|
29
|
+
# * <tt>:dimension_units</tt> - either IN or CM. (default: IN)
|
30
|
+
# * <tt>ship_time</tt> - a Time object representing when you want to ship the package. (default: Time.now)
|
31
|
+
# * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP)
|
32
|
+
# * <tt>include_transit_times</tt> - whether or not to include estimated transit times. (default: true)
|
33
|
+
# * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
|
34
|
+
# * <tt>special_services_requested</tt> - any exceptions or special services FedEx needs to be aware of, this should be
|
35
|
+
# one or more of SPECIAL_SERVICES. (default: nil)
|
36
|
+
# * <tt>currency_type</tt> - the type of currency. (default: nil, because FedEx will default to your account preferences)
|
37
|
+
# * <tt>rate_request_types</tt> - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT)
|
38
|
+
# * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil)
|
39
|
+
# * <tt>payment_type</tt> - one of PAYMENT_TYPES. (default: SENDER)
|
40
|
+
# * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then
|
41
|
+
# you can specify that here. (default: nil)
|
42
|
+
# * <tt>payor_country</tt> - the country code for the account number. (default: US)
|
43
|
+
#
|
44
|
+
# === Simple Example
|
45
|
+
#
|
46
|
+
# Here is a very simple example. Mix and match the options above to get more accurate rates:
|
47
|
+
#
|
48
|
+
# fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
49
|
+
# fedex.rates(
|
50
|
+
# :shipper_postal_code => "10007",
|
51
|
+
# :shipper_country => "US",
|
52
|
+
# :recipient_postal_code => "75201",
|
53
|
+
# :recipient_country_code => "US"
|
54
|
+
# :packages => [{:weight => 24, :length => 12, :width => 12, :height => 12}]
|
55
|
+
# )
|
56
|
+
class Rates < Service
|
57
|
+
# Each rate result is an object of this class
|
58
|
+
class Rate; attr_accessor :name, :type, :saturday, :deadline, :cost, :currency; end
|
59
|
+
|
60
|
+
VERSION = {:major => 6, :intermediate => 0, :minor => 0}
|
61
|
+
DROP_OFF_TYPES = ["REGULAR_PICKUP", "REQUEST_COURIER", "DROP_BOX", "BUSINESS_SERVICE_CENTER", "STATION"]
|
62
|
+
PACKAGE_TYPES = ["FEDEX_ENVELOPE", "FEDEX_PAK", "FEDEX_BOX", "FEDEX_TUBE", "FEDEX_10KG_BOX", "FEDEX_25KG_BOX", "YOUR_PACKAGING"]
|
63
|
+
SPECIAL_SERVICES = [
|
64
|
+
"APPOINTMENT_DELIVERY", "DANGEROUS_GOODS", "DRY_ICE", "NON_STANDARD_CONTAINER", "PRIORITY_ALERT", "SIGNATURE_OPTION",
|
65
|
+
"FEDEX_FREIGHT", "FEDEX_NATIONAL_FREIGHT", "INSIDE_PICKUP", "INSIDE_DELIVERY", "EXHIBITION", "EXTREME_LENGTH", "FLATBED_TRAILER",
|
66
|
+
"FREIGHT_GUARANTEE", "LIFTGATE_DELIVERY", "LIFTGATE_PICKUP", "LIMITED_ACCESS_DELIVERY", "LIMITED_ACCESS_PICKUP", "PRE_DELIVERY_NOTIFICATION",
|
67
|
+
"PROTECTION_FROM_FREEZING", "REGIONAL_MALL_DELIVERY", "REGIONAL_MALL_PICKUP"
|
68
|
+
]
|
69
|
+
RATE_REQUEST_TYPES = ["ACCOUNT", "LIST", "MULTIWEIGHT"]
|
70
|
+
PAYMENT_TYPES = ["SENDER", "CASH", "CREDIT_CARD"]
|
71
|
+
SERVICE_TYPES = [
|
72
|
+
"INTERNATIONAL_FIRST", "FEDEX_3_DAY_FREIGHT", "STANDARD_OVERNIGHT", "PRIORITY_OVERNIGHT", "FEDEX_GROUND", "INTERNATIONAL_PRIORITY",
|
73
|
+
"FIRST_OVERNIGHT", "FEDEX_1_DAY_FREIGHT", "FEDEX_2_DAY_FREIGHT", "INTERNATIONAL_GROUND", "INTERNATIONAL_ECONOMY_FREIGHT", "INTERNATIONAL_ECONOMY",
|
74
|
+
"FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY", "INTERNATIONAL_PRIORITY_FREIGHT", "FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY", "FEDEX_2_DAY_SATURDAY_DELIVERY",
|
75
|
+
"FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY", "FEDEX_2_DAY", "PRIORITY_OVERNIGHT_SATURDAY_DELIVERY", "INTERNATIONAL_PRIORITY_SATURDAY_DELIVERY",
|
76
|
+
"FEDEX_EXPRESS_SAVER", "GROUND_HOME_DELIVERY"
|
77
|
+
]
|
78
|
+
|
79
|
+
attribute :shipper_streets, :string
|
80
|
+
attribute :shipper_city, :string
|
81
|
+
attribute :shipper_state, :string
|
82
|
+
attribute :shipper_postal_code, :string
|
83
|
+
attribute :shipper_country, :string
|
84
|
+
attribute :shipper_residential, :boolean, :default => false
|
85
|
+
|
86
|
+
attribute :recipient_streets, :string
|
87
|
+
attribute :recipient_city, :string
|
88
|
+
attribute :recipient_state, :string
|
89
|
+
attribute :recipient_postal_code, :string
|
90
|
+
attribute :recipient_country, :string
|
91
|
+
attribute :recipient_residential, :boolean, :default => false
|
92
|
+
|
93
|
+
attribute :service_type, :string
|
94
|
+
attribute :packaging_type, :string, :default => "YOUR_PACKAGING"
|
95
|
+
attribute :packages, :array
|
96
|
+
attribute :ship_time, :datetime, :default => lambda { Time.now }
|
97
|
+
attribute :dropoff_type, :boolean, :default => "REGULAR_PICKUP"
|
98
|
+
attribute :include_transit_times, :boolean, :default => true
|
99
|
+
attribute :delivery_deadline, :datetime
|
100
|
+
attribute :special_services_requested, :array
|
101
|
+
attribute :currency_type, :string
|
102
|
+
attribute :rate_request_types, :array, :default => ["ACCOUNT"]
|
103
|
+
attribute :insured_value, :big_decimal
|
104
|
+
attribute :payment_type, :string, :default => "SENDER"
|
105
|
+
attribute :payor_account_number, :string
|
106
|
+
attribute :payor_country, :string, :default => "US"
|
107
|
+
|
108
|
+
private
|
109
|
+
def target
|
110
|
+
@target ||= parse_rate_response(request(build_rate_request))
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_rate_request
|
114
|
+
b = builder
|
115
|
+
xml = b.RateRequest(:xmlns => "http://fedex.com/ws/rate/v#{VERSION[:major]}") do
|
116
|
+
build_authentication(b)
|
117
|
+
build_version(b, "crs", VERSION[:major], VERSION[:intermediate], VERSION[:minor])
|
118
|
+
b.ReturnTransitAndCommit include_transit_times
|
119
|
+
b.SpecialServicesRequested special_services_requested.join(",") if special_services_requested.any?
|
120
|
+
|
121
|
+
b.RequestedShipment do
|
122
|
+
b.ShipTimestamp ship_time.xmlschema
|
123
|
+
b.ServiceType service_type if service_type
|
124
|
+
b.DropoffType dropoff_type if dropoff_type
|
125
|
+
b.PackagingType packaging_type if packaging_type
|
126
|
+
b.TotalInsuredValue insured_value if insured_value
|
127
|
+
b.Shipper { build_address(b, :shipper) }
|
128
|
+
b.Recipient { build_address(b, :recipient) }
|
129
|
+
b.ShippingChargesPayment do
|
130
|
+
b.PaymentType payment_type
|
131
|
+
if payor_account_number && payor_country_code
|
132
|
+
b.Payor do
|
133
|
+
b.AccountNumber payor_account_number
|
134
|
+
b.CountryCode payor_country
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
b.RateRequestTypes rate_request_types.join(",")
|
139
|
+
b.PackageCount packages.size
|
140
|
+
b.PackageDetail "INDIVIDUAL_PACKAGES"
|
141
|
+
|
142
|
+
packages.each { |package| build_package(b, package) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def build_address(b, type)
|
148
|
+
b.Address do
|
149
|
+
b.StreetLines send("#{type}_streets") if send("#{type}_streets")
|
150
|
+
b.City send("#{type}_city") if send("#{type}_city")
|
151
|
+
b.StateOrProvinceCode send("#{type}_state") if send("#{type}_state")
|
152
|
+
b.PostalCode send("#{type}_postal_code") if send("#{type}_postal_code")
|
153
|
+
b.CountryCode send("#{type}_country") if send("#{type}_country")
|
154
|
+
b.Residential send("#{type}_residential")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def build_package(b, package)
|
159
|
+
package.symbolize_keys! if package.respond_to?(:symbolize_keys!)
|
160
|
+
validate_package(package)
|
161
|
+
|
162
|
+
b.RequestedPackages do
|
163
|
+
b.Weight do
|
164
|
+
b.Units package[:weight_units] || "LB"
|
165
|
+
b.Value package[:weight]
|
166
|
+
end
|
167
|
+
|
168
|
+
b.Dimensions do
|
169
|
+
b.Length package[:length]
|
170
|
+
b.Width package[:width]
|
171
|
+
b.Height package[:height]
|
172
|
+
b.Units package[:dimension_units] || "IN"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def validate_package(package)
|
178
|
+
raise ArgumentError.new("Each package much be in a Hash format") if !package.is_a?(Hash)
|
179
|
+
package.assert_valid_keys(:weight, :weight_units, :length, :height, :width, :dimension_units)
|
180
|
+
end
|
181
|
+
|
182
|
+
def parse_rate_response(response)
|
183
|
+
response[:rate_reply_details].collect do |details|
|
184
|
+
shipment_detail = details[:rated_shipment_details].is_a?(Array) ? details[:rated_shipment_details].first : details[:rated_shipment_details]
|
185
|
+
cost = shipment_detail[:shipment_rate_detail][:total_net_charge]
|
186
|
+
|
187
|
+
rate = Rate.new
|
188
|
+
rate.name = details[:service_type].titleize
|
189
|
+
rate.type = details[:service_type]
|
190
|
+
rate.saturday = details[:applied_options] == "SATURDAY_DELIVERY"
|
191
|
+
rate.deadline = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp])
|
192
|
+
rate.cost = BigDecimal.new(cost[:amount])
|
193
|
+
rate.currency = cost[:currency]
|
194
|
+
rate
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# Methods relating to building and sending a request to FedEx's web services.
|
4
|
+
module Request
|
5
|
+
private
|
6
|
+
# Convenience method for sending requests to FedEx
|
7
|
+
def request(body)
|
8
|
+
self.class.post(base.options[:test] ? base.options[:test_url] : base.options[:production_url], :body => body)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Convenience method to create a builder object so that our builder options are consistent across
|
12
|
+
# the various services.
|
13
|
+
#
|
14
|
+
# Ex: if I want to change the indent level to 3 it should change for all requests built.
|
15
|
+
def builder
|
16
|
+
b = Builder::XmlMarkup.new(:indent => 2)
|
17
|
+
b.instruct!
|
18
|
+
b
|
19
|
+
end
|
20
|
+
|
21
|
+
# A convenience method for building the authentication block in your XML request
|
22
|
+
def build_authentication(b)
|
23
|
+
b.WebAuthenticationDetail do
|
24
|
+
b.UserCredential do
|
25
|
+
b.Key base.key
|
26
|
+
b.Password base.password
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
b.ClientDetail do
|
31
|
+
b.AccountNumber base.account
|
32
|
+
b.MeterNumber base.meter
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# A convenience method for building the version block in your XML request
|
37
|
+
def build_version(b, service, major, intermediate, minor)
|
38
|
+
b.Version do
|
39
|
+
b.ServiceId service
|
40
|
+
b.Major major
|
41
|
+
b.Intermediate intermediate
|
42
|
+
b.Minor minor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# Methods relating to receiving a response from FedEx and cleaning it up.
|
4
|
+
module Response
|
5
|
+
private
|
6
|
+
# Overwriting the request method to clean the response and handle errors.
|
7
|
+
def request(*args)
|
8
|
+
response = clean_response(super)
|
9
|
+
|
10
|
+
if success?(response)
|
11
|
+
response
|
12
|
+
else
|
13
|
+
raise Error.new(response)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Was the response a success?
|
18
|
+
def success?(response)
|
19
|
+
response.is_a?(Hash) && ["SUCCESS", "NOTE"].include?(response[:highest_severity])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Cleans the response and returns it in a more 'user friendly' format that is easier
|
23
|
+
# to work with.
|
24
|
+
def clean_response(response)
|
25
|
+
cut_to_the_chase(sanitize_response_keys(response))
|
26
|
+
end
|
27
|
+
|
28
|
+
# FedEx likes nested XML tags, because they send quite a bit of them back in responses.
|
29
|
+
# This method just 'cuts to the chase' and get to the heart of the response.
|
30
|
+
def cut_to_the_chase(response)
|
31
|
+
if response.is_a?(Hash) && response.keys.first && response.keys.first.to_s =~ /_reply(_details)?$/
|
32
|
+
response.values.first
|
33
|
+
else
|
34
|
+
response
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Recursively sanitizes the response object by clenaing up any hash keys.
|
39
|
+
def sanitize_response_keys(response)
|
40
|
+
if response.is_a?(Hash)
|
41
|
+
response.inject({}) do |r, (key, value)|
|
42
|
+
r[sanitize_response_key(key)] = sanitize_response_keys(value)
|
43
|
+
r
|
44
|
+
end
|
45
|
+
elsif response.is_a?(Array)
|
46
|
+
response.collect { |r| sanitize_response_keys(r) }
|
47
|
+
else
|
48
|
+
response
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# FedEx returns a SOAP response. I just want the plain response without all of the SOAP BS.
|
53
|
+
# It basically turns this:
|
54
|
+
#
|
55
|
+
# {"v3:ServiceInfo" => ...}
|
56
|
+
#
|
57
|
+
# into:
|
58
|
+
#
|
59
|
+
# {:service_info => ...}
|
60
|
+
#
|
61
|
+
# I also did not want to use the underscore method provided by ActiveSupport because I am trying
|
62
|
+
# to avoid using that as a dependency.
|
63
|
+
def sanitize_response_key(key)
|
64
|
+
key.to_s.gsub(/^(v[0-9]|ns):/, "").underscore.to_sym
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "shippinglogic/fedex/attributes"
|
2
|
+
require "shippinglogic/fedex/request"
|
3
|
+
require "shippinglogic/fedex/response"
|
4
|
+
require "shippinglogic/fedex/validation"
|
5
|
+
|
6
|
+
module Shippinglogic
|
7
|
+
class FedEx
|
8
|
+
class Service
|
9
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^class$|^send$|proxy_|^object_id$)/ }
|
10
|
+
|
11
|
+
include Attributes
|
12
|
+
include HTTParty
|
13
|
+
include Request
|
14
|
+
include Response
|
15
|
+
include Validation
|
16
|
+
|
17
|
+
attr_accessor :base
|
18
|
+
|
19
|
+
# Accepts the base service object as a single parameter so that we can access
|
20
|
+
# authentication credentials and options.
|
21
|
+
def initialize(base, attributes = {})
|
22
|
+
self.base = base
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# We undefined a lot of methods at the beginning of this class. The only methods present in this
|
28
|
+
# class are ones that we need, everything else is delegated to our target object.
|
29
|
+
def method_missing(name, *args, &block)
|
30
|
+
target.send(name, *args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# For each service you need to overwrite this method. This is where you make the call to fedex
|
34
|
+
# and do your magic.
|
35
|
+
def target
|
36
|
+
raise ImplementationError.new("You need to implement a target method that the proxy class can delegate method calls to")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# An interface to the track services provided by FedEx. Allows you to get an array of events for a specific
|
4
|
+
# tracking number.
|
5
|
+
#
|
6
|
+
# == Accessor methods / options
|
7
|
+
#
|
8
|
+
# * <tt>tracking_number</tt> - the tracking number
|
9
|
+
#
|
10
|
+
# === Simple Example
|
11
|
+
#
|
12
|
+
# Here is a very simple example:
|
13
|
+
#
|
14
|
+
# fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
15
|
+
# fedex.track(:tracking_number => "my number")
|
16
|
+
#
|
17
|
+
# === Note
|
18
|
+
# FedEx does support locating packages through means other than a tracking number.
|
19
|
+
# These are not supported and probably won't be until someone needs them. It should
|
20
|
+
# be fairly simple to add, but I could not think of a reason why anyone would want to track
|
21
|
+
# a package with anything other than a tracking number.
|
22
|
+
class Track < Service
|
23
|
+
# Each tracking result is an object of this class
|
24
|
+
class Event; attr_accessor :name, :type, :occured_at, :city, :state, :postal_code, :country, :residential; end
|
25
|
+
|
26
|
+
VERSION = {:major => 3, :intermediate => 0, :minor => 0}
|
27
|
+
|
28
|
+
attribute :tracking_number, :string
|
29
|
+
|
30
|
+
private
|
31
|
+
def target
|
32
|
+
@target ||= parse_track_response(request(build_track_request))
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_track_request
|
36
|
+
b = builder
|
37
|
+
xml = b.TrackRequest(:xmlns => "http://fedex.com/ws/track/v#{VERSION[:major]}") do
|
38
|
+
build_authentication(b)
|
39
|
+
build_version(b, "trck", VERSION[:major], VERSION[:intermediate], VERSION[:minor])
|
40
|
+
|
41
|
+
b.PackageIdentifier do
|
42
|
+
b.Value tracking_number
|
43
|
+
b.Type "TRACKING_NUMBER_OR_DOORTAG"
|
44
|
+
end
|
45
|
+
|
46
|
+
b.IncludeDetailedScans true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_track_response(response)
|
51
|
+
response[:track_details][:events].collect do |details|
|
52
|
+
event = Event.new
|
53
|
+
event.name = details[:event_description]
|
54
|
+
event.type = details[:event_type]
|
55
|
+
event.occured_at = Time.parse(details[:timestamp])
|
56
|
+
event.city = details[:address][:city]
|
57
|
+
event.state = details[:address][:state_or_province_code]
|
58
|
+
event.postal_code = details[:address][:postal_code]
|
59
|
+
event.country = details[:address][:country_code]
|
60
|
+
event.residential = details[:address][:residential] == "true"
|
61
|
+
event
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# This module is more for application integration, so you can do something like:
|
4
|
+
#
|
5
|
+
# tracking = fedex.tracking
|
6
|
+
# if tracking.valid?
|
7
|
+
# # render a successful response
|
8
|
+
# else
|
9
|
+
# # do something with the errors: fedex.errors
|
10
|
+
# end
|
11
|
+
module Validation
|
12
|
+
# Just an array of errors that were encounted if valid? returns false.
|
13
|
+
def errors
|
14
|
+
@errors ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Allows you to determine if the request is valid or not. All validation is delegated to the FedEx
|
18
|
+
# services, so what this does is make a call to FedEx and rescue any errors, then it puts those
|
19
|
+
# error into the 'errors' array.
|
20
|
+
def valid?
|
21
|
+
begin
|
22
|
+
target
|
23
|
+
true
|
24
|
+
rescue Error => e
|
25
|
+
errors.clear
|
26
|
+
self.errors << e.message
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|