active_shipping 1.0.0.pre4 → 1.0.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 (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +0 -1
  5. data/CHANGELOG.md +17 -0
  6. data/CONTRIBUTING.md +2 -2
  7. data/Gemfile.activesupport32 +1 -0
  8. data/README.md +1 -1
  9. data/Rakefile +1 -1
  10. data/active_shipping.gemspec +2 -2
  11. data/lib/active_shipping.rb +2 -3
  12. data/lib/active_shipping/carriers.rb +1 -0
  13. data/lib/active_shipping/carriers/canada_post_pws.rb +281 -266
  14. data/lib/active_shipping/carriers/correios.rb +285 -0
  15. data/lib/active_shipping/carriers/fedex.rb +205 -199
  16. data/lib/active_shipping/carriers/stamps.rb +218 -219
  17. data/lib/active_shipping/carriers/ups.rb +287 -48
  18. data/lib/active_shipping/carriers/usps.rb +3 -3
  19. data/lib/active_shipping/delivery_date_estimate.rb +21 -0
  20. data/lib/active_shipping/delivery_date_estimates_response.rb +11 -0
  21. data/lib/active_shipping/errors.rb +20 -1
  22. data/lib/active_shipping/location.rb +0 -5
  23. data/lib/active_shipping/response.rb +0 -15
  24. data/lib/active_shipping/version.rb +1 -1
  25. data/test/credentials.yml +11 -3
  26. data/test/fixtures/xml/correios/book_response.xml +13 -0
  27. data/test/fixtures/xml/correios/book_response_invalid.xml +13 -0
  28. data/test/fixtures/xml/correios/clothes_response.xml +43 -0
  29. data/test/fixtures/xml/correios/poster_response.xml +23 -0
  30. data/test/fixtures/xml/correios/shoes_response.xml +43 -0
  31. data/test/fixtures/xml/fedex/tracking_request.xml +13 -11
  32. data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
  33. data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
  34. data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
  35. data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
  36. data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
  37. data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
  38. data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
  39. data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
  40. data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
  41. data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
  42. data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
  43. data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
  44. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -1
  45. data/test/fixtures/xml/usps/delivered_extended_tracking_response.xml +11 -0
  46. data/test/remote/canada_post_pws_platform_test.rb +35 -22
  47. data/test/remote/canada_post_pws_test.rb +32 -40
  48. data/test/remote/correios_test.rb +83 -0
  49. data/test/remote/fedex_test.rb +95 -13
  50. data/test/remote/stamps_test.rb +1 -0
  51. data/test/remote/ups_test.rb +77 -40
  52. data/test/remote/usps_test.rb +13 -1
  53. data/test/test_helper.rb +12 -2
  54. data/test/unit/carriers/canada_post_pws_rating_test.rb +66 -59
  55. data/test/unit/carriers/canada_post_pws_shipping_test.rb +34 -23
  56. data/test/unit/carriers/correios_test.rb +244 -0
  57. data/test/unit/carriers/fedex_test.rb +161 -156
  58. data/test/unit/carriers/ups_test.rb +193 -1
  59. data/test/unit/carriers/usps_test.rb +14 -0
  60. data/test/unit/location_test.rb +0 -10
  61. metadata +63 -46
  62. metadata.gz.sig +0 -0
  63. data/lib/vendor/test_helper.rb +0 -6
  64. data/lib/vendor/xml_node/README +0 -36
  65. data/lib/vendor/xml_node/Rakefile +0 -21
  66. data/lib/vendor/xml_node/benchmark/bench_generation.rb +0 -30
  67. data/lib/vendor/xml_node/init.rb +0 -1
  68. data/lib/vendor/xml_node/lib/xml_node.rb +0 -221
  69. data/lib/vendor/xml_node/test/test_generating.rb +0 -89
  70. data/lib/vendor/xml_node/test/test_parsing.rb +0 -40
  71. data/test/fixtures/xml/fedex/tracking_response.xml +0 -151
  72. data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +0 -76
  73. data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +0 -139
  74. data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +0 -150
  75. data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +0 -95
  76. data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +0 -71
@@ -1,12 +1,8 @@
1
- require 'builder'
2
-
3
1
  module ActiveShipping
4
2
  # Stamps.com integration for rating, tracking, address validation, and label generation
5
3
  # Integration ID can be requested from Stamps.com
6
4
 
7
5
  class Stamps < Carrier
8
- self.ssl_version = :SSLv3
9
-
10
6
  cattr_reader :name
11
7
  @@name = 'Stamps'
12
8
 
@@ -225,27 +221,27 @@ module ActiveShipping
225
221
  end
226
222
 
227
223
  def build_header
228
- xml = Builder::XmlMarkup.new
229
- xml.instruct!
230
- xml.soap(:Envelope,
231
- 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
232
- 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
233
- 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
234
- 'xmlns:tns' => 'http://stamps.com/xml/namespace/2014/01/swsim/swsimv34'
235
- ) do
236
- xml.soap :Body do
237
- yield(xml)
224
+ Nokogiri::XML::Builder.new do |xml|
225
+ xml['soap'].Envelope(
226
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
227
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
228
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
229
+ 'xmlns:tns' => 'http://stamps.com/xml/namespace/2014/01/swsim/swsimv34'
230
+ ) do
231
+ xml['soap'].Body do
232
+ yield(xml)
233
+ end
238
234
  end
239
- end
235
+ end.to_xml
240
236
  end
241
237
 
242
238
  def build_authenticate_user_request
243
239
  build_header do |xml|
244
- xml.tns :AuthenticateUser do
245
- xml.tns :Credentials do
246
- xml.tns(:IntegrationID, @options[:integration_id])
247
- xml.tns(:Username, @options[:username])
248
- xml.tns(:Password, @options[:password])
240
+ xml['tns'].AuthenticateUser do
241
+ xml['tns'].Credentials do
242
+ xml['tns'].IntegrationID(@options[:integration_id])
243
+ xml['tns'].Username(@options[:username])
244
+ xml['tns'].Password(@options[:password])
249
245
  end
250
246
  end
251
247
  end
@@ -253,67 +249,67 @@ module ActiveShipping
253
249
 
254
250
  def build_get_account_info_request
255
251
  build_header do |xml|
256
- xml.tns :GetAccountInfo do
257
- xml.tns(:Authenticator, authenticator)
252
+ xml['tns'].GetAccountInfo do
253
+ xml['tns'].Authenticator(authenticator)
258
254
  end
259
255
  end
260
256
  end
261
257
 
262
258
  def build_purchase_postage_request(purchase_amount, control_total)
263
259
  build_header do |xml|
264
- xml.tns :PurchasePostage do
265
- xml.tns(:Authenticator, authenticator)
266
- xml.tns(:PurchaseAmount, purchase_amount)
267
- xml.tns(:ControlTotal, control_total)
260
+ xml['tns'].PurchasePostage do
261
+ xml['tns'].Authenticator(authenticator)
262
+ xml['tns'].PurchaseAmount(purchase_amount)
263
+ xml['tns'].ControlTotal(control_total)
268
264
  end
269
265
  end
270
266
  end
271
267
 
272
268
  def build_get_purchase_status(transaction_id)
273
269
  build_header do |xml|
274
- xml.tns :GetPurchaseStatus do
275
- xml.tns(:Authenticator, authenticator)
276
- xml.tns(:TransactionID, transaction_id)
270
+ xml['tns'].GetPurchaseStatus do
271
+ xml['tns'].Authenticator(authenticator)
272
+ xml['tns'].TransactionID(transaction_id)
277
273
  end
278
274
  end
279
275
  end
280
276
 
281
277
  def build_cleanse_address_request(address)
282
278
  build_header do |xml|
283
- xml.tns :CleanseAddress do
284
- xml.tns(:Authenticator, authenticator)
279
+ xml['tns'].CleanseAddress do
280
+ xml['tns'].Authenticator(authenticator)
285
281
  add_address(xml, address)
286
282
  end
287
283
  end
288
284
  end
289
285
 
290
286
  def add_address(xml, address, object_type = :Address)
291
- xml.tns object_type do
292
- xml.tns(:FullName, address.name) unless address.name.blank?
293
- xml.tns(:Company, address.company) unless address.company.blank?
294
- xml.tns(:Address1, address.address1)
295
- xml.tns(:Address2, address.address2) unless address.address2.blank?
296
- xml.tns(:Address3, address.address3) unless address.address3.blank?
297
- xml.tns(:City, address.city) unless address.city.blank?
287
+ xml['tns'].public_send(object_type) do
288
+ xml['tns'].FullName( address.name) unless address.name.blank?
289
+ xml['tns'].Company( address.company) unless address.company.blank?
290
+ xml['tns'].Address1( address.address1)
291
+ xml['tns'].Address2( address.address2) unless address.address2.blank?
292
+ xml['tns'].Address3( address.address3) unless address.address3.blank?
293
+ xml['tns'].City( address.city) unless address.city.blank?
298
294
  if domestic?(address)
299
- xml.tns(:State, address.state) unless address.state.blank?
295
+ xml['tns'].State( address.state) unless address.state.blank?
300
296
 
301
297
  zip = (address.postal_code || '').match(/^(\d{5})?-?(\d{4})?$/)
302
- xml.tns(:ZIPCode, zip[1]) unless zip[1].nil?
303
- xml.tns(:ZIPCodeAddOn, zip[2]) unless zip[2].nil?
298
+ xml['tns'].ZIPCode( zip[1]) unless zip[1].nil?
299
+ xml['tns'].ZIPCodeAddOn(zip[2]) unless zip[2].nil?
304
300
  else
305
- xml.tns(:Province, address.province) unless address.province.blank?
306
- xml.tns(:PostalCode, address.postal_code) unless address.postal_code.blank?
301
+ xml['tns'].Province( address.province) unless address.province.blank?
302
+ xml['tns'].PostalCode( address.postal_code) unless address.postal_code.blank?
307
303
  end
308
- xml.tns(:Country, address.country_code) unless address.country_code.blank?
309
- xml.tns(:PhoneNumber, address.phone) unless address.phone.blank?
304
+ xml['tns'].Country( address.country_code) unless address.country_code.blank?
305
+ xml['tns'].PhoneNumber( address.phone) unless address.phone.blank?
310
306
  end
311
307
  end
312
308
 
313
309
  def build_rate_request(origin, destination, package, options)
314
310
  build_header do |xml|
315
- xml.tns :GetRates do
316
- xml.tns(:Authenticator, authenticator)
311
+ xml['tns'].GetRates do
312
+ xml['tns'].Authenticator(authenticator)
317
313
  add_rate(xml, origin, destination, package, options)
318
314
  end
319
315
  end
@@ -324,21 +320,21 @@ module ActiveShipping
324
320
  options[:insured_value] ||= value
325
321
  options[:declared_value] ||= value if international?(destination)
326
322
 
327
- xml.tns :Rate do
328
- xml.tns(:FromZIPCode, origin.postal_code) unless origin.postal_code.blank?
329
- xml.tns(:ToZIPCode, destination.postal_code) unless destination.postal_code.blank?
330
- xml.tns(:ToCountry, destination.country_code) unless destination.country_code.blank?
331
- xml.tns(:ServiceType, options[:service]) unless options[:service].blank?
332
- xml.tns(:PrintLayout, options[:print_layout]) unless options[:print_layout].blank?
333
- xml.tns(:WeightOz, [package.ounces, 1].max)
334
- xml.tns(:PackageType, options[:package_type] || 'Package')
335
- xml.tns(:Length, package.inches(:length)) if package.inches(:length)
336
- xml.tns(:Width, package.inches(:width)) if package.inches(:width)
337
- xml.tns(:Height, package.inches(:height)) if package.inches(:height)
338
- xml.tns(:ShipDate, options[:ship_date] || Date.today)
339
- xml.tns(:InsuredValue, options[:insured_value]) unless options[:insured_value].blank?
340
- xml.tns(:CODValue, options[:cod_value]) unless options[:cod_value].blank?
341
- xml.tns(:DeclaredValue, options[:declared_value]) unless options[:declared_value].blank?
323
+ xml['tns'].Rate do
324
+ xml['tns'].FromZIPCode( origin.postal_code) unless origin.postal_code.blank?
325
+ xml['tns'].ToZIPCode( destination.postal_code) unless destination.postal_code.blank?
326
+ xml['tns'].ToCountry( destination.country_code) unless destination.country_code.blank?
327
+ xml['tns'].ServiceType( options[:service]) unless options[:service].blank?
328
+ xml['tns'].PrintLayout( options[:print_layout]) unless options[:print_layout].blank?
329
+ xml['tns'].WeightOz( [package.ounces, 1].max)
330
+ xml['tns'].PackageType( options[:package_type] || 'Package')
331
+ xml['tns'].Length( package.inches(:length)) if package.inches(:length)
332
+ xml['tns'].Width( package.inches(:width)) if package.inches(:width)
333
+ xml['tns'].Height( package.inches(:height)) if package.inches(:height)
334
+ xml['tns'].ShipDate( options[:ship_date] || Date.today)
335
+ xml['tns'].InsuredValue( options[:insured_value]) unless options[:insured_value].blank?
336
+ xml['tns'].CODValue( options[:cod_value]) unless options[:cod_value].blank?
337
+ xml['tns'].DeclaredValue( options[:declared_value]) unless options[:declared_value].blank?
342
338
 
343
339
  machinable = if package.options.has_key?(:machinable)
344
340
  package.options[:machinable] ? true : false
@@ -346,54 +342,54 @@ module ActiveShipping
346
342
  USPS.package_machinable?(package)
347
343
  end
348
344
 
349
- xml.tns(:NonMachinable, true) unless machinable
345
+ xml['tns'].NonMachinable( true) unless machinable
350
346
 
351
- xml.tns(:RectangularShaped, !package.cylinder?)
352
- xml.tns(:GEMNotes, options[:gem_notes]) unless options[:gem_notes].blank?
347
+ xml['tns'].RectangularShaped(!package.cylinder?)
348
+ xml['tns'].GEMNotes( options[:gem_notes]) unless options[:gem_notes].blank?
353
349
 
354
350
  add_ons = Array(options[:add_ons])
355
351
  unless add_ons.empty?
356
- xml.tns(:AddOns) do
352
+ xml['tns'].AddOns do
357
353
  add_ons.each do |add_on|
358
- xml.tns(:AddOnV5) do
359
- xml.tns(:AddOnType, add_on)
354
+ xml['tns'].AddOnV5 do
355
+ xml['tns'].AddOnType(add_on)
360
356
  end
361
357
  end
362
358
  end
363
359
  end
364
360
 
365
- xml.tns(:ToState, destination.province) unless destination.province.blank?
361
+ xml['tns'].ToState(destination.province) unless destination.province.blank?
366
362
  end
367
363
  end
368
364
 
369
365
  def build_create_indicium_request(origin, destination, package, line_items, options)
370
366
  build_header do |xml|
371
- xml.tns :CreateIndicium do
372
- xml.tns(:Authenticator, authenticator)
373
- xml.tns(:IntegratorTxID, options[:integrator_tx_id] || SecureRandom::uuid)
367
+ xml['tns'].CreateIndicium do
368
+ xml['tns'].Authenticator( authenticator)
369
+ xml['tns'].IntegratorTxID( options[:integrator_tx_id] || SecureRandom::uuid)
374
370
 
375
371
  add_rate(xml, origin, destination, package, options)
376
372
  add_address(xml, origin, :From)
377
373
  add_address(xml, destination, :To)
378
374
  add_customs(xml, line_items, options) unless options[:content_type].blank?
379
375
 
380
- xml.tns(:SampleOnly, options[:sample_only]) unless options[:sample_only].blank?
381
- xml.tns(:ImageType, options[:image_type]) unless options[:image_type].blank?
382
- xml.tns(:EltronPrinterDPIType, options[:label_resolution]) unless options[:label_resolution].blank?
383
- xml.tns(:memo, options[:memo]) unless options[:memo].blank?
384
- xml.tns(:deliveryNotification, options[:delivery_notification]) unless options[:delivery_notification].blank?
376
+ xml['tns'].SampleOnly( options[:sample_only]) unless options[:sample_only].blank?
377
+ xml['tns'].ImageType( options[:image_type]) unless options[:image_type].blank?
378
+ xml['tns'].EltronPrinterDPIType( options[:label_resolution]) unless options[:label_resolution].blank?
379
+ xml['tns'].memo( options[:memo]) unless options[:memo].blank?
380
+ xml['tns'].deliveryNotification( options[:delivery_notification]) unless options[:delivery_notification].blank?
385
381
 
386
382
  add_shipment_notification(xml, options) unless options[:email].blank?
387
383
 
388
- xml.tns(:horizontalOffset, options[:horizontal_offset]) unless options[:horizontal_offest].blank?
389
- xml.tns(:verticalOffset, options[:vertical_offset]) unless options[:vertical_offest].blank?
390
- xml.tns(:printDensity, options[:print_density]) unless options[:print_density].blank?
391
- xml.tns(:rotationDegrees, options[:rotation]) unless options[:rotation].blank?
392
- xml.tns(:printMemo, options[:print_memo]) unless options[:print_memo].blank?
393
- xml.tns(:printInstructions, options[:print_instructions]) unless options[:print_instructions].blank?
394
- xml.tns(:ReturnImageData, options[:return_image_data]) unless options[:return_image_data].blank?
395
- xml.tns(:InternalTransactionNumber, options[:internal_transaction_number]) unless options[:internal_transaction_number].blank?
396
- xml.tns(:PaperSize, options[:paper_size]) unless options[:paper_size].blank?
384
+ xml['tns'].horizontalOffset( options[:horizontal_offset]) unless options[:horizontal_offest].blank?
385
+ xml['tns'].verticalOffset( options[:vertical_offset]) unless options[:vertical_offest].blank?
386
+ xml['tns'].printDensity( options[:print_density]) unless options[:print_density].blank?
387
+ xml['tns'].rotationDegrees( options[:rotation]) unless options[:rotation].blank?
388
+ xml['tns'].printMemo( options[:print_memo]) unless options[:print_memo].blank?
389
+ xml['tns'].printInstructions( options[:print_instructions]) unless options[:print_instructions].blank?
390
+ xml['tns'].ReturnImageData( options[:return_image_data]) unless options[:return_image_data].blank?
391
+ xml['tns'].InternalTransactionNumber(options[:internal_transaction_number]) unless options[:internal_transaction_number].blank?
392
+ xml['tns'].PaperSize( options[:paper_size]) unless options[:paper_size].blank?
397
393
 
398
394
  add_label_recipient_info(xml, options) unless options[:label_email_address].blank?
399
395
  end
@@ -401,32 +397,32 @@ module ActiveShipping
401
397
  end
402
398
 
403
399
  def add_shipment_notification(xml, options)
404
- xml.tns :ShipmentNotification do
405
- xml.tns(:Email, options[:email])
406
- xml.tns(:CCToAccountHolder, options[:cc_to_account_holder]) unless options[:cc_to_account_holder].blank?
407
- xml.tns(:UseCompanyNameInFromLine, options[:use_company_name_in_from_name]) unless options[:use_company_name_in_from_line].blank?
408
- xml.tns(:UseCompanyNameInSubject, options[:use_company_name_in_subject]) unless options[:use_company_name_in_subject].blank?
400
+ xml['tns'].ShipmentNotification do
401
+ xml['tns'].Email( options[:email])
402
+ xml['tns'].CCToAccountHolder( options[:cc_to_account_holder]) unless options[:cc_to_account_holder].blank?
403
+ xml['tns'].UseCompanyNameInFromLine(options[:use_company_name_in_from_name]) unless options[:use_company_name_in_from_line].blank?
404
+ xml['tns'].UseCompanyNameInSubject( options[:use_company_name_in_subject]) unless options[:use_company_name_in_subject].blank?
409
405
  end
410
406
  end
411
407
 
412
408
  def add_customs(xml, line_items, options)
413
- xml.tns :Customs do
414
- xml.tns(:ContentType, options[:content_type])
415
- xml.tns(:Comments, options[:comments]) unless options[:comments].blank?
416
- xml.tns(:LicenseNumber, options[:license_number]) unless options[:license_number].blank?
417
- xml.tns(:CertificateNumber, options[:certificate_number]) unless options[:certificate_number].blank?
418
- xml.tns(:InvoiceNumber, options[:invoice_number]) unless options[:invoice_number].blank?
419
- xml.tns(:OtherDescribe, options[:other_describe]) unless options[:other_describe].blank?
420
-
421
- xml.tns :CustomsLines do
409
+ xml['tns'].Customs do
410
+ xml['tns'].ContentType( options[:content_type])
411
+ xml['tns'].Comments( options[:comments]) unless options[:comments].blank?
412
+ xml['tns'].LicenseNumber( options[:license_number]) unless options[:license_number].blank?
413
+ xml['tns'].CertificateNumber(options[:certificate_number]) unless options[:certificate_number].blank?
414
+ xml['tns'].InvoiceNumber( options[:invoice_number]) unless options[:invoice_number].blank?
415
+ xml['tns'].OtherDescribe( options[:other_describe]) unless options[:other_describe].blank?
416
+
417
+ xml['tns'].CustomsLines do
422
418
  line_items.each do |customs_line|
423
- xml.tns :CustomsLine do
424
- xml.tns(:Description, customs_line.name)
425
- xml.tns(:Quantity, customs_line.quantity)
426
- xml.tns(:Value, '%.2f' % (customs_line.value.to_f / 100))
427
- xml.tns(:WeightOz, customs_line.ounces) unless customs_line.ounces.blank?
428
- xml.tns(:HSTariffNumber, customs_line.hs_code.tr('.', '')[0..5]) unless customs_line.hs_code.blank?
429
- xml.tns(:CountryOfOrigin, customs_line.options[:country]) unless customs_line.options[:country].blank?
419
+ xml['tns'].CustomsLine do
420
+ xml['tns'].Description( customs_line.name)
421
+ xml['tns'].Quantity( customs_line.quantity)
422
+ xml['tns'].Value( '%.2f' % (customs_line.value.to_f / 100))
423
+ xml['tns'].WeightOz( customs_line.ounces) unless customs_line.ounces.blank?
424
+ xml['tns'].HSTariffNumber( customs_line.hs_code.tr('.', '')[0..5]) unless customs_line.hs_code.blank?
425
+ xml['tns'].CountryOfOrigin(customs_line.options[:country]) unless customs_line.options[:country].blank?
430
426
  end
431
427
  end
432
428
  end
@@ -434,19 +430,19 @@ module ActiveShipping
434
430
  end
435
431
 
436
432
  def add_label_recipient_info(xml, options)
437
- xml.tns :LabelRecipientInfo do
438
- xml.tns(:EmailAddress, options[:label_email_address])
439
- xml.tns(:Name, options[:name]) unless options[:name].blank?
440
- xml.tns(:Note, options[:note]) unless options[:note].blank?
441
- xml.tns(:CopyToOriginator, options[:copy_to_originator]) unless options[:copy_to_originator].blank?
433
+ xml['tns'].LabelRecipientInfo do
434
+ xml['tns'].EmailAddress( options[:label_email_address])
435
+ xml['tns'].Name( options[:name]) unless options[:name].blank?
436
+ xml['tns'].Note( options[:note]) unless options[:note].blank?
437
+ xml['tns'].CopyToOriginator(options[:copy_to_originator]) unless options[:copy_to_originator].blank?
442
438
  end
443
439
  end
444
440
 
445
441
  def build_track_shipment_request(shipment_id, options)
446
442
  build_header do |xml|
447
- xml.tns :TrackShipment do
448
- xml.tns(:Authenticator, authenticator)
449
- xml.tns(options[:stamps_tx_id] ? :StampsTxID : :TrackingNumber, shipment_id)
443
+ xml['tns'].TrackShipment do
444
+ xml['tns'].Authenticator(authenticator)
445
+ xml['tns'].public_send(options[:stamps_tx_id] ? :StampsTxID : :TrackingNumber, shipment_id)
450
446
  end
451
447
  end
452
448
  end
@@ -473,10 +469,11 @@ module ActiveShipping
473
469
  response_options[:request] = last_request
474
470
  response_options[:test] = test_mode?
475
471
 
476
- document = REXML::Document.new(xml)
477
- child_element = document.get_elements('//soap:Body/*').first
472
+ document = Nokogiri.XML(xml)
473
+ child_element = document.at_xpath('/soap:Envelope/soap:Body/*')
478
474
  parse_method = 'parse_' + child_element.name.underscore
479
475
  if respond_to?(parse_method, true)
476
+ child_element.document.remove_namespaces!
480
477
  send(parse_method, child_element, response_options)
481
478
  else
482
479
  Response.new(false, "Unknown response object #{child_element.name}", response_options)
@@ -484,22 +481,22 @@ module ActiveShipping
484
481
  end
485
482
 
486
483
  def parse_fault(fault, response_options)
487
- @authenticator = fault.get_text('detail/authenticator').value if fault.get_text('detail/authenticator')
484
+ @authenticator = fault.at('detail/authenticator').text if fault.at('detail/authenticator')
488
485
 
489
- error_code = if fault.elements['detail/stamps_exception']
490
- fault.elements['detail/stamps_exception'].attributes['code']
491
- elsif fault.elements['detail/sdcerror']
492
- fault.elements['detail/sdcerror'].attributes['code']
486
+ error_code = if fault.at('detail/stamps_exception')
487
+ fault.at('detail/stamps_exception')['code']
488
+ elsif fault.at('detail/sdcerror')
489
+ fault.at('detail/sdcerror')['code']
493
490
  else
494
491
  nil
495
492
  end
496
493
 
497
494
  # Renew the Authenticator if it has expired and retry the request
498
- if error_code and error_code.downcase == '002b0202'
495
+ if error_code && error_code.downcase == '002b0202'
499
496
  request = renew_authenticator(last_request)
500
497
  commit(last_swsim_method, request)
501
498
  else
502
- raise ResponseError.new(fault.get_text('faultstring').to_s)
499
+ raise ResponseError.new(fault.at('faultstring').text)
503
500
  end
504
501
  end
505
502
 
@@ -508,37 +505,37 @@ module ActiveShipping
508
505
  end
509
506
 
510
507
  def parse_authenticator(response)
511
- @authenticator = response.get_text('Authenticator').value
508
+ @authenticator = response.at_xpath('Authenticator').text
512
509
  end
513
510
 
514
511
  def parse_get_account_info_response(account_info_response, response_options)
515
512
  parse_authenticator(account_info_response)
516
513
 
517
- account_info = account_info_response.elements['AccountInfo']
518
- response_options[:customer_id] = account_info.get_text('CustomerID').to_s
519
- response_options[:meter_number] = account_info.get_text('MeterNumber').to_s
520
- response_options[:user_id] = account_info.get_text('UserID').to_s
521
- response_options[:max_postage_balance] = account_info.get_text('MaxPostageBalance').to_s
522
- response_options[:lpo_city] = account_info.get_text('LPOCity').to_s
523
- response_options[:lpo_state] = account_info.get_text('LPOState').to_s
524
- response_options[:lpo_zip] = account_info.get_text('LPOZip').to_s
525
-
526
- postage_balance_node = account_info.elements['PostageBalance']
527
- response_options[:available_postage] = postage_balance_node.get_text('AvailablePostage').to_s
528
- response_options[:control_total] = postage_balance_node.get_text('ControlTotal').to_s
529
-
530
- capabilities_node = account_info.elements['Capabilities']
531
- response_options[:can_print_shipping] = capabilities_node.get_text('CanPrintShipping').to_s == 'true'
532
- response_options[:can_use_cost_codes] = capabilities_node.get_text('CanUseCostCodes').to_s == 'true'
533
- response_options[:can_use_hidden_postage] = capabilities_node.get_text('CanUseHiddenPostage').to_s == 'true'
534
- response_options[:can_purchase_sdc_insurance] = capabilities_node.get_text('CanPurchaseSDCInsurance').to_s == 'true'
535
- response_options[:can_print_memo] = capabilities_node.get_text('CanPrintMemoOnShippingLabel').to_s == 'true'
536
- response_options[:can_print_international] = capabilities_node.get_text('CanPrintInternational').to_s == 'true'
537
- response_options[:can_purchase_postage] = capabilities_node.get_text('CanPurchasePostage').to_s == 'true'
538
- response_options[:can_edit_cost_codes] = capabilities_node.get_text('CanEditCostCodes').to_s == 'true'
539
- response_options[:must_use_cost_codes] = capabilities_node.get_text('MustUseCostCodes').to_s == 'true'
540
- response_options[:can_view_online_reports] = capabilities_node.get_text('CanViewOnlineReports').to_s == 'true'
541
- response_options[:per_print_limit] = capabilities_node.get_text('PerPrintLimit').to_s
514
+ account_info = account_info_response.at('AccountInfo')
515
+ response_options[:customer_id] = account_info.at('CustomerID').text
516
+ response_options[:meter_number] = account_info.at('MeterNumber').text
517
+ response_options[:user_id] = account_info.at('UserID').text
518
+ response_options[:max_postage_balance] = account_info.at('MaxPostageBalance').text
519
+ response_options[:lpo_city] = account_info.at('LPOCity').text
520
+ response_options[:lpo_state] = account_info.at('LPOState').text
521
+ response_options[:lpo_zip] = account_info.at('LPOZip').text
522
+
523
+ postage_balance_node = account_info.at('PostageBalance')
524
+ response_options[:available_postage] = postage_balance_node.at('AvailablePostage').text
525
+ response_options[:control_total] = postage_balance_node.at('ControlTotal').text
526
+
527
+ capabilities_node = account_info.at('Capabilities')
528
+ response_options[:can_print_shipping] = capabilities_node.at('CanPrintShipping').text == 'true'
529
+ response_options[:can_use_cost_codes] = capabilities_node.at('CanUseCostCodes').text == 'true'
530
+ response_options[:can_use_hidden_postage] = capabilities_node.at('CanUseHiddenPostage').text == 'true'
531
+ response_options[:can_purchase_sdc_insurance] = capabilities_node.at('CanPurchaseSDCInsurance').text == 'true'
532
+ response_options[:can_print_memo] = capabilities_node.at('CanPrintMemoOnShippingLabel').text == 'true'
533
+ response_options[:can_print_international] = capabilities_node.at('CanPrintInternational').text == 'true'
534
+ response_options[:can_purchase_postage] = capabilities_node.at('CanPurchasePostage').text == 'true'
535
+ response_options[:can_edit_cost_codes] = capabilities_node.at('CanEditCostCodes').text == 'true'
536
+ response_options[:must_use_cost_codes] = capabilities_node.at('MustUseCostCodes').text == 'true'
537
+ response_options[:can_view_online_reports] = capabilities_node.at('CanViewOnlineReports').text == 'true'
538
+ response_options[:per_print_limit] = capabilities_node.at('PerPrintLimit').text
542
539
 
543
540
  StampsAccountInfoResponse.new(true, '', {}, response_options)
544
541
  end
@@ -546,13 +543,13 @@ module ActiveShipping
546
543
  def parse_purchase_postage_response(postage, response_options)
547
544
  parse_authenticator(postage)
548
545
 
549
- response_options[:purchase_status] = postage.get_text('PurchaseStatus').to_s
550
- response_options[:rejection_reason] = postage.get_text('RejectionReason').to_s if postage.get_text('RejectionReason')
551
- response_options[:transaction_id] = postage.get_text('TransactionID').to_s if postage.get_text('TransactionID')
546
+ response_options[:purchase_status] = postage.at('PurchaseStatus').text
547
+ response_options[:rejection_reason] = parse_content(postage, 'RejectionReason')
548
+ response_options[:transaction_id] = parse_content(postage, 'TransactionID')
552
549
 
553
- balance = postage.elements['PostageBalance']
554
- response_options[:available_postage] = balance.get_text('AvailablePostage').to_s
555
- response_options[:control_total] = balance.get_text('ControlTotal').to_s if balance.get_text('ControlTotal')
550
+ balance = postage.at('PostageBalance')
551
+ response_options[:available_postage] = balance.at('AvailablePostage').text
552
+ response_options[:control_total] = parse_content(balance, 'ControlTotal')
556
553
 
557
554
  StampsPurchasePostageResponse.new(true, '', {}, response_options)
558
555
  end
@@ -561,19 +558,18 @@ module ActiveShipping
561
558
  def parse_cleanse_address_response(cleanse_address, response_options)
562
559
  parse_authenticator(cleanse_address)
563
560
 
564
- response_options[:address_match] = cleanse_address.get_text('AddressMatch').to_s == 'true'
565
- response_options[:city_state_zip_ok] = cleanse_address.get_text('CityStateZipOK').to_s == 'true'
561
+ response_options[:address_match] = cleanse_address.at('AddressMatch').text == 'true'
562
+ response_options[:city_state_zip_ok] = cleanse_address.at('CityStateZipOK').text == 'true'
566
563
 
567
- address = cleanse_address.elements['Address']
568
- response_options[:cleanse_hash] = address.get_text('CleanseHash').to_s if address.get_text('CleanseHash')
569
- response_options[:override_hash] = address.get_text('OverrideHash').to_s if address.get_text('OverrideHash')
564
+ address = cleanse_address.at('Address')
565
+ response_options[:cleanse_hash] = parse_content(address, 'CleanseHash')
566
+ response_options[:override_hash] = parse_content(address, 'OverrideHash')
570
567
 
571
- address_node = cleanse_address.elements['Address']
572
- indicator_node = cleanse_address.get_text('ResidentialDeliveryIndicatorType').to_s
573
- po_box_node = cleanse_address.get_text('IsPOBox').to_s
574
- response_options[:address] = parse_address(address_node, indicator_node, po_box_node)
568
+ indicator_node = cleanse_address.at('ResidentialDeliveryIndicatorType')
569
+ po_box_node = cleanse_address.at('IsPOBox')
570
+ response_options[:address] = parse_address(address, indicator_node, po_box_node)
575
571
 
576
- candidate_addresses = cleanse_address.get_elements('CandidateAddresses/Address')
572
+ candidate_addresses = cleanse_address.xpath('CandidateAddresses/Address')
577
573
  response_options[:candidate_addresses] = candidate_addresses.map do |candidate_address|
578
574
  parse_address(candidate_address)
579
575
  end
@@ -584,23 +580,23 @@ module ActiveShipping
584
580
  def parse_address(address_node, residential_indicator_node = nil, po_box_node = nil)
585
581
  address = {}
586
582
 
587
- address[:name] = address_node.get_text('FullName').to_s if address_node.get_text('FullName')
588
- address[:company] = address_node.get_text('Company').to_s if address_node.get_text('Company')
589
- address[:address1] = address_node.get_text('Address1').to_s if address_node.get_text('Address1')
590
- address[:address2] = address_node.get_text('Address2').to_s if address_node.get_text('Address2')
591
- address[:address3] = address_node.get_text('Address3').to_s if address_node.get_text('Address3')
592
- address[:city] = address_node.get_text('City').to_s if address_node.get_text('City')
593
- address[:country] = address_node.get_text('Country').to_s if address_node.get_text('Country')
594
- address[:phone] = address_node.get_text('PhoneNumber').to_s if address_node.get_text('PhoneNumber')
583
+ address[:name] = parse_content(address_node, 'FullName')
584
+ address[:company] = parse_content(address_node, 'Company')
585
+ address[:address1] = parse_content(address_node, 'Address1')
586
+ address[:address2] = parse_content(address_node, 'Address2')
587
+ address[:address3] = parse_content(address_node, 'Address3')
588
+ address[:city] = parse_content(address_node, 'City')
589
+ address[:country] = parse_content(address_node, 'Country')
590
+ address[:phone] = parse_content(address_node, 'PhoneNumber')
595
591
 
596
592
  if address[:country] == 'US' || address[:country].nil?
597
- address[:state] = address_node.get_text('State').to_s if address_node.get_text('State')
593
+ address[:state] = parse_content(address_node, 'State')
598
594
 
599
- address[:postal_code] = address_node.get_text('ZIPCode').to_s if address_node.get_text('ZIPCode')
600
- address[:postal_code] += '-' + address_node.get_text('ZIPCodeAddOn').to_s if address_node.get_text('ZIPCodeAddOn')
595
+ address[:postal_code] = parse_content(address_node, 'ZIPCode')
596
+ address[:postal_code] += '-' + parse_content(address_node, 'ZIPCodeAddOn')
601
597
  else
602
- address[:province] = address_node.get_text('Province').to_s if address_node.get_text('Province')
603
- address[:postal_code] = address_node.get_text('PostalCode').to_s if address_node.get_text('PostalCode')
598
+ address[:province] = parse_content(address_node, 'Province')
599
+ address[:postal_code] = parse_content(address_node, 'PostalCode')
604
600
  end
605
601
 
606
602
  address[:address_type] = if residential_indicator_node == 'Yes'
@@ -619,7 +615,7 @@ module ActiveShipping
619
615
  def parse_get_rates_response(get_rates, response_options)
620
616
  parse_authenticator(get_rates)
621
617
 
622
- response_options[:estimates] = get_rates.get_elements('Rates/Rate').map do |rate|
618
+ response_options[:estimates] = get_rates.xpath('Rates/Rate').map do |rate|
623
619
  parse_rate(rate)
624
620
  end
625
621
 
@@ -629,33 +625,33 @@ module ActiveShipping
629
625
  def parse_rate(rate)
630
626
  rate_options = {}
631
627
 
632
- origin = Location.new(zip: rate.get_text('FromZIPCode').to_s)
628
+ origin = Location.new(zip: rate.at('FromZIPCode').text)
633
629
 
634
630
  location_values = {}
635
- location_values[:zip] = rate.get_text('ToZIPCode').to_s if rate.get_text('ToZIPCode')
636
- location_values[:country] = rate.get_text('ToCountry').to_s if rate.get_text('ToCountry')
631
+ location_values[:zip] = parse_content(rate, 'ToZIPCode')
632
+ location_values[:country] = parse_content(rate, 'ToCountry')
637
633
  destination = Location.new(location_values)
638
634
 
639
- service_name = SERVICE_TYPES[rate.get_text('ServiceType').to_s]
635
+ service_name = SERVICE_TYPES[rate.at('ServiceType').text]
640
636
 
641
- rate_options[:service_code] = rate.get_text('ServiceType').to_s
637
+ rate_options[:service_code] = rate.at('ServiceType').text
642
638
  rate_options[:currency] = 'USD'
643
- rate_options[:shipping_date] = Date.parse(rate.get_text('ShipDate').to_s)
639
+ rate_options[:shipping_date] = Date.parse(rate.at('ShipDate').text)
644
640
 
645
- if delivery_days = rate.get_text('DeliverDays')
646
- delivery_days = delivery_days.to_s.split('-')
641
+ if delivery_days = rate.at('DeliverDays')
642
+ delivery_days = delivery_days.text.split('-')
647
643
  rate_options[:delivery_range] = delivery_days.map { |day| rate_options[:shipping_date] + day.to_i.days }
648
644
  end
649
645
 
650
- rate_options[:total_price] = rate.get_text('Amount').to_s
646
+ rate_options[:total_price] = rate.at('Amount').text
651
647
 
652
648
  rate_options[:add_ons] = parse_add_ons(rate)
653
649
  rate_options[:packages] = parse_package(rate)
654
650
 
655
651
  add_ons = rate_options[:add_ons]
656
- if add_ons['SC-A-INS'] and add_ons['SC-A-INS'][:amount]
652
+ if add_ons['SC-A-INS'] && add_ons['SC-A-INS'][:amount]
657
653
  rate_options[:insurance_price] = add_ons['SC-A-INS'][:amount]
658
- elsif add_ons['US-A-INS'] and add_ons['US-A-INS'][:amount]
654
+ elsif add_ons['US-A-INS'] && add_ons['US-A-INS'][:amount]
659
655
  rate_options[:insurance_price] = add_ons['US-A-INS'][:amount]
660
656
  end
661
657
 
@@ -664,14 +660,14 @@ module ActiveShipping
664
660
 
665
661
  def parse_add_ons(rate)
666
662
  add_ons = {}
667
- rate.get_elements('AddOns/AddOnV5').each do |add_on|
668
- add_on_type = add_on.get_text('AddOnType').to_s
663
+ rate.xpath('AddOns/AddOnV5').each do |add_on|
664
+ add_on_type = add_on.at('AddOnType').text
669
665
 
670
666
  add_on_details = {}
671
- add_on_details[:missing_data] = add_on.get_text('MissingData').to_s if add_on.get_text('MissingData')
672
- add_on_details[:amount] = add_on.get_text('Amount').to_s if add_on.get_text('Amount')
667
+ add_on_details[:missing_data] = parse_content(add_on, 'MissingData') if add_on.at('MissingData')
668
+ add_on_details[:amount] = parse_content(add_on, 'Amount') if add_on.at('Amount')
673
669
 
674
- prohibited_with = add_on.get_elements('ProhibitedWithAnyOf/AddOnTypeV5').map(&:text)
670
+ prohibited_with = add_on.xpath('ProhibitedWithAnyOf/AddOnTypeV5').map(&:text)
675
671
  add_on_details[:prohibited_with] = prohibited_with unless prohibited_with.empty?
676
672
 
677
673
  add_ons[add_on_type] = add_on_details
@@ -681,17 +677,17 @@ module ActiveShipping
681
677
  end
682
678
 
683
679
  def parse_package(rate)
684
- weight = rate.get_text('WeightOz').to_s.to_f
680
+ weight = rate.at('WeightOz').text.to_f
685
681
 
686
682
  dimensions = %w(Length Width Height).map do |dim|
687
- rate.get_text(dim) ? rate.get_text(dim).to_s.to_f : nil
683
+ rate.at(dim) ? rate.at(dim).text.to_f : nil
688
684
  end
689
685
  dimensions.compact!
690
686
 
691
687
  package_options = { units: :imperial }
692
688
 
693
- if value = rate.get_text('InsuredValue') || rate.get_text('DeclaredValue')
694
- package_options[:value] = value.to_s.to_f
689
+ if value = rate.at('InsuredValue') || rate.at('DeclaredValue')
690
+ package_options[:value] = value.text.to_f
695
691
  package_options[:currency] = 'USD'
696
692
  end
697
693
 
@@ -701,14 +697,14 @@ module ActiveShipping
701
697
  def parse_create_indicium_response(indicium, response_options)
702
698
  parse_authenticator(indicium)
703
699
 
704
- response_options[:shipping_id] = indicium.get_text('IntegratorTxID').to_s
705
- response_options[:tracking_number] = indicium.get_text('TrackingNumber').to_s if indicium.get_text('TrackingNumber')
706
- response_options[:stamps_tx_id] = indicium.get_text('StampsTxID').to_s
707
- response_options[:label_url] = indicium.get_text('URL').to_s if indicium.get_text('URL')
708
- response_options[:available_postage] = indicium.get_text('PostageBalance/AvailablePostage').to_s
709
- response_options[:control_total] = indicium.get_text('PostageBalance/ControlTotal').to_s
710
- response_options[:image_data] = Base64.decode64(indicium.get_text('ImageData/base64Binary').to_s) if indicium.get_text('ImageData/base64Binary')
711
- response_options[:rate] = parse_rate(indicium.elements['Rate'])
700
+ response_options[:shipping_id] = indicium.at('IntegratorTxID').text
701
+ response_options[:tracking_number] = parse_content(indicium, 'TrackingNumber')
702
+ response_options[:stamps_tx_id] = indicium.at('StampsTxID').text
703
+ response_options[:label_url] = parse_content(indicium, 'URL')
704
+ response_options[:available_postage] = indicium.at('PostageBalance/AvailablePostage').text
705
+ response_options[:control_total] = indicium.at('PostageBalance/ControlTotal').text
706
+ response_options[:image_data] = Base64.decode64(indicium.at('ImageData/base64Binary').text) if indicium.at('ImageData/base64Binary')
707
+ response_options[:rate] = parse_rate(indicium.at('Rate'))
712
708
 
713
709
  StampsShippingResponse.new(true, '', {}, response_options)
714
710
  end
@@ -718,27 +714,27 @@ module ActiveShipping
718
714
 
719
715
  response_options[:carrier] = @@name
720
716
 
721
- shipment_events = track_shipment.get_elements('TrackingEvents/TrackingEvent').map do |event|
717
+ shipment_events = track_shipment.xpath('TrackingEvents/TrackingEvent').map do |event|
722
718
  unless response_options[:status]
723
- response_options[:status_code] = event.get_text('TrackingEventType').to_s
719
+ response_options[:status_code] = event.at('TrackingEventType').text
724
720
  response_options[:status] = response_options[:status_code].underscore.to_sym
725
721
  end
726
722
 
727
- response_options[:delivery_signature] = event.get_text('SignedBy').to_s if event.get_text('SignedBy')
723
+ response_options[:delivery_signature] = parse_content(event, 'SignedBy')
728
724
 
729
- description = event.get_text('Event').to_s
725
+ description = event.at('Event').text
730
726
 
731
- timestamp = event.get_text('Timestamp').to_s
727
+ timestamp = event.at('Timestamp').text
732
728
  date, time = timestamp.split('T')
733
729
  year, month, day = date.split('-')
734
730
  hour, minute, second = time.split(':')
735
731
  zoneless_time = Time.utc(year, month, day, hour, minute, second)
736
732
 
737
733
  location = Location.new(
738
- city: event.get_text('City').to_s,
739
- state: event.get_text('State').to_s,
740
- zip: event.get_text('Zip').to_s,
741
- country: event.get_text('Country').to_s
734
+ city: event.at('City').text,
735
+ state: event.at('State').text,
736
+ zip: event.at('Zip').text,
737
+ country: event.at('Country').text
742
738
  )
743
739
 
744
740
  ShipmentEvent.new(description, zoneless_time, location)
@@ -749,6 +745,11 @@ module ActiveShipping
749
745
 
750
746
  TrackingResponse.new(true, '', {}, response_options)
751
747
  end
748
+
749
+ def parse_content(node, child)
750
+ return unless node.at(child) && node.at(child).text != ''
751
+ node.at(child).text
752
+ end
752
753
  end
753
754
 
754
755
  class StampsAccountInfoResponse < Response
@@ -840,8 +841,6 @@ module ActiveShipping
840
841
  class StampsShippingResponse < ShippingResponse
841
842
  include ActiveUtils::PostsData
842
843
 
843
- self.ssl_version = :SSLv3
844
-
845
844
  attr_reader :rate, :stamps_tx_id, :label_url, :available_postage, :control_total
846
845
 
847
846
  def initialize(success, message, params = {}, options = {})