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.
Files changed (38) hide show
  1. data/CHANGELOG +0 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.markdown +91 -0
  4. data/lib/omniship/address.rb +135 -0
  5. data/lib/omniship/base.rb +11 -0
  6. data/lib/omniship/carrier.rb +69 -0
  7. data/lib/omniship/carriers/fedex.rb +330 -0
  8. data/lib/omniship/carriers/ups.rb +564 -0
  9. data/lib/omniship/carriers/usps.rb +438 -0
  10. data/lib/omniship/carriers.rb +13 -0
  11. data/lib/omniship/contact.rb +19 -0
  12. data/lib/omniship/package.rb +147 -0
  13. data/lib/omniship/rate_estimate.rb +59 -0
  14. data/lib/omniship/rate_response.rb +16 -0
  15. data/lib/omniship/response.rb +42 -0
  16. data/lib/omniship/shipment_event.rb +12 -0
  17. data/lib/omniship/tracking_response.rb +18 -0
  18. data/lib/omniship/version.rb +3 -0
  19. data/lib/omniship.rb +73 -0
  20. data/lib/vendor/quantified/MIT-LICENSE +22 -0
  21. data/lib/vendor/quantified/README.markdown +49 -0
  22. data/lib/vendor/quantified/Rakefile +21 -0
  23. data/lib/vendor/quantified/init.rb +0 -0
  24. data/lib/vendor/quantified/lib/quantified/attribute.rb +208 -0
  25. data/lib/vendor/quantified/lib/quantified/length.rb +20 -0
  26. data/lib/vendor/quantified/lib/quantified/mass.rb +19 -0
  27. data/lib/vendor/quantified/lib/quantified.rb +6 -0
  28. data/lib/vendor/quantified/test/length_test.rb +92 -0
  29. data/lib/vendor/quantified/test/mass_test.rb +88 -0
  30. data/lib/vendor/quantified/test/test_helper.rb +10 -0
  31. data/lib/vendor/xml_node/README +36 -0
  32. data/lib/vendor/xml_node/Rakefile +21 -0
  33. data/lib/vendor/xml_node/benchmark/bench_generation.rb +32 -0
  34. data/lib/vendor/xml_node/init.rb +1 -0
  35. data/lib/vendor/xml_node/lib/xml_node.rb +222 -0
  36. data/lib/vendor/xml_node/test/test_generating.rb +94 -0
  37. data/lib/vendor/xml_node/test/test_parsing.rb +43 -0
  38. 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