omniship 0.1.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/CHANGELOG +0 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +91 -0
- data/lib/omniship/address.rb +135 -0
- data/lib/omniship/base.rb +11 -0
- data/lib/omniship/carrier.rb +69 -0
- data/lib/omniship/carriers/fedex.rb +330 -0
- data/lib/omniship/carriers/ups.rb +564 -0
- data/lib/omniship/carriers/usps.rb +438 -0
- data/lib/omniship/carriers.rb +13 -0
- data/lib/omniship/contact.rb +19 -0
- data/lib/omniship/package.rb +147 -0
- data/lib/omniship/rate_estimate.rb +59 -0
- data/lib/omniship/rate_response.rb +16 -0
- data/lib/omniship/response.rb +42 -0
- data/lib/omniship/shipment_event.rb +12 -0
- data/lib/omniship/tracking_response.rb +18 -0
- data/lib/omniship/version.rb +3 -0
- data/lib/omniship.rb +73 -0
- data/lib/vendor/quantified/MIT-LICENSE +22 -0
- data/lib/vendor/quantified/README.markdown +49 -0
- data/lib/vendor/quantified/Rakefile +21 -0
- data/lib/vendor/quantified/init.rb +0 -0
- data/lib/vendor/quantified/lib/quantified/attribute.rb +208 -0
- data/lib/vendor/quantified/lib/quantified/length.rb +20 -0
- data/lib/vendor/quantified/lib/quantified/mass.rb +19 -0
- data/lib/vendor/quantified/lib/quantified.rb +6 -0
- data/lib/vendor/quantified/test/length_test.rb +92 -0
- data/lib/vendor/quantified/test/mass_test.rb +88 -0
- data/lib/vendor/quantified/test/test_helper.rb +10 -0
- data/lib/vendor/xml_node/README +36 -0
- data/lib/vendor/xml_node/Rakefile +21 -0
- data/lib/vendor/xml_node/benchmark/bench_generation.rb +32 -0
- data/lib/vendor/xml_node/init.rb +1 -0
- data/lib/vendor/xml_node/lib/xml_node.rb +222 -0
- data/lib/vendor/xml_node/test/test_generating.rb +94 -0
- data/lib/vendor/xml_node/test/test_parsing.rb +43 -0
- metadata +205 -0
@@ -0,0 +1,564 @@
|
|
1
|
+
module Omniship
|
2
|
+
class UPS < Carrier
|
3
|
+
self.retry_safe = true
|
4
|
+
|
5
|
+
cattr_accessor :default_options
|
6
|
+
cattr_reader :name
|
7
|
+
@@name = "UPS"
|
8
|
+
|
9
|
+
TEST_URL = 'https://wwwcie.ups.com'
|
10
|
+
LIVE_URL = 'https://onlinetools.ups.com'
|
11
|
+
|
12
|
+
RESOURCES = {
|
13
|
+
:rates => 'ups.app/xml/Rate',
|
14
|
+
:track => 'ups.app/xml/Track',
|
15
|
+
:shipconfirm => 'ups.app/xml/ShipConfirm',
|
16
|
+
:shipaccept => 'ups.app/xml/ShipAccept',
|
17
|
+
:shipvoid => 'ups.app/xml/Void'
|
18
|
+
}
|
19
|
+
|
20
|
+
PICKUP_CODES = HashWithIndifferentAccess.new({
|
21
|
+
:daily_pickup => "01",
|
22
|
+
:customer_counter => "03",
|
23
|
+
:one_time_pickup => "06",
|
24
|
+
:on_call_air => "07",
|
25
|
+
:suggested_retail_rates => "11",
|
26
|
+
:letter_center => "19",
|
27
|
+
:air_service_center => "20"
|
28
|
+
})
|
29
|
+
|
30
|
+
CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new({
|
31
|
+
:wholesale => "01",
|
32
|
+
:occasional => "03",
|
33
|
+
:retail => "04"
|
34
|
+
})
|
35
|
+
|
36
|
+
# these are the defaults described in the UPS API docs,
|
37
|
+
# but they don't seem to apply them under all circumstances,
|
38
|
+
# so we need to take matters into our own hands
|
39
|
+
DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash,key|
|
40
|
+
hash[key] = case key.to_sym
|
41
|
+
when :daily_pickup then :wholesale
|
42
|
+
when :customer_counter then :retail
|
43
|
+
else
|
44
|
+
:occasional
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
DEFAULT_SERVICES = {
|
49
|
+
"01" => "UPS Next Day Air",
|
50
|
+
"02" => "UPS Second Day Air",
|
51
|
+
"03" => "UPS Ground",
|
52
|
+
"07" => "UPS Worldwide Express",
|
53
|
+
"08" => "UPS Worldwide Expedited",
|
54
|
+
"11" => "UPS Standard",
|
55
|
+
"12" => "UPS Three-Day Select",
|
56
|
+
"13" => "UPS Next Day Air Saver",
|
57
|
+
"14" => "UPS Next Day Air Early A.M.",
|
58
|
+
"54" => "UPS Worldwide Express Plus",
|
59
|
+
"59" => "UPS Second Day Air A.M.",
|
60
|
+
"65" => "UPS Saver",
|
61
|
+
"82" => "UPS Today Standard",
|
62
|
+
"83" => "UPS Today Dedicated Courier",
|
63
|
+
"84" => "UPS Today Intercity",
|
64
|
+
"85" => "UPS Today Express",
|
65
|
+
"86" => "UPS Today Express Saver"
|
66
|
+
}
|
67
|
+
|
68
|
+
CANADA_ORIGIN_SERVICES = {
|
69
|
+
"01" => "UPS Express",
|
70
|
+
"02" => "UPS Expedited",
|
71
|
+
"14" => "UPS Express Early A.M."
|
72
|
+
}
|
73
|
+
|
74
|
+
MEXICO_ORIGIN_SERVICES = {
|
75
|
+
"07" => "UPS Express",
|
76
|
+
"08" => "UPS Expedited",
|
77
|
+
"54" => "UPS Express Plus"
|
78
|
+
}
|
79
|
+
|
80
|
+
EU_ORIGIN_SERVICES = {
|
81
|
+
"07" => "UPS Express",
|
82
|
+
"08" => "UPS Expedited"
|
83
|
+
}
|
84
|
+
|
85
|
+
OTHER_NON_US_ORIGIN_SERVICES = {
|
86
|
+
"07" => "UPS Express"
|
87
|
+
}
|
88
|
+
|
89
|
+
# From http://en.wikipedia.org/w/index.php?title=European_Union&oldid=174718707 (Current as of November 30, 2007)
|
90
|
+
EU_COUNTRY_CODES = ["GB", "AT", "BE", "BG", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE"]
|
91
|
+
|
92
|
+
US_TERRITORIES_TREATED_AS_COUNTRIES = ["AS", "FM", "GU", "MH", "MP", "PW", "PR", "VI"]
|
93
|
+
|
94
|
+
def requirements
|
95
|
+
[:key, :login, :password]
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_rates(origin, destination, packages, options={})
|
99
|
+
origin, destination = upsified_location(origin), upsified_location(destination)
|
100
|
+
options = @options.merge(options)
|
101
|
+
packages = Array(packages)
|
102
|
+
access_request = build_access_request
|
103
|
+
rate_request = build_rate_request(origin, destination, packages, options)
|
104
|
+
response = commit(:rates, save_request(access_request.gsub("\n","") + rate_request.gsub("\n","")), (options[:test] || false))
|
105
|
+
parse_rate_response(origin, destination, packages, response, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_tracking_info(tracking_number, options={})
|
109
|
+
options = @options.update(options)
|
110
|
+
access_request = build_access_request
|
111
|
+
tracking_request = build_tracking_request(tracking_number, options)
|
112
|
+
response = commit(:track, save_request(access_request.gsub("\n","") + tracking_request.gsub("\n","")), (options[:test] || false))
|
113
|
+
parse_tracking_response(response, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Creating shipping functionality for UPS
|
117
|
+
def create_shipment(origin, destination, packages, options={})
|
118
|
+
origin, destination = upsified_location(origin), upsified_location(destination)
|
119
|
+
options = @options.merge(options)
|
120
|
+
packages = Array(packages)
|
121
|
+
access_request = build_access_request
|
122
|
+
ship_confirm_request = build_ship_confirm(origin, destination, packages, options)
|
123
|
+
response = commit(:shipconfirm, save_request(access_request.gsub("\n","") + ship_confirm_request.gsub("\n","")), (options[:test] || false))
|
124
|
+
parse_ship_confirm_response(origin, destination, packages, response, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
def accept_shipment(digest, options={})
|
128
|
+
access_request = build_access_request
|
129
|
+
ship_accept_request = build_ship_accept(digest)
|
130
|
+
response = commit(:shipaccept, save_request(access_request.gsub("\n","") + ship_accept_request.gsub("\n","")), (options[:test] || false))
|
131
|
+
parse_ship_accept_response(response, options)
|
132
|
+
end
|
133
|
+
|
134
|
+
def void_shipment(tracking_number, options={})
|
135
|
+
options = @options.merge(options)
|
136
|
+
access_request = build_access_request
|
137
|
+
ship_void_request = build_void_request(tracking_number)
|
138
|
+
response = commit(:shipvoid, save_request(access_request.gsub("\n","") + ship_void_request.gsub("\n","")), (options[:test] || false))
|
139
|
+
parse_ship_void_response(response, options)
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def upsified_location(location)
|
145
|
+
if location.country_code == 'US' && US_TERRITORIES_TREATED_AS_COUNTRIES.include?(location.state)
|
146
|
+
atts = {:country => location.state}
|
147
|
+
[:zip, :city, :address1, :address2, :address3, :phone, :fax, :address_type].each do |att|
|
148
|
+
atts[att] = location.send(att)
|
149
|
+
end
|
150
|
+
Address.new(atts)
|
151
|
+
else
|
152
|
+
location
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_access_request
|
157
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
158
|
+
xml.AccessRequest {
|
159
|
+
xml.AccessLicenseNumber @options[:key].nil? ? @config['ups']['key'] : @options[:key]
|
160
|
+
xml.UserId @options[:login].nil? ? @config['ups']['username'] : @options[:login]
|
161
|
+
xml.Password @options[:password].nil? ? @config['ups']['password'] : @options[:password]
|
162
|
+
|
163
|
+
|
164
|
+
xml.AccessLicenseNumber @config['ups']['key'] unless @options[:key]
|
165
|
+
xml.UserId @config['ups']['username'] unless @options[:login]
|
166
|
+
xml.Password @config['ups']['password'] unless @options[:password]
|
167
|
+
}
|
168
|
+
end
|
169
|
+
builder.to_xml
|
170
|
+
end
|
171
|
+
|
172
|
+
# Build the ship_confirm XML request
|
173
|
+
def build_ship_confirm(origin, destination, packages, options={})
|
174
|
+
packages = Array(packages)
|
175
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
176
|
+
xml.ShipmentConfirmRequest {
|
177
|
+
xml.Request {
|
178
|
+
xml.RequestAction 'ShipConfirm'
|
179
|
+
xml.RequestOption 'validate'
|
180
|
+
}
|
181
|
+
xml.Shipment {
|
182
|
+
build_location_node(['Shipper'], (options[:shipper] || origin), options, xml)
|
183
|
+
build_location_node(['ShipTo'], destination, options, xml)
|
184
|
+
if options[:shipper] && options[:shipper] != origin
|
185
|
+
build_location_node(['ShipFrom'], origin, options, xml)
|
186
|
+
end
|
187
|
+
xml.PaymentInformation {
|
188
|
+
xml.Prepaid {
|
189
|
+
xml.BillShipper {
|
190
|
+
xml.AccountNumber options[:origin_account]
|
191
|
+
}
|
192
|
+
}
|
193
|
+
}
|
194
|
+
xml.Service {
|
195
|
+
xml.Code options[:service]
|
196
|
+
}
|
197
|
+
xml.ShipmentServiceOptions {
|
198
|
+
xml.SaturdayDelivery if options[:saturday] == true
|
199
|
+
}
|
200
|
+
packages.each do |package|
|
201
|
+
imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
|
202
|
+
xml.Package {
|
203
|
+
xml.PackagingType {
|
204
|
+
xml.Code package.options[:package_type]
|
205
|
+
}
|
206
|
+
xml.Dimensions {
|
207
|
+
xml.UnitOfMeasurement {
|
208
|
+
xml.Code imperial ? 'IN' : 'CM'
|
209
|
+
}
|
210
|
+
[:length,:width,:height].each do |axis|
|
211
|
+
value = ((imperial ? package.inches(axis) : package.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
|
212
|
+
xml.send axis.to_s.gsub(/^[a-z]|\s+[-z]/) { |a| a.upcase }, [value,0.1].max
|
213
|
+
end
|
214
|
+
}
|
215
|
+
xml.PackageWeight {
|
216
|
+
xml.UnitOfMeasurement {
|
217
|
+
xml.Code imperial ? 'LBS' : 'KGS'
|
218
|
+
}
|
219
|
+
value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # decimals
|
220
|
+
xml.Weight [value,0.1].max
|
221
|
+
}
|
222
|
+
}
|
223
|
+
end
|
224
|
+
xml.LabelSpecification {
|
225
|
+
xml.LabelPrintMethod {
|
226
|
+
xml.Code 'GIF'
|
227
|
+
}
|
228
|
+
xml.LabelImageFormat {
|
229
|
+
xml.Code 'PNG'
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
end
|
235
|
+
builder.to_xml
|
236
|
+
end
|
237
|
+
|
238
|
+
def build_ship_accept(digest)
|
239
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
240
|
+
xml.ShipmentAcceptRequest {
|
241
|
+
xml.Request {
|
242
|
+
xml.RequestAction 'ShipAccept'
|
243
|
+
}
|
244
|
+
xml.ShipmentDigest digest
|
245
|
+
}
|
246
|
+
end
|
247
|
+
builder.to_xml
|
248
|
+
end
|
249
|
+
|
250
|
+
def build_void_request(tracking_number)
|
251
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
252
|
+
xml.VoidShipmentRequest {
|
253
|
+
xml.Request {
|
254
|
+
xml.RequestAction 'Void'
|
255
|
+
}
|
256
|
+
xml.ExpandedVoidShipment {
|
257
|
+
xml.ShipmentIdentificationNumber tracking_number
|
258
|
+
}
|
259
|
+
}
|
260
|
+
end
|
261
|
+
builder.to_xml
|
262
|
+
end
|
263
|
+
|
264
|
+
def build_rate_request(origin, destination, packages, options={})
|
265
|
+
packages = Array(packages)
|
266
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
267
|
+
xml.RatingServiceSelectionRequest {
|
268
|
+
xml.Request {
|
269
|
+
xml.RequestAction 'Rate'
|
270
|
+
xml.RequestOption 'Shop'
|
271
|
+
}
|
272
|
+
# not implemented: 'Rate' RequestOption to specify a single service query
|
273
|
+
# request << XmlNode.new('RequestOption', ((options[:service].nil? or options[:service] == :all) ? 'Shop' : 'Rate'))
|
274
|
+
pickup_type = options[:pickup_type] || :daily_pickup
|
275
|
+
xml.PickupType {
|
276
|
+
xml.Code PICKUP_CODES[pickup_type]
|
277
|
+
# not implemented: PickupType/PickupDetails element
|
278
|
+
}
|
279
|
+
cc = options[:customer_classification] || DEFAULT_CUSTOMER_CLASSIFICATIONS[pickup_type]
|
280
|
+
xml.CustomerClassification {
|
281
|
+
xml.Code CUSTOMER_CLASSIFICATIONS[cc]
|
282
|
+
}
|
283
|
+
xml.Shipment {
|
284
|
+
build_location_node(['Shipper'], (options[:shipper] || origin), options, xml)
|
285
|
+
build_location_node(['ShipTo'], destination, options, xml)
|
286
|
+
if options[:shipper] && options[:shipper] != origin
|
287
|
+
build_location_node(['ShipFrom'], origin, options, xml)
|
288
|
+
end
|
289
|
+
|
290
|
+
# not implemented: * Shipment/ShipmentWeight element
|
291
|
+
# * Shipment/ReferenceNumber element
|
292
|
+
# * Shipment/Service element
|
293
|
+
# * Shipment/PickupDate element
|
294
|
+
# * Shipment/ScheduledDeliveryDate element
|
295
|
+
# * Shipment/ScheduledDeliveryTime element
|
296
|
+
# * Shipment/AlternateDeliveryTime element
|
297
|
+
# * Shipment/DocumentsOnly element
|
298
|
+
|
299
|
+
packages.each do |package|
|
300
|
+
imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
|
301
|
+
xml.Package {
|
302
|
+
xml.PackagingType {
|
303
|
+
xml.Code '02'
|
304
|
+
}
|
305
|
+
xml.Dimensions {
|
306
|
+
xml.UnitOfMeasurement {
|
307
|
+
xml.Code imperial ? 'IN' : 'CM'
|
308
|
+
}
|
309
|
+
[:length,:width,:height].each do |axis|
|
310
|
+
value = ((imperial ? package.inches(axis) : package.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
|
311
|
+
xml.send axis.to_s.gsub(/^[a-z]|\s+[-z]/) { |a| a.upcase }, [value,0.1].max
|
312
|
+
end
|
313
|
+
}
|
314
|
+
xml.PackageWeight {
|
315
|
+
xml.UnitOfMeasurement {
|
316
|
+
xml.Code imperial ? 'LBS' : 'KGS'
|
317
|
+
}
|
318
|
+
value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # 3 decimals
|
319
|
+
xml.Weight [value,0.1].max
|
320
|
+
}
|
321
|
+
# not implemented: * Shipment/Package/LargePackageIndicator element
|
322
|
+
# * Shipment/Package/ReferenceNumber element
|
323
|
+
# * Shipment/Package/PackageServiceOptions element
|
324
|
+
# * Shipment/Package/AdditionalHandling element
|
325
|
+
}
|
326
|
+
end
|
327
|
+
# not implemented: * Shipment/ShipmentServiceOptions element
|
328
|
+
# * Shipment/RateInformation element
|
329
|
+
}
|
330
|
+
}
|
331
|
+
end
|
332
|
+
builder.to_xml
|
333
|
+
end
|
334
|
+
|
335
|
+
def build_tracking_request(tracking_number, options={})
|
336
|
+
bulder = Nokogiri::XML::Builder.new do |xml|
|
337
|
+
xml.TrackRequest {
|
338
|
+
xml.Request {
|
339
|
+
xml.RequestAction 'Track'
|
340
|
+
xml.RequestOption '1'
|
341
|
+
}
|
342
|
+
xml.TrackingNumber tracking_number.to_s
|
343
|
+
}
|
344
|
+
end
|
345
|
+
builder.to_xml
|
346
|
+
end
|
347
|
+
|
348
|
+
def build_location_node(name,location,options={},xml)
|
349
|
+
for name in name
|
350
|
+
xml.send(name) {
|
351
|
+
xml.Name location.name unless location.name.blank?
|
352
|
+
xml.AttentionName location.attention_name unless location.attention_name.blank?
|
353
|
+
xml.CompanyName location.company_name unless location.company_name.blank?
|
354
|
+
xml.PhoneNumber location.phone.gsub(/[^\d]/,'') unless location.phone.blank?
|
355
|
+
xml.FaxNumber location.fax.gsub(/[^\d]/,'') unless location.fax.blank?
|
356
|
+
|
357
|
+
if name =='Shipper' and (origin_account = @options[:origin_account] || options[:origin_account])
|
358
|
+
xml.ShipperNumber origin_account
|
359
|
+
elsif name == 'ShipTo' and (destination_account = @options[:destination_account] || options[:destination_account])
|
360
|
+
xml.ShipperAssignedIdentificationNumber destination_account
|
361
|
+
end
|
362
|
+
|
363
|
+
xml.Address {
|
364
|
+
xml.AddressLine1 location.address1 unless location.address1.blank?
|
365
|
+
xml.AddressLine2 location.address2 unless location.address2.blank?
|
366
|
+
xml.AddressLine3 location.address3 unless location.address3.blank?
|
367
|
+
xml.City location.city unless location.city.blank?
|
368
|
+
xml.StateProvinceCode location.province unless location.province.blank?
|
369
|
+
xml.PostalCode location.postal_code unless location.postal_code.blank?
|
370
|
+
xml.CountryCode location.country_code unless location.country_code.blank?
|
371
|
+
xml.ResidentialAddressIndicator true unless location.commercial?
|
372
|
+
}
|
373
|
+
}
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def parse_rate_response(origin, destination, packages, response, options={})
|
378
|
+
rates = []
|
379
|
+
|
380
|
+
xml = Nokogiri::XML(response)
|
381
|
+
success = response_success?(xml)
|
382
|
+
message = response_message(xml)
|
383
|
+
|
384
|
+
if success
|
385
|
+
rate_estimates = []
|
386
|
+
|
387
|
+
xml.xpath('/*/RatedShipment').each do |rated_shipment|
|
388
|
+
service_code = rated_shipment.xpath('Service/Code').text.to_s
|
389
|
+
days_to_delivery = rated_shipment.xpath('GuaranteedDaysToDelivery').text.to_s.to_i
|
390
|
+
delivery_date = days_to_delivery >= 1 ? days_to_delivery.days.from_now.strftime("%Y-%m-%d") : nil
|
391
|
+
|
392
|
+
rate_estimates << RateEstimate.new(origin, destination, @@name,
|
393
|
+
service_name_for(origin, service_code),
|
394
|
+
:total_price => rated_shipment.xpath('TotalCharges/MonetaryValue').text.to_s.to_f,
|
395
|
+
:currency => rated_shipment.xpath('TotalCharges/CurrencyCode').text.to_s,
|
396
|
+
:service_code => service_code,
|
397
|
+
:packages => packages,
|
398
|
+
:delivery_range => [delivery_date])
|
399
|
+
end
|
400
|
+
end
|
401
|
+
RateResponse.new(success, message, Hash.from_xml(response).values.first, :rates => rate_estimates, :xml => response, :request => last_request)
|
402
|
+
end
|
403
|
+
|
404
|
+
def parse_tracking_response(response, options={})
|
405
|
+
#TODO
|
406
|
+
xml = Nokogiri::XML(response)
|
407
|
+
success = response_success?(xml)
|
408
|
+
message = response_message(xml)
|
409
|
+
|
410
|
+
if success
|
411
|
+
tracking_number, origin, destination = nil
|
412
|
+
shipment_events = []
|
413
|
+
|
414
|
+
#first_shipment = xml.gelements['/*/Shipment']
|
415
|
+
#first_package = first_shipment.elements['Package']
|
416
|
+
#tracking_number = first_shipment.get_text('ShipmentIdentificationNumber | Package/TrackingNumber').to_s
|
417
|
+
|
418
|
+
#origin, destination = %w{Shipper ShipTo}.map do |location|
|
419
|
+
# location_from_address_node(first_shipment.elements["#{location}/Address"])
|
420
|
+
#end
|
421
|
+
|
422
|
+
#activities = first_package.get_elements('Activity')
|
423
|
+
#unless activities.empty?
|
424
|
+
# shipment_events = activities.map do |activity|
|
425
|
+
# description = activity.get_text('Status/StatusType/Description').to_s
|
426
|
+
# zoneless_time = if (time = activity.get_text('Time')) &&
|
427
|
+
# (date = activity.get_text('Date'))
|
428
|
+
# time, date = time.to_s, date.to_s
|
429
|
+
# hour, minute, second = time.scan(/\d{2}/)
|
430
|
+
# year, month, day = date[0..3], date[4..5], date[6..7]
|
431
|
+
# Time.utc(year, month, day, hour, minute, second)
|
432
|
+
# end
|
433
|
+
# location = location_from_address_node(activity.elements['ActivityLocation/Address'])
|
434
|
+
# ShipmentEvent.new(description, zoneless_time, location)
|
435
|
+
# end
|
436
|
+
#
|
437
|
+
# shipment_events = shipment_events.sort_by(&:time)
|
438
|
+
#
|
439
|
+
# if origin
|
440
|
+
# first_event = shipment_events[0]
|
441
|
+
# same_country = origin.country_code(:alpha2) == first_event.location.country_code(:alpha2)
|
442
|
+
# same_or_blank_city = first_event.location.city.blank? or first_event.location.city == origin.city
|
443
|
+
# origin_event = ShipmentEvent.new(first_event.name, first_event.time, origin)
|
444
|
+
# if same_country and same_or_blank_city
|
445
|
+
# shipment_events[0] = origin_event
|
446
|
+
# else
|
447
|
+
# shipment_events.unshift(origin_event)
|
448
|
+
# end
|
449
|
+
# end
|
450
|
+
# if shipment_events.last.name.downcase == 'delivered'
|
451
|
+
# shipment_events[-1] = ShipmentEvent.new(shipment_events.last.name, shipment_events.last.time, destination)
|
452
|
+
# end
|
453
|
+
#end
|
454
|
+
end
|
455
|
+
#TrackingResponse.new(success, message, Hash.from_xml(response).values.first,
|
456
|
+
# :xml => response,
|
457
|
+
# :request => last_request,
|
458
|
+
# :shipment_events => shipment_events,
|
459
|
+
# :origin => origin,
|
460
|
+
# :destination => destination,
|
461
|
+
# :tracking_number => tracking_number)
|
462
|
+
return 'Feature Not Available'
|
463
|
+
end
|
464
|
+
|
465
|
+
def parse_ship_confirm_response(origin, destination, packages, response, options={})
|
466
|
+
xml = Nokogiri::XML(response)
|
467
|
+
success = response_success?(xml)
|
468
|
+
|
469
|
+
if success
|
470
|
+
@response_text = xml.xpath('//*/ShipmentDigest').text
|
471
|
+
else
|
472
|
+
@response_text = {}
|
473
|
+
@response_text[:status] = xml.xpath('/*/Response/ResponseStatusDescription').text
|
474
|
+
@response_text[:error_severity] = xml.xpath('/*/Response/Error/ErrorSeverity').text
|
475
|
+
@response_text[:error_code] = xml.xpath('/*/Response/Error/ErrorCode').text
|
476
|
+
@response_text[:error_description] = xml.xpath('/*/Response/Error/ErrorDescription').text
|
477
|
+
end
|
478
|
+
return @response_text
|
479
|
+
end
|
480
|
+
|
481
|
+
def parse_ship_accept_response(response, options={})
|
482
|
+
xml = Nokogiri::XML(response)
|
483
|
+
success = response_success?(xml)
|
484
|
+
@response_text = {}
|
485
|
+
|
486
|
+
if success
|
487
|
+
tracking_number = []
|
488
|
+
label = []
|
489
|
+
|
490
|
+
@response_text[:charges] = xml.xpath('/*/ShipmentResults/*/TotalCharges/MonetaryValue').text
|
491
|
+
@response_text[:shipment_id] = xml.xpath('/*/ShipmentResults/ShipmentIdentificationNumber').text
|
492
|
+
|
493
|
+
xml.xpath('/*/ShipmentResults/*/TrackingNumber').each do |track|
|
494
|
+
tracking_number << track.text
|
495
|
+
end
|
496
|
+
@response_text[:tracking_number] = tracking_number
|
497
|
+
|
498
|
+
xml.xpath('/*/ShipmentResults/*/LabelImage/GraphicImage').each do |image|
|
499
|
+
label << image.text
|
500
|
+
end
|
501
|
+
@response_text[:label] = label
|
502
|
+
@response_text[:success] = true
|
503
|
+
else
|
504
|
+
@response_text[:status] = xml.xpath('/*/Response/ResponseStatusDescription').text
|
505
|
+
@response_text[:error_severity] = xml.xpath('/*/Response/Error/ErrorSeverity').text
|
506
|
+
@response_text[:error_code] = xml.xpath('/*/Response/Error/ErrorCode').text
|
507
|
+
@response_text[:error_description] = xml.xpath('/*/Response/Error/ErrorDescription').text
|
508
|
+
end
|
509
|
+
return @response_text
|
510
|
+
end
|
511
|
+
|
512
|
+
def parse_ship_void_response(response, options={})
|
513
|
+
xml = Nokogiri::XML(response)
|
514
|
+
success = response_success?(xml)
|
515
|
+
if success
|
516
|
+
@void = "Shipment successfully voided!"
|
517
|
+
else
|
518
|
+
@void = "Voiding shipment failed!"
|
519
|
+
end
|
520
|
+
|
521
|
+
return @void
|
522
|
+
end
|
523
|
+
|
524
|
+
def location_from_address_node(address)
|
525
|
+
return nil unless address
|
526
|
+
Address.new(
|
527
|
+
:country => node_text_or_nil(address.elements['CountryCode']),
|
528
|
+
:postal_code => node_text_or_nil(address.elements['PostalCode']),
|
529
|
+
:province => node_text_or_nil(address.elements['StateProvinceCode']),
|
530
|
+
:city => node_text_or_nil(address.elements['City']),
|
531
|
+
:address1 => node_text_or_nil(address.elements['AddressLine1']),
|
532
|
+
:address2 => node_text_or_nil(address.elements['AddressLine2']),
|
533
|
+
:address3 => node_text_or_nil(address.elements['AddressLine3'])
|
534
|
+
)
|
535
|
+
end
|
536
|
+
|
537
|
+
def response_success?(xml)
|
538
|
+
xml.xpath('/*/Response/ResponseStatusCode').text == '1'
|
539
|
+
end
|
540
|
+
|
541
|
+
def response_message(xml)
|
542
|
+
xml.xpath('/*/Response/Error/ErrorDescription | /*/Response/ResponseStatusDescription').text
|
543
|
+
end
|
544
|
+
|
545
|
+
def commit(action, request, test = false)
|
546
|
+
ssl_post("#{test ? TEST_URL : LIVE_URL}/#{RESOURCES[action]}", request)
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
def service_name_for(origin, code)
|
551
|
+
origin = origin.country_code(:alpha2)
|
552
|
+
|
553
|
+
name = case origin
|
554
|
+
when "CA" then CANADA_ORIGIN_SERVICES[code]
|
555
|
+
when "MX" then MEXICO_ORIGIN_SERVICES[code]
|
556
|
+
when *EU_COUNTRY_CODES then EU_ORIGIN_SERVICES[code]
|
557
|
+
end
|
558
|
+
|
559
|
+
name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
|
560
|
+
name ||= DEFAULT_SERVICES[code]
|
561
|
+
end
|
562
|
+
|
563
|
+
end
|
564
|
+
end
|