binarylogic-shippinglogic 0.9.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.
- 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
|