omniship 0.1.0 → 0.3.2.2

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.
@@ -1,150 +1,170 @@
1
1
  module Omniship
2
2
  class UPS < Carrier
3
3
  self.retry_safe = true
4
-
4
+
5
5
  cattr_accessor :default_options
6
6
  cattr_reader :name
7
7
  @@name = "UPS"
8
-
8
+
9
9
  TEST_URL = 'https://wwwcie.ups.com'
10
10
  LIVE_URL = 'https://onlinetools.ups.com'
11
-
11
+
12
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'
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
+ :valid_address => 'ups.app/xml/XAV'
18
19
  }
19
-
20
+
20
21
  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
-
22
+ :daily_pickup => "01",
23
+ :customer_counter => "03",
24
+ :one_time_pickup => "06",
25
+ :on_call_air => "07",
26
+ :suggested_retail_rates => "11",
27
+ :letter_center => "19",
28
+ :air_service_center => "20"
29
+ })
30
+
30
31
  CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new({
31
- :wholesale => "01",
32
- :occasional => "03",
33
- :retail => "04"
34
- })
35
-
32
+ :wholesale => "01",
33
+ :occasional => "03",
34
+ :retail => "04"
35
+ })
36
+
36
37
  # these are the defaults described in the UPS API docs,
37
38
  # but they don't seem to apply them under all circumstances,
38
39
  # so we need to take matters into our own hands
39
- DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash,key|
40
+ DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash, key|
40
41
  hash[key] = case key.to_sym
41
- when :daily_pickup then :wholesale
42
- when :customer_counter then :retail
43
- else
44
- :occasional
45
- end
42
+ when :daily_pickup then
43
+ :wholesale
44
+ when :customer_counter then
45
+ :retail
46
+ else
47
+ :occasional
48
+ end
46
49
  end
47
-
50
+
48
51
  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"
52
+ "01" => "UPS Next Day Air",
53
+ "02" => "UPS Second Day Air",
54
+ "03" => "UPS Ground",
55
+ "07" => "UPS Worldwide Express",
56
+ "08" => "UPS Worldwide Expedited",
57
+ "11" => "UPS Standard",
58
+ "12" => "UPS Three-Day Select",
59
+ "13" => "UPS Next Day Air Saver",
60
+ "14" => "UPS Next Day Air Early A.M.",
61
+ "54" => "UPS Worldwide Express Plus",
62
+ "59" => "UPS Second Day Air A.M.",
63
+ "65" => "UPS Saver",
64
+ "82" => "UPS Today Standard",
65
+ "83" => "UPS Today Dedicated Courier",
66
+ "84" => "UPS Today Intercity",
67
+ "85" => "UPS Today Express",
68
+ "86" => "UPS Today Express Saver"
66
69
  }
67
-
70
+
68
71
  CANADA_ORIGIN_SERVICES = {
69
- "01" => "UPS Express",
70
- "02" => "UPS Expedited",
71
- "14" => "UPS Express Early A.M."
72
+ "01" => "UPS Express",
73
+ "02" => "UPS Expedited",
74
+ "14" => "UPS Express Early A.M."
72
75
  }
73
-
76
+
74
77
  MEXICO_ORIGIN_SERVICES = {
75
- "07" => "UPS Express",
76
- "08" => "UPS Expedited",
77
- "54" => "UPS Express Plus"
78
+ "07" => "UPS Express",
79
+ "08" => "UPS Expedited",
80
+ "54" => "UPS Express Plus"
78
81
  }
79
-
82
+
80
83
  EU_ORIGIN_SERVICES = {
81
- "07" => "UPS Express",
82
- "08" => "UPS Expedited"
84
+ "07" => "UPS Express",
85
+ "08" => "UPS Expedited"
83
86
  }
84
-
87
+
85
88
  OTHER_NON_US_ORIGIN_SERVICES = {
86
- "07" => "UPS Express"
89
+ "07" => "UPS Express"
87
90
  }
88
-
91
+
89
92
  # From http://en.wikipedia.org/w/index.php?title=European_Union&oldid=174718707 (Current as of November 30, 2007)
90
93
  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
-
94
+
92
95
  US_TERRITORIES_TREATED_AS_COUNTRIES = ["AS", "FM", "GU", "MH", "MP", "PW", "PR", "VI"]
93
-
96
+
94
97
  def requirements
95
98
  [:key, :login, :password]
96
99
  end
97
-
100
+
98
101
  def find_rates(origin, destination, packages, options={})
99
102
  origin, destination = upsified_location(origin), upsified_location(destination)
100
103
  options = @options.merge(options)
104
+ options[:test] = options[:test].nil? ? false : options[:test]
101
105
  packages = Array(packages)
102
106
  access_request = build_access_request
103
107
  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))
108
+ response = commit(:rates, save_request(access_request.gsub("\n", "") + rate_request.gsub("\n", "")), options[:test])
105
109
  parse_rate_response(origin, destination, packages, response, options)
106
110
  end
107
-
111
+
108
112
  def find_tracking_info(tracking_number, options={})
109
113
  options = @options.update(options)
114
+ options[:test] = options[:test].nil? ? false : options[:test]
110
115
  access_request = build_access_request
111
116
  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))
117
+ response = commit(:track, save_request(access_request.gsub("\n", "") + tracking_request.gsub("\n", "")), options[:test])
113
118
  parse_tracking_response(response, options)
114
119
  end
115
120
 
116
121
  # Creating shipping functionality for UPS
117
122
  def create_shipment(origin, destination, packages, options={})
123
+ @options = @options.merge(options)
118
124
  origin, destination = upsified_location(origin), upsified_location(destination)
119
125
  options = @options.merge(options)
126
+ options[:test] = options[:test].nil? ? true : options[:test]
120
127
  packages = Array(packages)
121
128
  access_request = build_access_request
122
129
  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))
130
+ response = commit(:shipconfirm, save_request(access_request.gsub("\n", "") + ship_confirm_request.gsub("\n", "")), options[:test])
124
131
  parse_ship_confirm_response(origin, destination, packages, response, options)
125
132
  end
126
133
 
127
134
  def accept_shipment(digest, options={})
135
+ options[:test] = options[:test].nil? ? true : options[:test]
128
136
  access_request = build_access_request
129
137
  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))
138
+ response = commit(:shipaccept, save_request(access_request.gsub("\n", "") + ship_accept_request.gsub("\n", "")), options[:test])
131
139
  parse_ship_accept_response(response, options)
132
140
  end
133
141
 
134
- def void_shipment(tracking_number, options={})
142
+ def void_shipment(ups_shipment_id,tracking_number, options={})
143
+ @options = @options.merge(options)
135
144
  options = @options.merge(options)
145
+ options[:test] = options[:test].nil? ? true : options[:test]
136
146
  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))
147
+ ship_void_request = build_void_request(ups_shipment_id,tracking_number)
148
+ response = commit(:shipvoid, save_request(access_request.gsub("\n", "") + ship_void_request.gsub("\n", "")), options[:test])
139
149
  parse_ship_void_response(response, options)
140
150
  end
141
151
 
152
+ def validate_address(address,city,state,zip_code,country_code, options={})
153
+ @options = @options.merge(options)
154
+ access_request = build_access_request
155
+ validate_address_request = build_valid_address_request(address,city,state,zip_code,country_code)
156
+ options[:test] = options[:test].nil? ? true : options[:test]
157
+ response = commit(:valid_address, save_request(access_request.gsub("\n", "") + validate_address_request.gsub("\n", "")), options[:test])
158
+ parse_response = parse_ship_valid_address(response)
159
+ parse_response
160
+ end
161
+
142
162
  protected
143
-
163
+
144
164
  def upsified_location(location)
145
165
  if location.country_code == 'US' && US_TERRITORIES_TREATED_AS_COUNTRIES.include?(location.state)
146
166
  atts = {:country => location.state}
147
- [:zip, :city, :address1, :address2, :address3, :phone, :fax, :address_type].each do |att|
167
+ [:zip, :city, :address1, :address2, :address3, :phone, :fax, :address_type, :attention_name].each do |att|
148
168
  atts[att] = location.send(att)
149
169
  end
150
170
  Address.new(atts)
@@ -152,7 +172,8 @@ module Omniship
152
172
  location
153
173
  end
154
174
  end
155
-
175
+
176
+
156
177
  def build_access_request
157
178
  builder = Nokogiri::XML::Builder.new do |xml|
158
179
  xml.AccessRequest {
@@ -171,6 +192,15 @@ module Omniship
171
192
 
172
193
  # Build the ship_confirm XML request
173
194
  def build_ship_confirm(origin, destination, packages, options={})
195
+
196
+ #Return Service types:
197
+ #‘2’ = UPS Print and Mail (PNM)
198
+ #‘3’ = UPS Return Service 1- Attempt (RS1)
199
+ #‘5’ = UPS Return Service 3-Attempt (RS3)
200
+ #‘8’ = UPS Electronic Return Label (ERL)
201
+ #‘9’ = UPS Print Return Label (PRL)
202
+
203
+
174
204
  packages = Array(packages)
175
205
  builder = Nokogiri::XML::Builder.new do |xml|
176
206
  xml.ShipmentConfirmRequest {
@@ -196,9 +226,22 @@ module Omniship
196
226
  }
197
227
  xml.ShipmentServiceOptions {
198
228
  xml.SaturdayDelivery if options[:saturday] == true
229
+ if options[:delivery_confirmation_type].present?
230
+ xml.DeliveryConfirmation {
231
+ xml.DCISType options[:delivery_confirmation_type]
232
+ }
233
+ end
199
234
  }
235
+ if options[:return_service_code].present?
236
+ xml.ReturnService {
237
+ xml.Code options[:return_service_code]
238
+ if options[:return_service_description].present?
239
+ xml.Description options[:return_service_description]
240
+ end
241
+ }
242
+ end
200
243
  packages.each do |package|
201
- imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
244
+ imperial = ['US', 'LR', 'MM'].include?(origin.country_code(:alpha2))
202
245
  xml.Package {
203
246
  xml.PackagingType {
204
247
  xml.Code package.options[:package_type]
@@ -207,9 +250,9 @@ module Omniship
207
250
  xml.UnitOfMeasurement {
208
251
  xml.Code imperial ? 'IN' : 'CM'
209
252
  }
210
- [:length,:width,:height].each do |axis|
253
+ [:length, :width, :height].each do |axis|
211
254
  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
255
+ xml.send axis.to_s.gsub(/^[a-z]|\s+[-z]/) { |a| a.upcase }, [value, 0.1].max
213
256
  end
214
257
  }
215
258
  xml.PackageWeight {
@@ -217,7 +260,15 @@ module Omniship
217
260
  xml.Code imperial ? 'LBS' : 'KGS'
218
261
  }
219
262
  value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # decimals
220
- xml.Weight [value,0.1].max
263
+ xml.Weight [value, 0.1].max
264
+ }
265
+ xml.Description package.options[:package_description] if package.options[:package_description].present?
266
+ xml.PackageServiceOptions {
267
+ if package.options[:delivery_confirmation_type].present?
268
+ xml.DeliveryConfirmation {
269
+ xml.DCISType package.options[:delivery_confirmation_type]
270
+ }
271
+ end
221
272
  }
222
273
  }
223
274
  end
@@ -247,19 +298,40 @@ module Omniship
247
298
  builder.to_xml
248
299
  end
249
300
 
250
- def build_void_request(tracking_number)
301
+ def build_void_request(ups_shipment_id,tracking_number)
251
302
  builder = Nokogiri::XML::Builder.new do |xml|
252
- xml.VoidShipmentRequest {
303
+ xml.VoidShipmentRequest {
253
304
  xml.Request {
254
305
  xml.RequestAction 'Void'
255
306
  }
256
307
  xml.ExpandedVoidShipment {
257
- xml.ShipmentIdentificationNumber tracking_number
308
+ xml.ShipmentIdentificationNumber ups_shipment_id
309
+ xml.TrackingNumber tracking_number
258
310
  }
259
311
  }
260
312
  end
261
313
  builder.to_xml
262
314
  end
315
+
316
+ def build_valid_address_request(address,city,state,zip_code,country_code)
317
+ builder = Nokogiri::XML::Builder.new do |xml|
318
+ xml.AddressValidationRequest {
319
+ xml.Request{
320
+ xml.RequestAction 'XAV'
321
+ xml.RequestOption 3
322
+ }
323
+
324
+ xml.AddressKeyFormat{
325
+ xml.AddressLine address
326
+ xml.PoliticalDivision2 city
327
+ xml.PoliticalDivision1 state
328
+ xml.PostcodePrimaryLow zip_code
329
+ xml.CountryCode country_code
330
+ }
331
+ }
332
+ end
333
+ builder.to_xml
334
+ end
263
335
 
264
336
  def build_rate_request(origin, destination, packages, options={})
265
337
  packages = Array(packages)
@@ -271,88 +343,95 @@ module Omniship
271
343
  }
272
344
  # not implemented: 'Rate' RequestOption to specify a single service query
273
345
  # 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
346
+ pickup_type = options[:pickup_type] || :daily_pickup
347
+ xml.PickupType {
348
+ xml.Code PICKUP_CODES[pickup_type]
349
+ # not implemented: PickupType/PickupDetails element
350
+ }
351
+ cc = options[:customer_classification] || DEFAULT_CUSTOMER_CLASSIFICATIONS[pickup_type]
352
+ xml.CustomerClassification {
353
+ xml.Code CUSTOMER_CLASSIFICATIONS[cc]
354
+ }
355
+ xml.Shipment {
356
+ build_location_node(['Shipper'], (options[:shipper] || origin), options, xml)
357
+ build_location_node(['ShipTo'], destination, options, xml)
358
+ if options[:shipper] && options[:shipper] != origin
359
+ build_location_node(['ShipFrom'], origin, options, xml)
360
+ end
289
361
 
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'
362
+ # not implemented: * Shipment/ShipmentWeight element
363
+ # * Shipment/ReferenceNumber element
364
+ # * Shipment/Service element
365
+ # * Shipment/PickupDate element
366
+ # * Shipment/ScheduledDeliveryDate element
367
+ # * Shipment/ScheduledDeliveryTime element
368
+ # * Shipment/AlternateDeliveryTime element
369
+ # * Shipment/DocumentsOnly element
370
+
371
+ packages.each do |package|
372
+ imperial = ['US', 'LR', 'MM'].include?(origin.country_code(:alpha2))
373
+ xml.Package {
374
+ xml.PackagingType {
375
+ xml.Code '02'
308
376
  }
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'
377
+ xml.Dimensions {
378
+ xml.UnitOfMeasurement {
379
+ xml.Code imperial ? 'IN' : 'CM'
380
+ }
381
+ [:length, :width, :height].each do |axis|
382
+ value = ((imperial ? package.inches(axis) : package.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
383
+ xml.send axis.to_s.gsub(/^[a-z]|\s+[-z]/) { |a| a.upcase }, [value, 0.1].max
384
+ end
317
385
  }
318
- value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # 3 decimals
319
- xml.Weight [value,0.1].max
386
+ xml.PackageWeight {
387
+ xml.UnitOfMeasurement {
388
+ xml.Code imperial ? 'LBS' : 'KGS'
389
+ }
390
+ value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # 3 decimals
391
+ xml.Weight [value, 0.1].max
392
+ }
393
+ xml.PackageServiceOptions {
394
+ if package.options[:delivery_confirmation_type].present?
395
+ xml.DeliveryConfirmation {
396
+ xml.DCISType package.options[:delivery_confirmation_type]
397
+ }
398
+ end
399
+ }
400
+ # not implemented: * Shipment/Package/LargePackageIndicator element
401
+ # * Shipment/Package/ReferenceNumber element
402
+ # * Shipment/Package/AdditionalHandling element
320
403
  }
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
- }
404
+ end
405
+ # not implemented: * Shipment/ShipmentServiceOptions element
406
+ # * Shipment/RateInformation element
407
+ }
408
+ }
331
409
  end
332
410
  builder.to_xml
333
411
  end
334
-
412
+
335
413
  def build_tracking_request(tracking_number, options={})
336
- bulder = Nokogiri::XML::Builder.new do |xml|
414
+ builder = Nokogiri::XML::Builder.new do |xml|
337
415
  xml.TrackRequest {
338
416
  xml.Request {
339
417
  xml.RequestAction 'Track'
340
- xml.RequestOption '1'
341
- }
342
- xml.TrackingNumber tracking_number.to_s
418
+ xml.RequestOption '7'
419
+ }
420
+ xml.TrackingNumber tracking_number.to_s
421
+ xml.TrackingOption '02'
343
422
  }
344
423
  end
345
424
  builder.to_xml
346
425
  end
347
-
348
- def build_location_node(name,location,options={},xml)
349
- for name in name
426
+
427
+ def build_location_node(name, location, options={}, xml)
428
+ for name in name
350
429
  xml.send(name) {
351
430
  xml.Name location.name unless location.name.blank?
352
431
  xml.AttentionName location.attention_name unless location.attention_name.blank?
353
432
  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?
433
+ xml.PhoneNumber location.phone.gsub(/[^\d]/, '') unless location.phone.blank?
434
+ xml.FaxNumber location.fax.gsub(/[^\d]/, '') unless location.fax.blank?
356
435
 
357
436
  if name =='Shipper' and (origin_account = @options[:origin_account] || options[:origin_account])
358
437
  xml.ShipperNumber origin_account
@@ -368,7 +447,7 @@ module Omniship
368
447
  xml.StateProvinceCode location.province unless location.province.blank?
369
448
  xml.PostalCode location.postal_code unless location.postal_code.blank?
370
449
  xml.CountryCode location.country_code unless location.country_code.blank?
371
- xml.ResidentialAddressIndicator true unless location.commercial?
450
+ xml.ResidentialAddress unless location.commercial?
372
451
  }
373
452
  }
374
453
  end
@@ -376,49 +455,82 @@ module Omniship
376
455
 
377
456
  def parse_rate_response(origin, destination, packages, response, options={})
378
457
  rates = []
379
-
458
+
380
459
  xml = Nokogiri::XML(response)
381
460
  success = response_success?(xml)
382
461
  message = response_message(xml)
383
462
 
384
463
  if success
385
464
  rate_estimates = []
386
-
465
+
387
466
  xml.xpath('/*/RatedShipment').each do |rated_shipment|
388
467
  service_code = rated_shipment.xpath('Service/Code').text.to_s
389
468
  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
469
+ delivery_date = days_to_delivery >= 1 ? days_to_delivery.days.from_now.strftime("%Y-%m-%d") : nil
391
470
 
392
471
  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])
472
+ service_name_for(origin, service_code),
473
+ :total_price => rated_shipment.xpath('TotalCharges/MonetaryValue').text.to_s.to_f,
474
+ :currency => rated_shipment.xpath('TotalCharges/CurrencyCode').text.to_s,
475
+ :service_code => service_code,
476
+ :packages => packages,
477
+ :delivery_range => [delivery_date])
399
478
  end
400
479
  end
401
480
  RateResponse.new(success, message, Hash.from_xml(response).values.first, :rates => rate_estimates, :xml => response, :request => last_request)
402
481
  end
403
-
404
- def parse_tracking_response(response, options={})
405
- #TODO
482
+
483
+ def parse_tracking_response(response, options={})
406
484
  xml = Nokogiri::XML(response)
407
485
  success = response_success?(xml)
408
486
  message = response_message(xml)
409
487
 
488
+ puts "response :" + xml.to_s
489
+
410
490
  if success
411
491
  tracking_number, origin, destination = nil
412
- shipment_events = []
492
+ shipment_details = Hash.new
413
493
 
494
+ tracking_number = xml.xpath('/*/Shipment/Package/TrackingNumber').text
495
+ shipment_details[:tracking_number] = tracking_number
496
+
497
+ estimated_delivery_date = xml.xpath('/*/Shipment/ScheduledDeliveryDate').text
498
+ if !estimated_delivery_date.blank?
499
+ estimated_delivery_date = Date.strptime(estimated_delivery_date,'%Y%m%d')
500
+ shipment_details[:estimated_delivery_date] = estimated_delivery_date
501
+ else
502
+ reschedule_delivery_date = xml.xpath('/*/Shipment/Package/RescheduledDeliveryDate').text
503
+ if !reschedule_delivery_date.blank?
504
+ reschedule_delivery_date = Date.strptime(reschedule_delivery_date,'%Y%m%d')
505
+ shipment_details[:estimated_delivery_date] = reschedule_delivery_date
506
+ end
507
+ end
508
+
509
+ puts 'tracking_number: ' + tracking_number
510
+ puts 'estimated_delivery_date: ' + estimated_delivery_date.to_s
511
+
512
+ activities = []
513
+ xml.xpath('/*/Shipment/Package/Activity').each do |activity|
514
+ status_code = activity.xpath('Status/StatusCode').text
515
+ status_dsc = activity.xpath('Status/StatusType/Description').text
516
+ date = activity.xpath('Date').text
517
+ time = activity.xpath('Time').text
518
+ hour, minute, second = time.scan(/\d{2}/)
519
+ year, month, day = date[0..3], date[4..5], date[6..7]
520
+ date_time = Time.utc(year, month, day, hour, minute, second)
521
+ location = activity.xpath('ActivityLocation/Address/City').text + ' ' + activity.xpath('ActivityLocation/Address/StateProvinceCode').text + ' ' + activity.xpath('ActivityLocation/Address/CountryCode').text
522
+ activities << {:status_code => status_code, :status_dsc => status_dsc, :date => date_time, :location => location}
523
+ end
524
+ shipment_details[:activities] = activities
525
+
414
526
  #first_shipment = xml.gelements['/*/Shipment']
415
527
  #first_package = first_shipment.elements['Package']
416
528
  #tracking_number = first_shipment.get_text('ShipmentIdentificationNumber | Package/TrackingNumber').to_s
417
-
529
+
418
530
  #origin, destination = %w{Shipper ShipTo}.map do |location|
419
531
  # location_from_address_node(first_shipment.elements["#{location}/Address"])
420
532
  #end
421
-
533
+
422
534
  #activities = first_package.get_elements('Activity')
423
535
  #unless activities.empty?
424
536
  # shipment_events = activities.map do |activity|
@@ -459,15 +571,15 @@ module Omniship
459
571
  # :origin => origin,
460
572
  # :destination => destination,
461
573
  # :tracking_number => tracking_number)
462
- return 'Feature Not Available'
574
+ return shipment_details
463
575
  end
464
-
576
+
465
577
  def parse_ship_confirm_response(origin, destination, packages, response, options={})
466
578
  xml = Nokogiri::XML(response)
467
579
  success = response_success?(xml)
468
-
580
+
469
581
  if success
470
- @response_text = xml.xpath('//*/ShipmentDigest').text
582
+ @response_text = xml.xpath('//*/ShipmentDigest').text
471
583
  else
472
584
  @response_text = {}
473
585
  @response_text[:status] = xml.xpath('/*/Response/ResponseStatusDescription').text
@@ -481,24 +593,24 @@ module Omniship
481
593
  def parse_ship_accept_response(response, options={})
482
594
  xml = Nokogiri::XML(response)
483
595
  success = response_success?(xml)
484
- @response_text = {}
485
-
596
+ @response_text = {}
597
+
486
598
  if success
487
599
  tracking_number = []
488
- label = []
600
+ label = []
489
601
 
490
- @response_text[:charges] = xml.xpath('/*/ShipmentResults/*/TotalCharges/MonetaryValue').text
602
+ @response_text[:charges] = xml.xpath('/*/ShipmentResults/*/TotalCharges/MonetaryValue').text
491
603
  @response_text[:shipment_id] = xml.xpath('/*/ShipmentResults/ShipmentIdentificationNumber').text
492
-
604
+
493
605
  xml.xpath('/*/ShipmentResults/*/TrackingNumber').each do |track|
494
606
  tracking_number << track.text
495
607
  end
496
- @response_text[:tracking_number] = tracking_number
608
+ @response_text[:tracking_number] = tracking_number
497
609
 
498
610
  xml.xpath('/*/ShipmentResults/*/LabelImage/GraphicImage').each do |image|
499
611
  label << image.text
500
612
  end
501
- @response_text[:label] = label
613
+ @response_text[:label] = label
502
614
  @response_text[:success] = true
503
615
  else
504
616
  @response_text[:status] = xml.xpath('/*/Response/ResponseStatusDescription').text
@@ -511,54 +623,79 @@ module Omniship
511
623
 
512
624
  def parse_ship_void_response(response, options={})
513
625
  xml = Nokogiri::XML(response)
626
+ puts "Void response: " + xml.to_s
514
627
  success = response_success?(xml)
515
628
  if success
516
629
  @void = "Shipment successfully voided!"
517
630
  else
518
631
  @void = "Voiding shipment failed!"
519
632
  end
520
-
633
+
521
634
  return @void
522
635
  end
636
+
637
+ def parse_ship_valid_address(response, options={})
638
+ xml = Nokogiri::XML(response)
639
+ success = response_success?(xml)
640
+ suggested_addresses = Array.new
641
+ if success
642
+ addresses = xml.xpath('/*/AddressKeyFormat').each do |address_data|
643
+ address_hash = Hash.new
644
+ address_hash[:address] = address_data.xpath('AddressLine').text
645
+ address_hash[:city] = address_data.xpath('PoliticalDivision2').text
646
+ address_hash[:state] = address_data.xpath('PoliticalDivision1').text
647
+ address_hash[:zip_code] = address_data.xpath('PostcodePrimaryLow').text
648
+ address_hash[:country_code] = address_data.xpath('CountryCode').text
649
+ suggested_addresses << address_hash
650
+ end
651
+ else
652
+ return "Address validation failed!"
653
+ end
654
+
655
+ return suggested_addresses
656
+ end
523
657
 
524
658
  def location_from_address_node(address)
525
659
  return nil unless address
526
660
  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
- )
661
+ :country => node_text_or_nil(address.elements['CountryCode']),
662
+ :postal_code => node_text_or_nil(address.elements['PostalCode']),
663
+ :province => node_text_or_nil(address.elements['StateProvinceCode']),
664
+ :city => node_text_or_nil(address.elements['City']),
665
+ :address1 => node_text_or_nil(address.elements['AddressLine1']),
666
+ :address2 => node_text_or_nil(address.elements['AddressLine2']),
667
+ :address3 => node_text_or_nil(address.elements['AddressLine3'])
668
+ )
535
669
  end
536
-
670
+
537
671
  def response_success?(xml)
538
672
  xml.xpath('/*/Response/ResponseStatusCode').text == '1'
539
673
  end
540
-
674
+
541
675
  def response_message(xml)
542
676
  xml.xpath('/*/Response/Error/ErrorDescription | /*/Response/ResponseStatusDescription').text
543
677
  end
544
-
678
+
545
679
  def commit(action, request, test = false)
546
680
  ssl_post("#{test ? TEST_URL : LIVE_URL}/#{RESOURCES[action]}", request)
547
681
  end
548
-
549
-
682
+
683
+
550
684
  def service_name_for(origin, code)
551
685
  origin = origin.country_code(:alpha2)
552
-
686
+
553
687
  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
-
688
+ when "CA" then
689
+ CANADA_ORIGIN_SERVICES[code]
690
+ when "MX" then
691
+ MEXICO_ORIGIN_SERVICES[code]
692
+ when *EU_COUNTRY_CODES then
693
+ EU_ORIGIN_SERVICES[code]
694
+ end
695
+
559
696
  name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
560
697
  name ||= DEFAULT_SERVICES[code]
561
698
  end
562
-
699
+
563
700
  end
564
701
  end