shippinglogic 1.2.3
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/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
|