active_shipping 0.12.4 → 0.12.5

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_shipping.rb +2 -1
  3. data/lib/active_shipping/shipping/base.rb +2 -2
  4. data/lib/active_shipping/shipping/carrier.rb +16 -13
  5. data/lib/active_shipping/shipping/carriers/benchmark_carrier.rb +3 -4
  6. data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +1 -3
  7. data/lib/active_shipping/shipping/carriers/canada_post.rb +33 -44
  8. data/lib/active_shipping/shipping/carriers/canada_post_pws.rb +72 -81
  9. data/lib/active_shipping/shipping/carriers/fedex.rb +118 -109
  10. data/lib/active_shipping/shipping/carriers/kunaki.rb +33 -32
  11. data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +9 -16
  12. data/lib/active_shipping/shipping/carriers/shipwire.rb +36 -35
  13. data/lib/active_shipping/shipping/carriers/stamps.rb +39 -51
  14. data/lib/active_shipping/shipping/carriers/ups.rb +280 -116
  15. data/lib/active_shipping/shipping/carriers/ups.rb.orig +456 -0
  16. data/lib/active_shipping/shipping/carriers/usps.rb +145 -100
  17. data/lib/active_shipping/shipping/carriers/usps.rb.orig +616 -0
  18. data/lib/active_shipping/shipping/errors.rb +1 -1
  19. data/lib/active_shipping/shipping/label_response.rb +25 -0
  20. data/lib/active_shipping/shipping/location.rb +18 -16
  21. data/lib/active_shipping/shipping/package.rb +51 -54
  22. data/lib/active_shipping/shipping/rate_estimate.rb +10 -12
  23. data/lib/active_shipping/shipping/rate_response.rb +3 -7
  24. data/lib/active_shipping/shipping/response.rb +6 -9
  25. data/lib/active_shipping/shipping/shipment_event.rb +2 -4
  26. data/lib/active_shipping/shipping/shipment_packer.rb +32 -17
  27. data/lib/active_shipping/shipping/shipping_response.rb +2 -4
  28. data/lib/active_shipping/shipping/tracking_response.rb +3 -5
  29. data/lib/active_shipping/version.rb +1 -1
  30. data/lib/vendor/quantified/lib/quantified/attribute.rb +79 -80
  31. data/lib/vendor/quantified/lib/quantified/length.rb +5 -5
  32. data/lib/vendor/quantified/lib/quantified/mass.rb +4 -4
  33. data/lib/vendor/quantified/test/length_test.rb +19 -15
  34. data/lib/vendor/quantified/test/mass_test.rb +14 -14
  35. data/lib/vendor/quantified/test/test_helper.rb +1 -2
  36. data/lib/vendor/test_helper.rb +0 -1
  37. data/lib/vendor/xml_node/benchmark/bench_generation.rb +2 -4
  38. data/lib/vendor/xml_node/lib/xml_node.rb +54 -55
  39. data/lib/vendor/xml_node/test/test_generating.rb +23 -28
  40. data/lib/vendor/xml_node/test/test_parsing.rb +5 -8
  41. metadata +6 -25
  42. checksums.yaml.gz.sig +0 -1
  43. data.tar.gz.sig +0 -0
  44. metadata.gz.sig +0 -0
@@ -14,10 +14,12 @@ module ActiveMerchant
14
14
 
15
15
  RESOURCES = {
16
16
  :rates => 'ups.app/xml/Rate',
17
- :track => 'ups.app/xml/Track'
17
+ :track => 'ups.app/xml/Track',
18
+ :ship_confirm => 'ups.app/xml/ShipConfirm',
19
+ :ship_accept => 'ups.app/xml/ShipAccept'
18
20
  }
19
21
 
20
- PICKUP_CODES = HashWithIndifferentAccess.new({
22
+ PICKUP_CODES = HashWithIndifferentAccess.new(
21
23
  :daily_pickup => "01",
22
24
  :customer_counter => "03",
23
25
  :one_time_pickup => "06",
@@ -25,18 +27,18 @@ module ActiveMerchant
25
27
  :suggested_retail_rates => "11",
26
28
  :letter_center => "19",
27
29
  :air_service_center => "20"
28
- })
30
+ )
29
31
 
30
- CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new({
32
+ CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new(
31
33
  :wholesale => "01",
32
34
  :occasional => "03",
33
35
  :retail => "04"
34
- })
36
+ )
35
37
 
36
38
  # these are the defaults described in the UPS API docs,
37
39
  # but they don't seem to apply them under all circumstances,
38
40
  # so we need to take matters into our own hands
39
- DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash,key|
41
+ DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash, key|
40
42
  hash[key] = case key.to_sym
41
43
  when :daily_pickup then :wholesale
42
44
  when :customer_counter then :retail
@@ -86,24 +88,26 @@ module ActiveMerchant
86
88
  "07" => "UPS Express"
87
89
  }
88
90
 
89
- TRACKING_STATUS_CODES = HashWithIndifferentAccess.new({
91
+ TRACKING_STATUS_CODES = HashWithIndifferentAccess.new(
90
92
  'I' => :in_transit,
91
93
  'D' => :delivered,
92
94
  'X' => :exception,
93
95
  'P' => :pickup,
94
96
  'M' => :manifest_pickup
95
- })
97
+ )
96
98
 
97
99
  # From http://en.wikipedia.org/w/index.php?title=European_Union&oldid=174718707 (Current as of November 30, 2007)
98
- 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"]
100
+ EU_COUNTRY_CODES = %w(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)
99
101
 
100
- US_TERRITORIES_TREATED_AS_COUNTRIES = ["AS", "FM", "GU", "MH", "MP", "PW", "PR", "VI"]
102
+ US_TERRITORIES_TREATED_AS_COUNTRIES = %w(AS FM GU MH MP PW PR VI)
103
+
104
+ IMPERIAL_COUNTRIES = %w(US LR MM)
101
105
 
102
106
  def requirements
103
107
  [:key, :login, :password]
104
108
  end
105
109
 
106
- def find_rates(origin, destination, packages, options={})
110
+ def find_rates(origin, destination, packages, options = {})
107
111
  origin, destination = upsified_location(origin), upsified_location(destination)
108
112
  options = @options.merge(options)
109
113
  packages = Array(packages)
@@ -113,7 +117,7 @@ module ActiveMerchant
113
117
  parse_rate_response(origin, destination, packages, response, options)
114
118
  end
115
119
 
116
- def find_tracking_info(tracking_number, options={})
120
+ def find_tracking_info(tracking_number, options = {})
117
121
  options = @options.update(options)
118
122
  access_request = build_access_request
119
123
  tracking_request = build_tracking_request(tracking_number, options)
@@ -121,6 +125,46 @@ module ActiveMerchant
121
125
  parse_tracking_response(response, options)
122
126
  end
123
127
 
128
+ def create_shipment(origin, destination, packages, options = {})
129
+ options = @options.merge(options)
130
+ packages = Array(packages)
131
+ access_request = build_access_request
132
+
133
+ begin
134
+
135
+ # STEP 1: Confirm. Validation step, important for verifying price.
136
+ confirm_request = build_shipment_request(origin, destination, packages, options)
137
+ logger.debug(confirm_request) if logger
138
+
139
+ confirm_response = commit(:ship_confirm, save_request(access_request + confirm_request), (options[:test] || false))
140
+ logger.debug(confirm_response) if logger
141
+
142
+ # ... now, get the digest, it's needed to get the label. In theory,
143
+ # one could make decisions based on the price or some such to avoid
144
+ # surprises. This also has *no* error handling yet.
145
+ xml = parse_ship_confirm(confirm_response)
146
+ success = response_success?(xml)
147
+ message = response_message(xml)
148
+ digest = response_digest(xml)
149
+ raise message unless success
150
+
151
+ # STEP 2: Accept. Use shipment digest in first response to get the actual label.
152
+ accept_request = build_accept_request(digest, options)
153
+ logger.debug(accept_request) if logger
154
+
155
+ accept_response = commit(:ship_accept, save_request(access_request + accept_request), (options[:test] || false))
156
+ logger.debug(accept_response) if logger
157
+
158
+ # ...finally, build a map from the response that contains
159
+ # the label data and tracking information.
160
+ parse_ship_accept(accept_response)
161
+
162
+ rescue RuntimeError => e
163
+ raise "Could not obtain shipping label. #{e.message}."
164
+
165
+ end
166
+ end
167
+
124
168
  protected
125
169
 
126
170
  def upsified_location(location)
@@ -144,7 +188,7 @@ module ActiveMerchant
144
188
  xml_request.to_s
145
189
  end
146
190
 
147
- def build_rate_request(origin, destination, packages, options={})
191
+ def build_rate_request(origin, destination, packages, options = {})
148
192
  packages = Array(packages)
149
193
  xml_request = XmlNode.new('RatingServiceSelectionRequest') do |root_node|
150
194
  root_node << XmlNode.new('Request') do |request|
@@ -183,45 +227,8 @@ module ActiveMerchant
183
227
  # * Shipment/DocumentsOnly element
184
228
 
185
229
  packages.each do |package|
186
- imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
187
-
188
- shipment << XmlNode.new("Package") do |package_node|
189
-
190
- # not implemented: * Shipment/Package/PackagingType element
191
- # * Shipment/Package/Description element
192
-
193
- package_node << XmlNode.new("PackagingType") do |packaging_type|
194
- packaging_type << XmlNode.new("Code", '02')
195
- end
196
-
197
- package_node << XmlNode.new("Dimensions") do |dimensions|
198
- dimensions << XmlNode.new("UnitOfMeasurement") do |units|
199
- units << XmlNode.new("Code", imperial ? 'IN' : 'CM')
200
- end
201
- [:length,:width,:height].each do |axis|
202
- value = ((imperial ? package.inches(axis) : package.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
203
- dimensions << XmlNode.new(axis.to_s.capitalize, [value,0.1].max)
204
- end
205
- end
206
-
207
- package_node << XmlNode.new("PackageWeight") do |package_weight|
208
- package_weight << XmlNode.new("UnitOfMeasurement") do |units|
209
- units << XmlNode.new("Code", imperial ? 'LBS' : 'KGS')
210
- end
211
- if package.value.present? && package.currency.present?
212
- add_insured_node( package_node, currency:package.currency, value:(package.value.to_i/100) )
213
- end
214
-
215
- value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # 3 decimals
216
- package_weight << XmlNode.new("Weight", [value,0.1].max)
217
- end
218
-
219
- # not implemented: * Shipment/Package/LargePackageIndicator element
220
- # * Shipment/Package/ReferenceNumber element
221
- # * Shipment/Package/PackageServiceOptions element
222
- # * Shipment/Package/AdditionalHandling element
223
- end
224
-
230
+ options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
231
+ shipment << build_package_node(package, options)
225
232
  end
226
233
 
227
234
  # not implemented: * Shipment/ShipmentServiceOptions element
@@ -235,7 +242,110 @@ module ActiveMerchant
235
242
  xml_request.to_s
236
243
  end
237
244
 
238
- def build_tracking_request(tracking_number, options={})
245
+ # Build XML node to request a shipping label for the given packages.
246
+ #
247
+ # options:
248
+ # * origin_account: who will pay for the shipping label
249
+ # * customer_context: a "guid like substance" -- according to UPS
250
+ # * shipper: who is sending the package and where it should be returned
251
+ # if it is undeliverable.
252
+ # * ship_from: where the package is picked up.
253
+ # * service_code: default to '14'
254
+ # * service_descriptor: default to 'Next Day Air Early AM'
255
+ # * saturday_delivery: any truthy value causes this element to exist
256
+ # * optional_processing: 'validate' (blank) or 'nonvalidate' or blank
257
+ #
258
+ def build_shipment_request(origin, destination, packages, options = {})
259
+ # There are a lot of unimplemented elements, documenting all of them
260
+ # wouldprobably be unhelpful.
261
+
262
+ xml_request = XmlNode.new('ShipmentConfirmRequest') do |root_node|
263
+ root_node << XmlNode.new('Request') do |request|
264
+ # Required element and the text must be "ShipConfirm"
265
+ request << XmlNode.new('RequestAction', 'ShipConfirm')
266
+ # Required element cotnrols level of address validation.
267
+ request << XmlNode.new('RequestOption', options[:optional_processing] || 'validate')
268
+ # Optional element to identify transactions between client and server.
269
+ if options[:customer_context]
270
+ request << XmlNode.new('TransactionReference') do |refer|
271
+ refer << XmlNode.new('CustomerContext', options[:customer_context])
272
+ end
273
+ end
274
+ end
275
+ root_node << XmlNode.new('Shipment') do |shipment|
276
+ # Required element.
277
+ shipment << XmlNode.new('Service') do |service|
278
+ service << XmlNode.new('Code', options[:service_code] || '14')
279
+ service << XmlNode.new('Description', options[:service_description] || 'Next Day Air Early AM')
280
+ end
281
+ # Required element. The delivery destination.
282
+ shipment << build_location_node('ShipTo', destination, {})
283
+ # Required element. The company whose account is responsible for the label(s).
284
+ shipment << build_location_node('Shipper', options[:shipper] || origin, {})
285
+ # Required if pickup is different different from shipper's address.
286
+ if options[:ship_from]
287
+ shipment << build_location_node('ShipFrom', options[:ship_from], {})
288
+ end
289
+ # Optional.
290
+ if options[:saturday_delivery]
291
+ shipment << XmlNode.new('ShipmentServiceOptions') do |opts|
292
+ opts << XmlNode.new('SaturdayDelivery')
293
+ end
294
+ end
295
+ # Optional.
296
+ if options[:origin_account]
297
+ shipment << XmlNode.new('RateInformation') do |rate|
298
+ rate << XmlNode.new('NegotiatedRatesIndicator')
299
+ end
300
+ end
301
+ # Optional.
302
+ if options[:shipment] && options[:shipment][:reference_number]
303
+ shipment << XmlNode.new("ReferenceNumber") do |ref_node|
304
+ ref_node << XmlNode.new("Code", options[:shipment][:reference_number][:code] || "")
305
+ ref_node << XmlNode.new("Value", options[:shipment][:reference_number][:value])
306
+ end
307
+ end
308
+ # Conditionally required. Either this element or an ItemizedPaymentInformation
309
+ # is needed. However, only PaymentInformation is not implemented.
310
+ shipment << XmlNode.new('PaymentInformation') do |payment|
311
+ payment << XmlNode.new('Prepaid') do |prepay|
312
+ prepay << XmlNode.new('BillShipper') do |bill|
313
+ bill << XmlNode.new('AccountNumber', options[:origin_account])
314
+ end
315
+ end
316
+ end
317
+ # A request may specify multiple packages.
318
+ options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
319
+ packages.each do |package|
320
+ shipment << build_package_node(package, options)
321
+ end
322
+ end
323
+ # I don't know all of the options that UPS supports for labels
324
+ # so I'm going with something very simple for now.
325
+ root_node << XmlNode.new('LabelSpecification') do |specification|
326
+ specification << XmlNode.new('LabelPrintMethod') do |print_method|
327
+ print_method << XmlNode.new('Code', 'GIF')
328
+ end
329
+ specification << XmlNode.new('HTTPUserAgent', 'Mozilla/4.5') # hmmm
330
+ specification << XmlNode.new('LabelImageFormat', 'GIF') do |image_format|
331
+ image_format << XmlNode.new('Code', 'GIF')
332
+ end
333
+ end
334
+ end
335
+ xml_request.to_s
336
+ end
337
+
338
+ def build_accept_request(digest, options = {})
339
+ xml_request = XmlNode.new('ShipmentAcceptRequest') do |root_node|
340
+ root_node << XmlNode.new('Request') do |request|
341
+ request << XmlNode.new('RequestAction', 'ShipAccept')
342
+ end
343
+ root_node << XmlNode.new('ShipmentDigest', digest)
344
+ end
345
+ xml_request.to_s
346
+ end
347
+
348
+ def build_tracking_request(tracking_number, options = {})
239
349
  xml_request = XmlNode.new('TrackRequest') do |root_node|
240
350
  root_node << XmlNode.new('Request') do |request|
241
351
  request << XmlNode.new('RequestAction', 'Track')
@@ -246,28 +356,44 @@ module ActiveMerchant
246
356
  xml_request.to_s
247
357
  end
248
358
 
249
- def build_location_node(name,location,options={})
359
+ def build_location_node(name, location, options = {})
250
360
  # not implemented: * Shipment/Shipper/Name element
251
361
  # * Shipment/(ShipTo|ShipFrom)/CompanyName element
252
362
  # * Shipment/(Shipper|ShipTo|ShipFrom)/AttentionName element
253
363
  # * Shipment/(Shipper|ShipTo|ShipFrom)/TaxIdentificationNumber element
254
- location_node = XmlNode.new(name) do |location_node|
255
- location_node << XmlNode.new('PhoneNumber', location.phone.gsub(/[^\d]/,'')) unless location.phone.blank?
256
- location_node << XmlNode.new('FaxNumber', location.fax.gsub(/[^\d]/,'')) unless location.fax.blank?
364
+ XmlNode.new(name) do |location_node|
365
+ # You must specify the shipper name when creating labels.
366
+ if shipper_name = (options[:origin_name] || @options[:origin_name])
367
+ location_node << XmlNode.new('Name', shipper_name)
368
+ end
369
+ location_node << XmlNode.new('PhoneNumber', location.phone.gsub(/[^\d]/, '')) unless location.phone.blank?
370
+ location_node << XmlNode.new('FaxNumber', location.fax.gsub(/[^\d]/, '')) unless location.fax.blank?
257
371
 
258
- if name == 'Shipper' and (origin_account = @options[:origin_account] || options[:origin_account])
372
+ if name == 'Shipper' and (origin_account = options[:origin_account] || @options[:origin_account])
259
373
  location_node << XmlNode.new('ShipperNumber', origin_account)
260
- elsif name == 'ShipTo' and (destination_account = @options[:destination_account] || options[:destination_account])
374
+ elsif name == 'ShipTo' and (destination_account = options[:destination_account] || @options[:destination_account])
261
375
  location_node << XmlNode.new('ShipperAssignedIdentificationNumber', destination_account)
262
376
  end
263
377
 
378
+ if name = location.company_name || location.name
379
+ location_node << XmlNode.new('CompanyName', name)
380
+ end
381
+
382
+ if phone = location.phone
383
+ location_node << XmlNode.new('PhoneNumber', phone)
384
+ end
385
+
386
+ if attn = location.name
387
+ location_node << XmlNode.new('AttentionName', attn)
388
+ end
389
+
264
390
  location_node << XmlNode.new('Address') do |address|
265
391
  address << XmlNode.new("AddressLine1", location.address1) unless location.address1.blank?
266
392
  address << XmlNode.new("AddressLine2", location.address2) unless location.address2.blank?
267
393
  address << XmlNode.new("AddressLine3", location.address3) unless location.address3.blank?
268
394
  address << XmlNode.new("City", location.city) unless location.city.blank?
269
395
  address << XmlNode.new("StateProvinceCode", location.province) unless location.province.blank?
270
- # StateProvinceCode required for negotiated rates but not otherwise, for some reason
396
+ # StateProvinceCode required for negotiated rates but not otherwise, for some reason
271
397
  address << XmlNode.new("PostalCode", location.postal_code) unless location.postal_code.blank?
272
398
  address << XmlNode.new("CountryCode", location.country_code(:alpha2)) unless location.country_code(:alpha2).blank?
273
399
  address << XmlNode.new("ResidentialAddressIndicator", true) unless location.commercial? # the default should be that UPS returns residential rates for destinations that it doesn't know about
@@ -276,24 +402,51 @@ module ActiveMerchant
276
402
  end
277
403
  end
278
404
 
279
- def add_insured_node(*args)
280
- params, package_node = args.extract_options!, args[0]
281
- currency, value = params[:currency], params[:value].to_i
282
- package_node << XmlNode.new("PackageServiceOptions") do |package_service_options|
283
- package_service_options << XmlNode.new("DeclaredValue") do |declared_value|
284
- declared_value << XmlNode.new("CurrencyCode", currency)
285
- declared_value << XmlNode.new("MonetaryValue", (value.to_i))
405
+ def build_package_node(package, options = {})
406
+ XmlNode.new("Package") do |package_node|
407
+
408
+ # not implemented: * Shipment/Package/PackagingType element
409
+ # * Shipment/Package/Description element
410
+
411
+ package_node << XmlNode.new("PackagingType") do |packaging_type|
412
+ packaging_type << XmlNode.new("Code", '02')
286
413
  end
287
- package_service_options << XmlNode.new("InsuredValue") do |declared_value|
288
- declared_value << XmlNode.new("CurrencyCode", currency)
289
- declared_value << XmlNode.new("MonetaryValue", (value.to_i))
414
+
415
+ package_node << XmlNode.new("Dimensions") do |dimensions|
416
+ dimensions << XmlNode.new("UnitOfMeasurement") do |units|
417
+ units << XmlNode.new("Code", options[:imperial] ? 'IN' : 'CM')
418
+ end
419
+ [:length, :width, :height].each do |axis|
420
+ value = ((options[:imperial] ? package.inches(axis) : package.cm(axis)).to_f * 1000).round / 1000.0 # 3 decimals
421
+ dimensions << XmlNode.new(axis.to_s.capitalize, [value, 0.1].max)
422
+ end
290
423
  end
424
+
425
+ package_node << XmlNode.new("PackageWeight") do |package_weight|
426
+ package_weight << XmlNode.new("UnitOfMeasurement") do |units|
427
+ units << XmlNode.new("Code", options[:imperial] ? 'LBS' : 'KGS')
428
+ end
429
+
430
+ value = ((options[:imperial] ? package.lbs : package.kgs).to_f * 1000).round / 1000.0 # 3 decimals
431
+ package_weight << XmlNode.new("Weight", [value, 0.1].max)
432
+ end
433
+
434
+ if options[:package] && options[:package][:reference_number]
435
+ package_node << XmlNode.new("ReferenceNumber") do |ref_node|
436
+ ref_node << XmlNode.new("Code", options[:package][:reference_number][:code] || "")
437
+ ref_node << XmlNode.new("Value", options[:package][:reference_number][:value])
438
+ end
439
+ end
440
+
441
+ package_node
442
+
443
+ # not implemented: * Shipment/Package/LargePackageIndicator element
444
+ # * Shipment/Package/PackageServiceOptions element
445
+ # * Shipment/Package/AdditionalHandling element
291
446
  end
292
447
  end
293
448
 
294
- def parse_rate_response(origin, destination, packages, response, options={})
295
- rates = []
296
-
449
+ def parse_rate_response(origin, destination, packages, response, options = {})
297
450
  xml = REXML::Document.new(response)
298
451
  success = response_success?(xml)
299
452
  message = response_message(xml)
@@ -306,31 +459,29 @@ module ActiveMerchant
306
459
  days_to_delivery = rated_shipment.get_text('GuaranteedDaysToDelivery').to_s.to_i
307
460
  days_to_delivery = nil if days_to_delivery == 0
308
461
  rate_estimates << RateEstimate.new(origin, destination, @@name,
309
- service_name_for(origin, service_code),
310
- :total_price => rated_shipment.get_text('TotalCharges/MonetaryValue').to_s.to_f,
311
- :insurance_price => rated_shipment.get_text('ServiceOptionsCharges/MonetaryValue').to_s.to_f,
312
- :currency => rated_shipment.get_text('TotalCharges/CurrencyCode').to_s,
313
- :service_code => service_code,
314
- :packages => packages,
315
- :delivery_range => [timestamp_from_business_day(days_to_delivery)],
316
- :negotiated_rate => rated_shipment.get_text('NegotiatedRates/NetSummaryCharges/GrandTotal/MonetaryValue').to_s.to_f)
462
+ service_name_for(origin, service_code),
463
+ :total_price => rated_shipment.get_text('TotalCharges/MonetaryValue').to_s.to_f,
464
+ :insurance_price => rated_shipment.get_text('ServiceOptionsCharges/MonetaryValue').to_s.to_f,
465
+ :currency => rated_shipment.get_text('TotalCharges/CurrencyCode').to_s,
466
+ :service_code => service_code,
467
+ :packages => packages,
468
+ :delivery_range => [timestamp_from_business_day(days_to_delivery)],
469
+ :negotiated_rate => rated_shipment.get_text('NegotiatedRates/NetSummaryCharges/GrandTotal/MonetaryValue').to_s.to_f)
317
470
  end
318
471
  end
319
472
  RateResponse.new(success, message, Hash.from_xml(response).values.first, :rates => rate_estimates, :xml => response, :request => last_request)
320
473
  end
321
474
 
322
- def parse_tracking_response(response, options={})
475
+ def parse_tracking_response(response, options = {})
323
476
  xml = REXML::Document.new(response)
324
477
  success = response_success?(xml)
325
478
  message = response_message(xml)
326
479
 
327
480
  if success
328
- tracking_number, origin, destination, status_code, status_description, delivery_signature = nil
481
+ delivery_signature = nil
482
+ exception_event, scheduled_delivery_date, actual_delivery_date = nil
329
483
  delivered, exception = false
330
- exception_event = nil
331
484
  shipment_events = []
332
- status = {}
333
- scheduled_delivery_date = nil
334
485
 
335
486
  first_shipment = xml.elements['/*/Shipment']
336
487
  first_package = first_shipment.elements['Package']
@@ -347,34 +498,27 @@ module ActiveMerchant
347
498
  status_description = status_node.get_text('Description').to_s
348
499
  status = TRACKING_STATUS_CODES[status_code]
349
500
 
350
-
351
501
  if status_description =~ /out.*delivery/i
352
502
  status = :out_for_delivery
353
503
  end
354
504
 
355
- origin, destination = %w{Shipper ShipTo}.map do |location|
505
+ origin, destination = %w(Shipper ShipTo).map do |location|
356
506
  location_from_address_node(first_shipment.elements["#{location}/Address"])
357
507
  end
358
508
 
359
509
  # Get scheduled delivery date
360
510
  unless status == :delivered
361
- scheduled_delivery_date = parse_ups_datetime({
511
+ scheduled_delivery_date = parse_ups_datetime(
362
512
  :date => first_shipment.get_text('ScheduledDeliveryDate'),
363
513
  :time => nil
364
- })
514
+ )
365
515
  end
366
516
 
367
517
  activities = first_package.get_elements('Activity')
368
518
  unless activities.empty?
369
519
  shipment_events = activities.map do |activity|
370
520
  description = activity.get_text('Status/StatusType/Description').to_s
371
- zoneless_time = if (time = activity.get_text('Time')) &&
372
- (date = activity.get_text('Date'))
373
- time, date = time.to_s, date.to_s
374
- hour, minute, second = time.scan(/\d{2}/)
375
- year, month, day = date[0..3], date[4..5], date[6..7]
376
- Time.utc(year, month, day, hour, minute, second)
377
- end
521
+ zoneless_time = parse_ups_datetime(:time => activity.get_text('Time'), :date => activity.get_text('Date'))
378
522
  location = location_from_address_node(activity.elements['ActivityLocation/Address'])
379
523
  ShipmentEvent.new(description, zoneless_time, location)
380
524
  end
@@ -397,8 +541,12 @@ module ActiveMerchant
397
541
 
398
542
  # Has the shipment been delivered?
399
543
  if status == :delivered
400
- delivery_signature = activities.first.get_text('ActivityLocation/SignedForByName').to_s
401
- if !destination
544
+ delivered_activity = activities.first
545
+ delivery_signature = delivered_activity.get_text('ActivityLocation/SignedForByName').to_s
546
+ if delivered_activity.get_text('Status/StatusType/Code') == 'D'
547
+ actual_delivery_date = parse_ups_datetime(:date => delivered_activity.get_text('Date'), :time => delivered_activity.get_text('Time'))
548
+ end
549
+ unless destination
402
550
  destination = shipment_events[-1].location
403
551
  end
404
552
  shipment_events[-1] = ShipmentEvent.new(shipment_events.last.name, shipment_events.last.time, destination)
@@ -407,21 +555,22 @@ module ActiveMerchant
407
555
 
408
556
  end
409
557
  TrackingResponse.new(success, message, Hash.from_xml(response).values.first,
410
- :carrier => @@name,
411
- :xml => response,
412
- :request => last_request,
413
- :status => status,
414
- :status_code => status_code,
415
- :status_description => status_description,
416
- :delivery_signature => delivery_signature,
417
- :scheduled_delivery_date => scheduled_delivery_date,
418
- :shipment_events => shipment_events,
419
- :delivered => delivered,
420
- :exception => exception,
421
- :exception_event => exception_event,
422
- :origin => origin,
423
- :destination => destination,
424
- :tracking_number => tracking_number)
558
+ :carrier => @@name,
559
+ :xml => response,
560
+ :request => last_request,
561
+ :status => status,
562
+ :status_code => status_code,
563
+ :status_description => status_description,
564
+ :delivery_signature => delivery_signature,
565
+ :scheduled_delivery_date => scheduled_delivery_date,
566
+ :actual_delivery_date => actual_delivery_date,
567
+ :shipment_events => shipment_events,
568
+ :delivered => delivered,
569
+ :exception => exception,
570
+ :exception_event => exception_event,
571
+ :origin => origin,
572
+ :destination => destination,
573
+ :tracking_number => tracking_number)
425
574
  end
426
575
 
427
576
  def location_from_address_node(address)
@@ -457,6 +606,22 @@ module ActiveMerchant
457
606
  xml.get_text('/*/Response/Error/ErrorDescription | /*/Response/ResponseStatusDescription').to_s
458
607
  end
459
608
 
609
+ def response_digest(xml)
610
+ xml.get_text('/*/ShipmentDigest').to_s
611
+ end
612
+
613
+ def parse_ship_confirm(response)
614
+ REXML::Document.new(response)
615
+ end
616
+
617
+ def parse_ship_accept(response)
618
+ xml = REXML::Document.new(response)
619
+ success = response_success?(xml)
620
+ message = response_message(xml)
621
+
622
+ LabelResponse.new(success, message, Hash.from_xml(response).values.first)
623
+ end
624
+
460
625
  def commit(action, request, test = false)
461
626
  ssl_post("#{test ? TEST_URL : LIVE_URL}/#{RESOURCES[action]}", request)
462
627
  end
@@ -478,9 +643,8 @@ module ActiveMerchant
478
643
  end
479
644
 
480
645
  name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
481
- name ||= DEFAULT_SERVICES[code]
646
+ name || DEFAULT_SERVICES[code]
482
647
  end
483
-
484
648
  end
485
649
  end
486
650
  end