shippinglogic 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGELOG.rdoc +55 -0
- data/LICENSE +20 -0
- data/README.rdoc +175 -0
- data/Rakefile +37 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/shippinglogic.rb +3 -0
- data/lib/shippinglogic/attributes.rb +121 -0
- data/lib/shippinglogic/error.rb +22 -0
- data/lib/shippinglogic/fedex.rb +84 -0
- data/lib/shippinglogic/fedex/cancel.rb +47 -0
- data/lib/shippinglogic/fedex/enumerations.rb +348 -0
- data/lib/shippinglogic/fedex/error.rb +47 -0
- data/lib/shippinglogic/fedex/rate.rb +229 -0
- data/lib/shippinglogic/fedex/request.rb +134 -0
- data/lib/shippinglogic/fedex/response.rb +72 -0
- data/lib/shippinglogic/fedex/service.rb +11 -0
- data/lib/shippinglogic/fedex/ship.rb +238 -0
- data/lib/shippinglogic/fedex/signature.rb +68 -0
- data/lib/shippinglogic/fedex/track.rb +124 -0
- data/lib/shippinglogic/proxy.rb +23 -0
- data/lib/shippinglogic/service.rb +42 -0
- data/lib/shippinglogic/ups.rb +83 -0
- data/lib/shippinglogic/ups/cancel.rb +52 -0
- data/lib/shippinglogic/ups/enumerations.rb +56 -0
- data/lib/shippinglogic/ups/error.rb +42 -0
- data/lib/shippinglogic/ups/label.rb +50 -0
- data/lib/shippinglogic/ups/rate.rb +228 -0
- data/lib/shippinglogic/ups/request.rb +49 -0
- data/lib/shippinglogic/ups/response.rb +58 -0
- data/lib/shippinglogic/ups/service.rb +11 -0
- data/lib/shippinglogic/ups/ship_accept.rb +53 -0
- data/lib/shippinglogic/ups/ship_confirm.rb +170 -0
- data/lib/shippinglogic/ups/track.rb +118 -0
- data/lib/shippinglogic/validation.rb +32 -0
- data/shippinglogic.gemspec +120 -0
- data/spec/attributes_spec.rb +67 -0
- data/spec/config/fedex_credentials.example.yml +4 -0
- data/spec/config/ups_credentials.example.yml +3 -0
- data/spec/error_spec.rb +43 -0
- data/spec/fedex/cancel_spec.rb +10 -0
- data/spec/fedex/error_spec.rb +26 -0
- data/spec/fedex/rate_spec.rb +87 -0
- data/spec/fedex/request_spec.rb +15 -0
- data/spec/fedex/responses/blank.xml +0 -0
- data/spec/fedex/responses/cancel_not_found.xml +7 -0
- data/spec/fedex/responses/failed_authentication.xml +7 -0
- data/spec/fedex/responses/malformed.xml +8 -0
- data/spec/fedex/responses/rate_defaults.xml +7 -0
- data/spec/fedex/responses/rate_insurance.xml +9 -0
- data/spec/fedex/responses/rate_no_services.xml +7 -0
- data/spec/fedex/responses/rate_non_custom_packaging.xml +7 -0
- data/spec/fedex/responses/ship_defaults.xml +7 -0
- data/spec/fedex/responses/ship_with_no_signature.xml +7 -0
- data/spec/fedex/responses/signature_defaults.xml +7 -0
- data/spec/fedex/responses/track_defaults.xml +7 -0
- data/spec/fedex/responses/unexpected.xml +1 -0
- data/spec/fedex/service_spec.rb +19 -0
- data/spec/fedex/ship_spec.rb +37 -0
- data/spec/fedex/signature_spec.rb +11 -0
- data/spec/fedex/spec_helper.rb +84 -0
- data/spec/fedex/track_spec.rb +37 -0
- data/spec/fedex_spec.rb +16 -0
- data/spec/lib/interceptor.rb +17 -0
- data/spec/proxy_spec.rb +42 -0
- data/spec/service_spec.rb +23 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/ups/responses/blank.xml +0 -0
- data/spec/ups/responses/track_defaults.xml +2 -0
- data/spec/ups/spec_helper.rb +43 -0
- data/spec/ups_spec.rb +16 -0
- data/spec/validation_spec.rb +49 -0
- metadata +163 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# If FedEx responds with an error, we try our best to pull the pertinent information out of that
|
4
|
+
# response and raise it with this object. Any time FedEx says there is a problem an object of this
|
5
|
+
# class will be raised.
|
6
|
+
#
|
7
|
+
# === Tip
|
8
|
+
#
|
9
|
+
# If you want to see the raw request / respose catch the error object and call the request / response method. Ex:
|
10
|
+
#
|
11
|
+
# begin
|
12
|
+
# # my fedex code
|
13
|
+
# rescue Shippinglogic::FedEx::Error => e
|
14
|
+
# # do whatever you want here, just do:
|
15
|
+
# # e.request
|
16
|
+
# # e.response
|
17
|
+
# # to get the raw response from fedex
|
18
|
+
# end
|
19
|
+
class Error < Shippinglogic::Error
|
20
|
+
def initialize(request, response)
|
21
|
+
super
|
22
|
+
|
23
|
+
if response.blank?
|
24
|
+
add_error("The response from FedEx was blank.")
|
25
|
+
elsif !response.is_a?(Hash)
|
26
|
+
add_error("The response from FedEx was malformed and was not in a valid XML format.")
|
27
|
+
elsif notifications = response[:notifications]
|
28
|
+
notifications = notifications.is_a?(Array) ? notifications : [notifications]
|
29
|
+
notifications.delete_if { |notification| Response::SUCCESSFUL_SEVERITIES.include?(notification[:severity]) }
|
30
|
+
notifications.each { |notification| add_error(notification[:message], notification[:code]) }
|
31
|
+
elsif response[:"soapenv:fault"] && detail = response[:"soapenv:fault"][:detail][:"con:fault"]
|
32
|
+
add_error(detail[:"con:reason"], detail[:"con:error_code"])
|
33
|
+
|
34
|
+
if detail[:"con:details"] && detail[:"con:details"][:"con1:validation_failure_detail"] && messages = detail[:"con:details"][:"con1:validation_failure_detail"][:"con1:message"]
|
35
|
+
messages = messages.is_a?(Array) ? messages : [messages]
|
36
|
+
messages.each { |message| add_error(message) }
|
37
|
+
end
|
38
|
+
else
|
39
|
+
add_error(
|
40
|
+
"There was a problem with your fedex request, and we couldn't locate a specific error message. This means your response " +
|
41
|
+
"was in an unexpected format. You might try glancing at the raw response by using the 'response' method on this error object."
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,229 @@
|
|
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
|
+
# == Options
|
7
|
+
# === Shipper options
|
8
|
+
#
|
9
|
+
# * <tt>shipper_name</tt> - name of the shipper.
|
10
|
+
# * <tt>shipper_title</tt> - title of the shipper.
|
11
|
+
# * <tt>shipper_company_name</tt> - company name of the shipper.
|
12
|
+
# * <tt>shipper_phone_number</tt> - phone number of the shipper.
|
13
|
+
# * <tt>shipper_email</tt> - email of the shipper.
|
14
|
+
# * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
15
|
+
# * <tt>shipper_city</tt> - city part of the address.
|
16
|
+
# * <tt>shipper_state_</tt> - state part of the address, use state abreviations.
|
17
|
+
# * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
18
|
+
# * <tt>shipper_country</tt> - country code part of the address. FedEx expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
|
19
|
+
# * <tt>shipper_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
20
|
+
#
|
21
|
+
# === Recipient options
|
22
|
+
#
|
23
|
+
# * <tt>recipient_name</tt> - name of the recipient.
|
24
|
+
# * <tt>recipient_title</tt> - title of the recipient.
|
25
|
+
# * <tt>recipient_company_name</tt> - company name of the recipient.
|
26
|
+
# * <tt>recipient_phone_number</tt> - phone number of the recipient.
|
27
|
+
# * <tt>recipient_email</tt> - email of the recipient.
|
28
|
+
# * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
29
|
+
# * <tt>recipient_city</tt> - city part of the address.
|
30
|
+
# * <tt>recipient_state</tt> - state part of the address, use state abreviations.
|
31
|
+
# * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
32
|
+
# * <tt>recipient_country</tt> - country code part of the address. FedEx expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
|
33
|
+
# * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
34
|
+
#
|
35
|
+
# === Packaging options
|
36
|
+
#
|
37
|
+
# One thing to note is that FedEx does support multiple package shipments. The problem is that all of the packages must be identical.
|
38
|
+
# FedEx specifically notes in their documentation that mutiple package specifications are not allowed. So your only option for a
|
39
|
+
# multi package shipment is to increase the package_count option and keep the dimensions and weight the same for all packages. Then again,
|
40
|
+
# the documentation for the FedEx web services is terrible, so I could be wrong. Any tests I tried resulted in an error though.
|
41
|
+
#
|
42
|
+
# * <tt>packaging_type</tt> - one of Enumerations::PACKAGE_TYPES. (default: YOUR_PACKAGING)
|
43
|
+
# * <tt>package_count</tt> - the number of packages in your shipment. (default: 1)
|
44
|
+
# * <tt>package_weight</tt> - a single packages weight.
|
45
|
+
# * <tt>package_weight_units</tt> - either LB or KG. (default: LB)
|
46
|
+
# * <tt>package_length</tt> - a single packages length, only required if using YOUR_PACKAGING for packaging_type.
|
47
|
+
# * <tt>package_width</tt> - a single packages width, only required if using YOUR_PACKAGING for packaging_type.
|
48
|
+
# * <tt>package_height</tt> - a single packages height, only required if using YOUR_PACKAGING for packaging_type.
|
49
|
+
# * <tt>package_dimension_units</tt> - either IN or CM. (default: IN)
|
50
|
+
#
|
51
|
+
# === Monetary options
|
52
|
+
#
|
53
|
+
# * <tt>currency_type</tt> - the type of currency. (default: nil, because FedEx will default to your account preferences)
|
54
|
+
# * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil)
|
55
|
+
# * <tt>payment_type</tt> - one of Enumerations::PAYMENT_TYPES. (default: SENDER)
|
56
|
+
# * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then
|
57
|
+
# you can specify that here. (default: your account number)
|
58
|
+
# * <tt>payor_country</tt> - the country code for the account number. (default: US)
|
59
|
+
#
|
60
|
+
# === Delivery options
|
61
|
+
#
|
62
|
+
# * <tt>ship_time</tt> - a Time object representing when you want to ship the package. (default: Time.now)
|
63
|
+
# * <tt>service_type</tt> - one of Enumerations::SERVICE_TYPES, this is optional, leave this blank if you want a list of all
|
64
|
+
# available services. (default: nil)
|
65
|
+
# * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
|
66
|
+
# * <tt>dropoff_type</tt> - one of Enumerations::DROP_OFF_TYPES. (default: REGULAR_PICKUP)
|
67
|
+
# * <tt>special_services_requested</tt> - any exceptions or special services FedEx needs to be aware of, this should be
|
68
|
+
# one or more of Enumerations::SPECIAL_SERVICES. (default: nil)
|
69
|
+
#
|
70
|
+
# === Misc options
|
71
|
+
#
|
72
|
+
# * <tt>rate_request_types</tt> - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT)
|
73
|
+
# * <tt>include_transit_times</tt> - whether or not to include estimated transit times. (default: true)
|
74
|
+
#
|
75
|
+
# == Simple Example
|
76
|
+
#
|
77
|
+
# Here is a very simple example. Mix and match the options above to get more accurate rates:
|
78
|
+
#
|
79
|
+
# fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
80
|
+
# rates = fedex.rate(
|
81
|
+
# :shipper_postal_code => "10007",
|
82
|
+
# :shipper_country => "US",
|
83
|
+
# :recipient_postal_code => "75201",
|
84
|
+
# :recipient_country => "US",
|
85
|
+
# :package_weight => 24,
|
86
|
+
# :package_length => 12,
|
87
|
+
# :package_width => 12,
|
88
|
+
# :package_height => 12
|
89
|
+
# )
|
90
|
+
#
|
91
|
+
# rates.first
|
92
|
+
# #<Shippinglogic::FedEx::Rates::Rate @currency="USD", @name="First Overnight", @cost=#<BigDecimal:19ea290,'0.7001E2',8(8)>,
|
93
|
+
# @deadline=Fri Aug 07 08:00:00 -0400 2009, @type="FIRST_OVERNIGHT", @saturday=false>
|
94
|
+
#
|
95
|
+
# # to show accessor methods
|
96
|
+
# rates.first.name
|
97
|
+
# # => "First Overnight"
|
98
|
+
class Rate < Service
|
99
|
+
# Each rate result is an object of this class
|
100
|
+
class Service; attr_accessor :name, :type, :saturday, :delivered_by, :speed, :rate, :currency; end
|
101
|
+
|
102
|
+
VERSION = {:major => 6, :intermediate => 0, :minor => 0}
|
103
|
+
|
104
|
+
# shipper options
|
105
|
+
attribute :shipper_name, :string
|
106
|
+
attribute :shipper_title, :string
|
107
|
+
attribute :shipper_company_name, :string
|
108
|
+
attribute :shipper_phone_number, :string
|
109
|
+
attribute :shipper_email, :string
|
110
|
+
attribute :shipper_streets, :string
|
111
|
+
attribute :shipper_city, :string
|
112
|
+
attribute :shipper_state, :string
|
113
|
+
attribute :shipper_postal_code, :string
|
114
|
+
attribute :shipper_country, :string, :modifier => :country_code
|
115
|
+
attribute :shipper_residential, :boolean, :default => false
|
116
|
+
|
117
|
+
# recipient options
|
118
|
+
attribute :recipient_name, :string
|
119
|
+
attribute :recipient_title, :string
|
120
|
+
attribute :recipient_company_name, :string
|
121
|
+
attribute :recipient_phone_number, :string
|
122
|
+
attribute :recipient_email, :string
|
123
|
+
attribute :recipient_streets, :string
|
124
|
+
attribute :recipient_city, :string
|
125
|
+
attribute :recipient_state, :string
|
126
|
+
attribute :recipient_postal_code, :string
|
127
|
+
attribute :recipient_country, :string, :modifier => :country_code
|
128
|
+
attribute :recipient_residential, :boolean, :default => false
|
129
|
+
|
130
|
+
# packaging options
|
131
|
+
attribute :packaging_type, :string, :default => "YOUR_PACKAGING"
|
132
|
+
attribute :package_count, :integer, :default => 1
|
133
|
+
attribute :package_weight, :float
|
134
|
+
attribute :package_weight_units, :string, :default => "LB"
|
135
|
+
attribute :package_length, :integer
|
136
|
+
attribute :package_width, :integer
|
137
|
+
attribute :package_height, :integer
|
138
|
+
attribute :package_dimension_units, :string, :default => "IN"
|
139
|
+
|
140
|
+
# monetary options
|
141
|
+
attribute :currency_type, :string
|
142
|
+
attribute :insured_value, :decimal
|
143
|
+
attribute :payment_type, :string, :default => "SENDER"
|
144
|
+
attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.account }
|
145
|
+
attribute :payor_country, :string
|
146
|
+
|
147
|
+
# delivery options
|
148
|
+
attribute :ship_time, :datetime, :default => lambda { |rate| Time.now }
|
149
|
+
attribute :service_type, :string
|
150
|
+
attribute :delivery_deadline, :datetime
|
151
|
+
attribute :dropoff_type, :string, :default => "REGULAR_PICKUP"
|
152
|
+
attribute :special_services_requested, :array
|
153
|
+
|
154
|
+
# misc options
|
155
|
+
attribute :rate_request_types, :array, :default => ["ACCOUNT"]
|
156
|
+
attribute :include_transit_times, :boolean, :default => true
|
157
|
+
|
158
|
+
private
|
159
|
+
def target
|
160
|
+
@target ||= parse_response(request(build_request))
|
161
|
+
end
|
162
|
+
|
163
|
+
def build_request
|
164
|
+
b = builder
|
165
|
+
xml = b.RateRequest(:xmlns => "http://fedex.com/ws/rate/v#{VERSION[:major]}") do
|
166
|
+
build_authentication(b)
|
167
|
+
build_version(b, "crs", VERSION[:major], VERSION[:intermediate], VERSION[:minor])
|
168
|
+
b.ReturnTransitAndCommit include_transit_times
|
169
|
+
b.SpecialServicesRequested special_services_requested.join(",") if special_services_requested.any?
|
170
|
+
|
171
|
+
b.RequestedShipment do
|
172
|
+
b.ShipTimestamp ship_time.xmlschema if ship_time
|
173
|
+
b.ServiceType service_type if service_type
|
174
|
+
b.DropoffType dropoff_type if dropoff_type
|
175
|
+
b.PackagingType packaging_type if packaging_type
|
176
|
+
build_insured_value(b)
|
177
|
+
b.Shipper { build_address(b, :shipper) }
|
178
|
+
b.Recipient { build_address(b, :recipient) }
|
179
|
+
b.ShippingChargesPayment do
|
180
|
+
b.PaymentType payment_type if payment_type
|
181
|
+
b.Payor do
|
182
|
+
b.AccountNumber payor_account_number if payor_account_number
|
183
|
+
b.CountryCode payor_country if payor_country
|
184
|
+
end
|
185
|
+
end
|
186
|
+
b.RateRequestTypes rate_request_types.join(",") if rate_request_types
|
187
|
+
build_package(b)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def parse_response(response)
|
193
|
+
return [] if !response[:rate_reply_details]
|
194
|
+
|
195
|
+
response[:rate_reply_details].collect do |details|
|
196
|
+
shipment_detail = details[:rated_shipment_details].is_a?(Array) ? details[:rated_shipment_details].first : details[:rated_shipment_details]
|
197
|
+
cost = shipment_detail[:shipment_rate_detail][:total_net_charge]
|
198
|
+
|
199
|
+
delivered_by = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp])
|
200
|
+
speed = case details[:service_type]
|
201
|
+
when /overnight/i
|
202
|
+
86400 # 1.day
|
203
|
+
when /2_day/i
|
204
|
+
172800 # 2.days
|
205
|
+
else
|
206
|
+
259200 # 3.days
|
207
|
+
end
|
208
|
+
|
209
|
+
if meets_deadline?(delivered_by)
|
210
|
+
service = Service.new
|
211
|
+
service.name = details[:service_type].gsub("_", " ").gsub(/\b(\w)(\w*)/){ $1 + $2.downcase }
|
212
|
+
service.type = details[:service_type]
|
213
|
+
service.saturday = details[:applied_options] == "SATURDAY_DELIVERY"
|
214
|
+
service.delivered_by = delivered_by
|
215
|
+
service.speed = speed
|
216
|
+
service.rate = BigDecimal.new(cost[:amount])
|
217
|
+
service.currency = cost[:currency]
|
218
|
+
service
|
219
|
+
end
|
220
|
+
end.compact
|
221
|
+
end
|
222
|
+
|
223
|
+
def meets_deadline?(delivered_by)
|
224
|
+
return true if !delivery_deadline
|
225
|
+
delivered_by && delivered_by <= delivery_deadline
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "builder"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class FedEx
|
5
|
+
# Methods relating to building and sending a request to FedEx's web services.
|
6
|
+
module Request
|
7
|
+
private
|
8
|
+
# Convenience method for sending requests to FedEx
|
9
|
+
def request(body)
|
10
|
+
real_class.post(base.url, :body => body)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Convenience method to create a builder object so that our builder options are consistent across
|
14
|
+
# the various services.
|
15
|
+
#
|
16
|
+
# Ex: if I want to change the indent level to 3 it should change for all requests built.
|
17
|
+
def builder
|
18
|
+
b = Builder::XmlMarkup.new(:indent => 2)
|
19
|
+
b.instruct!
|
20
|
+
b
|
21
|
+
end
|
22
|
+
|
23
|
+
# A convenience method for building the authentication block in your XML request
|
24
|
+
def build_authentication(b)
|
25
|
+
b.WebAuthenticationDetail do
|
26
|
+
b.UserCredential do
|
27
|
+
b.Key base.key
|
28
|
+
b.Password base.password
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
b.ClientDetail do
|
33
|
+
b.AccountNumber base.account
|
34
|
+
b.MeterNumber base.meter
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# A convenience method for building the version block in your XML request
|
39
|
+
def build_version(b, service, major, intermediate, minor)
|
40
|
+
b.Version do
|
41
|
+
b.ServiceId service
|
42
|
+
b.Major major
|
43
|
+
b.Intermediate intermediate
|
44
|
+
b.Minor minor
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# A convenience method for building the contact block in your XML request
|
49
|
+
def build_contact(b, type)
|
50
|
+
b.Contact do
|
51
|
+
b.PersonName send("#{type}_name") if send("#{type}_name")
|
52
|
+
b.Title send("#{type}_title") if send("#{type}_title")
|
53
|
+
b.CompanyName send("#{type}_company_name") if send("#{type}_company_name")
|
54
|
+
b.PhoneNumber send("#{type}_phone_number") if send("#{type}_phone_number")
|
55
|
+
b.EMailAddress send("#{type}_email") if send("#{type}_email")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# A convenience method for building the address block in your XML request
|
60
|
+
def build_address(b, type)
|
61
|
+
b.Address do
|
62
|
+
b.StreetLines send("#{type}_streets") if send("#{type}_streets")
|
63
|
+
b.City send("#{type}_city") if send("#{type}_city")
|
64
|
+
b.StateOrProvinceCode state_code(send("#{type}_state")) if send("#{type}_state")
|
65
|
+
b.PostalCode send("#{type}_postal_code") if send("#{type}_postal_code")
|
66
|
+
b.CountryCode country_code(send("#{type}_country")) if send("#{type}_country")
|
67
|
+
b.Residential send("#{type}_residential")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_insured_value(b)
|
72
|
+
if insured_value
|
73
|
+
b.TotalInsuredValue do
|
74
|
+
b.Currency currency_type
|
75
|
+
b.Amount insured_value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# A convenience method for building the package block in your XML request
|
81
|
+
def build_package(b)
|
82
|
+
b.PackageCount package_count
|
83
|
+
|
84
|
+
b.RequestedPackages do
|
85
|
+
b.SequenceNumber 1
|
86
|
+
|
87
|
+
b.Weight do
|
88
|
+
b.Units package_weight_units
|
89
|
+
b.Value package_weight
|
90
|
+
end
|
91
|
+
|
92
|
+
if custom_packaging?
|
93
|
+
b.Dimensions do
|
94
|
+
b.Length package_length
|
95
|
+
b.Width package_width
|
96
|
+
b.Height package_height
|
97
|
+
b.Units package_dimension_units
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if respond_to?(:signature) && signature
|
102
|
+
self.special_services_requested << "SIGNATURE_OPTION"
|
103
|
+
end
|
104
|
+
|
105
|
+
if (respond_to?(:special_services_requested) && special_services_requested.any?)
|
106
|
+
b.SpecialServicesRequested do
|
107
|
+
if special_services_requested.any?
|
108
|
+
b.SpecialServiceTypes special_services_requested.join(",")
|
109
|
+
end
|
110
|
+
|
111
|
+
if signature
|
112
|
+
b.SignatureOptionDetail do
|
113
|
+
b.OptionType signature
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def custom_packaging?
|
122
|
+
packaging_type == "YOUR_PACKAGING"
|
123
|
+
end
|
124
|
+
|
125
|
+
def country_code(value)
|
126
|
+
Enumerations::FEDEX_COUNTRY_CODES[value.to_s] || Enumerations::RAILS_COUNTRY_CODES[value.to_s] || value.to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
def state_code(value)
|
130
|
+
Enumerations::STATE_CODES[value.to_s] || value.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "shippinglogic/fedex/error"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class FedEx
|
5
|
+
# Methods relating to receiving a response from FedEx and cleaning it up.
|
6
|
+
module Response
|
7
|
+
SUCCESSFUL_SEVERITIES = ["SUCCESS", "NOTE", "WARNING"]
|
8
|
+
|
9
|
+
private
|
10
|
+
# Overwriting the request method to clean the response and handle errors.
|
11
|
+
def request(body)
|
12
|
+
response = clean_response(super)
|
13
|
+
|
14
|
+
if success?(response)
|
15
|
+
response
|
16
|
+
else
|
17
|
+
raise Error.new(body, response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Was the response a success?
|
22
|
+
def success?(response)
|
23
|
+
response.is_a?(Hash) && SUCCESSFUL_SEVERITIES.include?(response[:highest_severity])
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cleans the response and returns it in a more 'user friendly' format that is easier
|
27
|
+
# to work with.
|
28
|
+
def clean_response(response)
|
29
|
+
cut_to_the_chase(sanitize_response_keys(response))
|
30
|
+
end
|
31
|
+
|
32
|
+
# FedEx likes nested XML tags, because they send quite a bit of them back in responses.
|
33
|
+
# This method just 'cuts to the chase' and get to the heart of the response.
|
34
|
+
def cut_to_the_chase(response)
|
35
|
+
if response.is_a?(Hash) && response.keys.first && response.keys.first.to_s =~ /_reply(_details)?$/
|
36
|
+
response.values.first
|
37
|
+
else
|
38
|
+
response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Recursively sanitizes the response object by clenaing up any hash keys.
|
43
|
+
def sanitize_response_keys(response)
|
44
|
+
if response.is_a?(Hash)
|
45
|
+
response.inject({}) do |r, (key, value)|
|
46
|
+
r[sanitize_response_key(key)] = sanitize_response_keys(value)
|
47
|
+
r
|
48
|
+
end
|
49
|
+
elsif response.is_a?(Array)
|
50
|
+
response.collect { |r| sanitize_response_keys(r) }
|
51
|
+
else
|
52
|
+
response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# FedEx returns a SOAP response. I just want the plain response without all of the SOAP BS.
|
57
|
+
# It basically turns this:
|
58
|
+
#
|
59
|
+
# {"v3:ServiceInfo" => ...}
|
60
|
+
#
|
61
|
+
# into:
|
62
|
+
#
|
63
|
+
# {:service_info => ...}
|
64
|
+
#
|
65
|
+
# I also did not want to use the underscore method provided by ActiveSupport because I am trying
|
66
|
+
# to avoid using that as a dependency.
|
67
|
+
def sanitize_response_key(key)
|
68
|
+
key.to_s.sub(/^(v[0-9]|ns):/, "").gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase.to_sym
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|