active_shipping 1.0.0.pre2 → 1.0.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +12 -0
  5. data/.travis.yml +14 -0
  6. data/.yardopts +14 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.activesupport32 +4 -0
  9. data/Gemfile.activesupport40 +4 -0
  10. data/Gemfile.activesupport41 +4 -0
  11. data/Gemfile.activesupport42 +4 -0
  12. data/Rakefile +23 -0
  13. data/active_shipping.gemspec +32 -0
  14. data/lib/active_shipping.rb +1 -1
  15. data/lib/active_shipping/carrier.rb +1 -1
  16. data/lib/active_shipping/carriers/shipwire.rb +32 -35
  17. data/lib/active_shipping/carriers/ups.rb +2 -2
  18. data/lib/active_shipping/carriers/usps.rb +103 -101
  19. data/lib/active_shipping/label_response.rb +1 -1
  20. data/lib/active_shipping/rate_estimate.rb +76 -15
  21. data/lib/active_shipping/rate_response.rb +20 -0
  22. data/lib/active_shipping/response.rb +17 -0
  23. data/lib/active_shipping/shipment_packer.rb +2 -2
  24. data/lib/active_shipping/shipping_response.rb +24 -2
  25. data/lib/active_shipping/tracking_response.rb +65 -17
  26. data/lib/active_shipping/version.rb +1 -1
  27. data/shipit.rubygems.yml +1 -0
  28. data/test/credentials.yml +47 -0
  29. data/test/fixtures/files/label1.pdf +0 -0
  30. data/test/fixtures/files/ups-shipping-label.gif +0 -0
  31. data/test/fixtures/json/newzealandpost/domestic_book.json +1 -0
  32. data/test/fixtures/json/newzealandpost/domestic_default.json +1 -0
  33. data/test/fixtures/json/newzealandpost/domestic_error.json +1 -0
  34. data/test/fixtures/json/newzealandpost/domestic_poster.json +1 -0
  35. data/test/fixtures/json/newzealandpost/domestic_small_half_pound.json +1 -0
  36. data/test/fixtures/json/newzealandpost/international_book.json +1 -0
  37. data/test/fixtures/json/newzealandpost/international_new_zealand_wii.json +1 -0
  38. data/test/fixtures/json/newzealandpost/international_small_half_pound.json +1 -0
  39. data/test/fixtures/json/newzealandpost/international_wii.json +1 -0
  40. data/test/fixtures/xml/canadapost/example_request.xml +25 -0
  41. data/test/fixtures/xml/canadapost/example_response.xml +130 -0
  42. data/test/fixtures/xml/canadapost/example_response_error.xml +16 -0
  43. data/test/fixtures/xml/canadapost/example_response_french.xml +122 -0
  44. data/test/fixtures/xml/canadapost/example_response_with_nil_value.xml +164 -0
  45. data/test/fixtures/xml/canadapost/example_response_with_postal_outlet.xml +155 -0
  46. data/test/fixtures/xml/canadapost/example_response_with_postal_outlet_french.xml +274 -0
  47. data/test/fixtures/xml/canadapost/example_response_with_strange_delivery_date.xml +130 -0
  48. data/test/fixtures/xml/canadapost_pws/dnc_tracking_details_en.xml +112 -0
  49. data/test/fixtures/xml/canadapost_pws/merchant_details_error.xml +7 -0
  50. data/test/fixtures/xml/canadapost_pws/merchant_details_response.xml +7 -0
  51. data/test/fixtures/xml/canadapost_pws/option_response.xml +13 -0
  52. data/test/fixtures/xml/canadapost_pws/option_response_no_conflicts.xml +7 -0
  53. data/test/fixtures/xml/canadapost_pws/rates_info.xml +190 -0
  54. data/test/fixtures/xml/canadapost_pws/rates_info_error.xml +7 -0
  55. data/test/fixtures/xml/canadapost_pws/receipt_response.xml +42 -0
  56. data/test/fixtures/xml/canadapost_pws/receipt_response_no_priced_options.xml +36 -0
  57. data/test/fixtures/xml/canadapost_pws/register_token_error.xml +7 -0
  58. data/test/fixtures/xml/canadapost_pws/register_token_response.xml +3 -0
  59. data/test/fixtures/xml/canadapost_pws/service_options_response.xml +42 -0
  60. data/test/fixtures/xml/canadapost_pws/services_error.xml +6 -0
  61. data/test/fixtures/xml/canadapost_pws/services_response.xml +32 -0
  62. data/test/fixtures/xml/canadapost_pws/shipment_domestic.xml +69 -0
  63. data/test/fixtures/xml/canadapost_pws/shipment_response.xml +20 -0
  64. data/test/fixtures/xml/canadapost_pws/shipment_us.xml +69 -0
  65. data/test/fixtures/xml/canadapost_pws/tracking_details_en.xml +152 -0
  66. data/test/fixtures/xml/canadapost_pws/tracking_details_en_error.xml +7 -0
  67. data/test/fixtures/xml/canadapost_pws/tracking_details_en_undelivered.xml +116 -0
  68. data/test/fixtures/xml/canadapost_pws/tracking_details_fr.xml +156 -0
  69. data/test/fixtures/xml/canadapost_pws/tracking_details_no_expected_delivery_date.xml +40 -0
  70. data/test/fixtures/xml/fedex/freight_rate_request.xml +82 -0
  71. data/test/fixtures/xml/fedex/freight_rate_response.xml +506 -0
  72. data/test/fixtures/xml/fedex/invalid_fedex_reply.xml +27 -0
  73. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_commercial_rate_request.xml +79 -0
  74. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_request.xml +80 -0
  75. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_response.xml +214 -0
  76. data/test/fixtures/xml/fedex/raterequest_reply.xml +213 -0
  77. data/test/fixtures/xml/fedex/reply_without_notifications.xml +185 -0
  78. data/test/fixtures/xml/fedex/tracking_request.xml +27 -0
  79. data/test/fixtures/xml/fedex/tracking_response.xml +151 -0
  80. data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +76 -0
  81. data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +139 -0
  82. data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +150 -0
  83. data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +95 -0
  84. data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +71 -0
  85. data/test/fixtures/xml/fedex/unknown_fedex_document_reply.xml +3 -0
  86. data/test/fixtures/xml/kunaki/invalid_state_response.xml +3 -0
  87. data/test/fixtures/xml/kunaki/no_valid_items_response.xml +3 -0
  88. data/test/fixtures/xml/kunaki/successful_rates_response.xml +3 -0
  89. data/test/fixtures/xml/kunaki/unsuccessful_rates_response.xml +9 -0
  90. data/test/fixtures/xml/shipwire/international_rates_response.xml +17 -0
  91. data/test/fixtures/xml/shipwire/new_carrier_rate_response.xml +18 -0
  92. data/test/fixtures/xml/shipwire/no_rates_response.xml +7 -0
  93. data/test/fixtures/xml/shipwire/rates_response.xml +36 -0
  94. data/test/fixtures/xml/shipwire/rates_response_no_estimate.xml +14 -0
  95. data/test/fixtures/xml/stamps/authenticate_user_request.xml +15 -0
  96. data/test/fixtures/xml/stamps/authenticate_user_response.xml +10 -0
  97. data/test/fixtures/xml/stamps/cleanse_address_request.xml +19 -0
  98. data/test/fixtures/xml/stamps/cleanse_address_response.xml +27 -0
  99. data/test/fixtures/xml/stamps/create_indicium_request.xml +69 -0
  100. data/test/fixtures/xml/stamps/create_indicium_response.xml +40 -0
  101. data/test/fixtures/xml/stamps/expired_authenticator_response.xml +15 -0
  102. data/test/fixtures/xml/stamps/get_account_info_request.xml +11 -0
  103. data/test/fixtures/xml/stamps/get_account_info_response.xml +36 -0
  104. data/test/fixtures/xml/stamps/get_purchase_status_request.xml +12 -0
  105. data/test/fixtures/xml/stamps/get_purchase_status_response.xml +16 -0
  106. data/test/fixtures/xml/stamps/get_rates_request.xml +19 -0
  107. data/test/fixtures/xml/stamps/get_rates_response.xml +351 -0
  108. data/test/fixtures/xml/stamps/purchase_postage_request.xml +13 -0
  109. data/test/fixtures/xml/stamps/purchase_postage_response.xml +17 -0
  110. data/test/fixtures/xml/stamps/track_shipment_request.xml +12 -0
  111. data/test/fixtures/xml/stamps/track_shipment_response.xml +45 -0
  112. data/test/fixtures/xml/ups/delivered_shipment_with_refund.xml +290 -0
  113. data/test/fixtures/xml/ups/delivered_shipment_without_events_tracking_response.xml +62 -0
  114. data/test/fixtures/xml/ups/example_tracking_response.xml +53 -0
  115. data/test/fixtures/xml/ups/in_transit_shipment.xml +183 -0
  116. data/test/fixtures/xml/ups/out_for_delivery_shipment.xml +165 -0
  117. data/test/fixtures/xml/ups/shipment_accept_response.xml +42 -0
  118. data/test/fixtures/xml/ups/shipment_confirm_response.xml +33 -0
  119. data/test/fixtures/xml/ups/shipment_from_tiger_direct.xml +222 -0
  120. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +1 -0
  121. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response_with_insured.xml +289 -0
  122. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_with_origin_account_response.xml +311 -0
  123. data/test/fixtures/xml/ups/triple_accept_response.xml +72 -0
  124. data/test/fixtures/xml/ups/triple_confirm_response.xml +32 -0
  125. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_base_rate_response.xml +2 -0
  126. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_plus_rate_response.xml +258 -0
  127. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_rate_response.xml +108 -0
  128. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_base_rate_response.xml +84 -0
  129. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_plus_rate_response.xml +212 -0
  130. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_rate_response.xml +230 -0
  131. data/test/fixtures/xml/usps/delivered_tracking_response.xml +11 -0
  132. data/test/fixtures/xml/usps/first_class_packages_with_invalid_mail_type_response.xml +12 -0
  133. data/test/fixtures/xml/usps/first_class_packages_with_mail_type_response.xml +16 -0
  134. data/test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml +12 -0
  135. data/test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml +2 -0
  136. data/test/fixtures/xml/usps/tracking_request.xml +3 -0
  137. data/test/fixtures/xml/usps/tracking_response.xml +13 -0
  138. data/test/fixtures/xml/usps/tracking_response_failure.xml +3 -0
  139. data/test/fixtures/xml/usps/tracking_response_not_available.xml +12 -0
  140. data/test/fixtures/xml/usps/tracking_response_test_error.xml +8 -0
  141. data/test/fixtures/xml/usps/us_rate_request.xml +18 -0
  142. data/test/fixtures/xml/usps/world_rate_request_with_value.xml +20 -0
  143. data/test/fixtures/xml/usps/world_rate_request_without_value.xml +20 -0
  144. data/test/remote/canada_post_pws_platform_test.rb +246 -0
  145. data/test/remote/canada_post_pws_test.rb +171 -0
  146. data/test/remote/canada_post_test.rb +53 -0
  147. data/test/remote/fedex_test.rb +216 -0
  148. data/test/remote/kunaki_test.rb +36 -0
  149. data/test/remote/new_zealand_post_test.rb +147 -0
  150. data/test/remote/shipwire_test.rb +82 -0
  151. data/test/remote/stamps_test.rb +394 -0
  152. data/test/remote/ups_test.rb +257 -0
  153. data/test/remote/usps_test.rb +235 -0
  154. data/test/test_helper.rb +220 -0
  155. data/test/unit/carriers/benchmark_test.rb +18 -0
  156. data/test/unit/carriers/canada_post_pws_rating_test.rb +349 -0
  157. data/test/unit/carriers/canada_post_pws_register_test.rb +75 -0
  158. data/test/unit/carriers/canada_post_pws_shipping_test.rb +244 -0
  159. data/test/unit/carriers/canada_post_pws_tracking_test.rb +154 -0
  160. data/test/unit/carriers/canada_post_test.rb +145 -0
  161. data/test/unit/carriers/fedex_test.rb +408 -0
  162. data/test/unit/carriers/kunaki_test.rb +52 -0
  163. data/test/unit/carriers/new_zealand_post_test.rb +174 -0
  164. data/test/unit/carriers/shipwire_test.rb +187 -0
  165. data/test/unit/carriers/stamps_test.rb +241 -0
  166. data/test/unit/carriers/ups_test.rb +311 -0
  167. data/test/unit/carriers/usps_test.rb +484 -0
  168. data/test/unit/carriers_test.rb +17 -0
  169. data/test/unit/label_response_test.rb +15 -0
  170. data/test/unit/location_test.rb +138 -0
  171. data/test/unit/package_test.rb +68 -0
  172. data/test/unit/rate_estimate_test.rb +34 -0
  173. data/test/unit/response_test.rb +14 -0
  174. data/test/unit/shipment_packer_test.rb +174 -0
  175. metadata +331 -35
  176. metadata.gz.sig +0 -0
  177. data/lib/vendor/quantified/MIT-LICENSE +0 -22
  178. data/lib/vendor/quantified/README.markdown +0 -49
  179. data/lib/vendor/quantified/Rakefile +0 -13
  180. data/lib/vendor/quantified/init.rb +0 -0
  181. data/lib/vendor/quantified/lib/quantified.rb +0 -6
  182. data/lib/vendor/quantified/lib/quantified/attribute.rb +0 -211
  183. data/lib/vendor/quantified/lib/quantified/length.rb +0 -20
  184. data/lib/vendor/quantified/lib/quantified/mass.rb +0 -19
  185. data/lib/vendor/quantified/test/length_test.rb +0 -94
  186. data/lib/vendor/quantified/test/mass_test.rb +0 -96
  187. data/lib/vendor/quantified/test/test_helper.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73732fbdf2ce8048403b8039027c688126ba5233
4
- data.tar.gz: 6f16c0d09ecd5392008b5cd6f20a401a0631d8b3
3
+ metadata.gz: 893b67f5102c88b62a062968374250a09ac2e266
4
+ data.tar.gz: 4438e27c1d8d4f70abbcbdd59ba5a33f679d0a3d
5
5
  SHA512:
6
- metadata.gz: 2f007d4d627a81ba03b03fc638520e1576087fec52ac0989046720505c8eb550a07ff3501b7e43966624f53d8e0ee6e161d06752c8edeafd8106b91935ff2d42
7
- data.tar.gz: e56fe6610709fbdd802a3e3bcd573ac3465d97ed3e79a879ea4708520fb43a9658725af24f13a20bd6de0920727db4bbcc5f769ca6a2c61655b106d9c6997874
6
+ metadata.gz: 4f0286ca3df78d1803da401396e755254dda1d4792384b4f454ce272712781469d98b6cfc2d7d10fb4b7658ba6e84da452745b99562d7fc8743932f4d326ef74
7
+ data.tar.gz: 0743b1728a552e6bafbc6fd4a3ec76bef05a6ed844219d9915bbbd77bbdadc450dc315b8f52f4552621d362ac3f43c3e750cc2a45b773bc7f2bc1d131df03029
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .DS_Store
2
+ test.xml
3
+ sample.rb
4
+ *.orig
5
+ pkg
6
+ .dotest
7
+ Gemfile*.lock
8
+ .rvmrc
9
+ test/fixtures/live_credentials.yml
10
+ .rbenv-version
11
+ .yardoc/
12
+ doc/
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ script: bundle exec rake
3
+ sudo: false
4
+
5
+ rvm:
6
+ - "2.0"
7
+ - "2.1"
8
+ - "2.2"
9
+
10
+ gemfile:
11
+ - Gemfile.activesupport32
12
+ - Gemfile.activesupport40
13
+ - Gemfile.activesupport41
14
+ - Gemfile.activesupport42
data/.yardopts ADDED
@@ -0,0 +1,14 @@
1
+ --readme README.md
2
+ --title 'ActiveShipping API documentation'
3
+ --charset utf-8
4
+ --hide-void-return
5
+ --protected
6
+ --no-private
7
+ --markup markdown
8
+ --markup-provider redcarpet
9
+ --exclude /lib/vendor/xml_node/
10
+ -
11
+ README.md
12
+ CHANGELOG.md
13
+ CONTRIBUTING.md
14
+ MIT-LICENSE
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'activesupport', '~> 3.2.0'
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'activesupport', '~> 4.0.0'
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'activesupport', '~> 4.1.0'
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'activesupport', '~> 4.2.0'
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+ Rake::TestTask.new(:units) do |t|
7
+ t.libs << "test"
8
+ t.pattern = 'test/unit/**/*_test.rb'
9
+ t.verbose = true
10
+ end
11
+
12
+ Rake::TestTask.new(:remote) do |t|
13
+ t.libs << "test"
14
+ t.pattern = 'test/remote/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+ end
18
+
19
+ desc "Default Task"
20
+ task :default => 'test:units'
21
+
22
+ desc "Run the unit and remote tests"
23
+ task :test => %w(test:units test:remote)
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'active_shipping/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "active_shipping"
8
+ s.version = ActiveShipping::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["James MacAulay", "Tobi Lutke", "Cody Fauser", "Jimmy Baker"]
11
+ s.email = ["james@shopify.com"]
12
+ s.homepage = "http://github.com/shopify/active_shipping"
13
+ s.summary = "Simple shipping abstraction library"
14
+ s.description = "Get rates and tracking info from various shipping carriers. Extracted from Shopify."
15
+ s.license = 'MIT'
16
+
17
+ s.add_dependency('quantified', '~> 1.0')
18
+ s.add_dependency('activesupport', '>= 3.2', '< 5.0.0')
19
+ s.add_dependency('active_utils', '~> 3.0.0')
20
+ s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
21
+ s.add_dependency('nokogiri', '>= 1.6')
22
+
23
+ s.add_development_dependency('minitest')
24
+ s.add_development_dependency('rake')
25
+ s.add_development_dependency('mocha', '~> 1')
26
+ s.add_development_dependency('timecop')
27
+
28
+ s.files = `git ls-files`.split($/)
29
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
30
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
31
+ s.require_path = ['lib']
32
+ end
@@ -29,8 +29,8 @@ require 'active_utils'
29
29
 
30
30
  require 'rexml/document'
31
31
  require 'nokogiri'
32
+ require 'quantified'
32
33
 
33
- require 'vendor/quantified/lib/quantified'
34
34
  require 'vendor/xml_node/lib/xml_node'
35
35
 
36
36
  require 'active_shipping/response'
@@ -136,7 +136,7 @@ module ActiveShipping
136
136
 
137
137
  # Calculates a timestamp that corresponds a given number if business days in the future
138
138
  #
139
- # @param [Integer] The number of business days from now.
139
+ # @param days [Integer] The number of business days from now.
140
140
  # @return [DateTime] A timestamp, the provided number of business days in the future.
141
141
  def timestamp_from_business_day(days)
142
142
  return unless days
@@ -1,5 +1,3 @@
1
- require 'builder'
2
-
3
1
  module ActiveShipping
4
2
  class Shipwire < Carrier
5
3
  self.retry_safe = true
@@ -47,24 +45,23 @@ module ActiveShipping
47
45
  end
48
46
 
49
47
  def build_request(destination, options)
50
- xml = Builder::XmlMarkup.new
51
- xml.instruct!
52
- xml.declare! :DOCTYPE, :RateRequest, :SYSTEM, SCHEMA_URL
53
- xml.tag! 'RateRequest' do
54
- add_credentials(xml)
55
- add_order(xml, destination, options)
56
- end
57
- xml.target!
48
+ Nokogiri::XML::Builder.new do |xml|
49
+ xml.doc.create_internal_subset('RateRequest', nil, SCHEMA_URL)
50
+ xml.RateRequest do
51
+ add_credentials(xml)
52
+ add_order(xml, destination, options)
53
+ end
54
+ end.to_xml
58
55
  end
59
56
 
60
57
  def add_credentials(xml)
61
- xml.tag! 'EmailAddress', @options[:login]
62
- xml.tag! 'Password', @options[:password]
58
+ xml.EmailAddress @options[:login]
59
+ xml.Password @options[:password]
63
60
  end
64
61
 
65
62
  def add_order(xml, destination, options)
66
- xml.tag! 'Order', :id => options[:order_id] do
67
- xml.tag! 'Warehouse', options[:warehouse] || '00'
63
+ xml.Order :id => options[:order_id] do
64
+ xml.Warehouse options[:warehouse] || '00'
68
65
 
69
66
  add_address(xml, destination)
70
67
  Array(options[:items]).each_with_index do |line_item, index|
@@ -74,28 +71,28 @@ module ActiveShipping
74
71
  end
75
72
 
76
73
  def add_address(xml, destination)
77
- xml.tag! 'AddressInfo', :type => 'Ship' do
74
+ xml.AddressInfo :type => 'Ship' do
78
75
  if destination.name.present?
79
- xml.tag! 'Name' do
80
- xml.tag! 'Full', destination.name
76
+ xml.Name do
77
+ xml.Full destination.name
81
78
  end
82
79
  end
83
- xml.tag! 'Address1', destination.address1
84
- xml.tag! 'Address2', destination.address2 unless destination.address2.blank?
85
- xml.tag! 'Address3', destination.address3 unless destination.address3.blank?
86
- xml.tag! 'Company', destination.company unless destination.company.blank?
87
- xml.tag! 'City', destination.city
88
- xml.tag! 'State', destination.state unless destination.state.blank?
89
- xml.tag! 'Country', destination.country_code
90
- xml.tag! 'Zip', destination.zip unless destination.zip.blank?
80
+ xml.Address1 destination.address1
81
+ xml.Address2 destination.address2 unless destination.address2.blank?
82
+ xml.Address3 destination.address3 unless destination.address3.blank?
83
+ xml.Company destination.company unless destination.company.blank?
84
+ xml.City destination.city
85
+ xml.State destination.state unless destination.state.blank?
86
+ xml.Country destination.country_code
87
+ xml.Zip destination.zip unless destination.zip.blank?
91
88
  end
92
89
  end
93
90
 
94
91
  # Code is limited to 12 characters
95
92
  def add_item(xml, item, index)
96
- xml.tag! 'Item', :num => index do
97
- xml.tag! 'Code', item[:sku]
98
- xml.tag! 'Quantity', item[:quantity]
93
+ xml.Item :num => index do
94
+ xml.Code item[:sku]
95
+ xml.Quantity item[:quantity]
99
96
  end
100
97
  end
101
98
 
@@ -132,18 +129,18 @@ module ActiveShipping
132
129
  response = {}
133
130
  response["rates"] = []
134
131
 
135
- document = REXML::Document.new(xml)
132
+ document = Nokogiri.XML(xml)
136
133
 
137
134
  response["status"] = parse_child_text(document.root, "Status")
138
135
 
139
- document.root.elements.each("Order/Quotes/Quote") do |e|
136
+ document.root.xpath("Order/Quotes/Quote").each do |e|
140
137
  rate = {}
141
- rate["method"] = e.attributes["method"]
138
+ rate["method"] = e["method"]
142
139
  rate["warehouse"] = parse_child_text(e, "Warehouse")
143
140
  rate["service"] = parse_child_text(e, "Service")
144
141
  rate["cost"] = parse_child_text(e, "Cost")
145
142
  rate["currency"] = parse_child_attribute(e, "Cost", "currency")
146
- if delivery_estimate = e.elements["DeliveryEstimate"]
143
+ if delivery_estimate = e.at("DeliveryEstimate")
147
144
  rate["delivery_min"] = parse_child_text(delivery_estimate, "Minimum").to_i
148
145
  rate["delivery_max"] = parse_child_text(delivery_estimate, "Maximum").to_i
149
146
  end
@@ -167,14 +164,14 @@ module ActiveShipping
167
164
  end
168
165
 
169
166
  def parse_child_text(parent, name)
170
- if element = parent.elements[name]
167
+ if element = parent.at(name)
171
168
  element.text
172
169
  end
173
170
  end
174
171
 
175
172
  def parse_child_attribute(parent, name, attribute)
176
- if element = parent.elements[name]
177
- element.attributes[attribute]
173
+ if element = parent.at(name)
174
+ element[attribute]
178
175
  end
179
176
  end
180
177
  end
@@ -112,7 +112,7 @@ module ActiveShipping
112
112
  packages = Array(packages)
113
113
  access_request = build_access_request
114
114
  rate_request = build_rate_request(origin, destination, packages, options)
115
- response = commit(:rates, save_request(access_request + rate_request), (options[:test] || false))
115
+ response = commit(:rates, save_request(access_request + rate_request), options[:test])
116
116
  parse_rate_response(origin, destination, packages, response, options)
117
117
  end
118
118
 
@@ -120,7 +120,7 @@ module ActiveShipping
120
120
  options = @options.update(options)
121
121
  access_request = build_access_request
122
122
  tracking_request = build_tracking_request(tracking_number, options)
123
- response = commit(:track, save_request(access_request + tracking_request), (options[:test] || false))
123
+ response = commit(:track, save_request(access_request + tracking_request), options[:test])
124
124
  parse_tracking_response(response, options)
125
125
  end
126
126
 
@@ -168,15 +168,15 @@ module ActiveShipping
168
168
  /Delivery status information is not available/
169
169
  ]
170
170
 
171
- ESCAPING_AND_SYMBOLS = /&amp;lt;\S*&amp;gt;/
172
- LEADING_USPS = /^USPS/
171
+ ESCAPING_AND_SYMBOLS = /&lt;\S*&gt;/
172
+ LEADING_USPS = /^USPS /
173
173
  TRAILING_ASTERISKS = /\*+$/
174
174
  SERVICE_NAME_SUBSTITUTIONS = /#{ESCAPING_AND_SYMBOLS}|#{LEADING_USPS}|#{TRAILING_ASTERISKS}/
175
175
 
176
176
  def find_tracking_info(tracking_number, options = {})
177
177
  options = @options.update(options)
178
178
  tracking_request = build_tracking_request(tracking_number, options)
179
- response = commit(:track, tracking_request, (options[:test] || false))
179
+ response = commit(:track, tracking_request, options[:test] || false)
180
180
  parse_tracking_response(response, options)
181
181
  end
182
182
 
@@ -249,22 +249,24 @@ module ActiveShipping
249
249
  protected
250
250
 
251
251
  def build_tracking_request(tracking_number, options = {})
252
- xml_request = XmlNode.new('TrackRequest', 'USERID' => @options[:login]) do |root_node|
253
- root_node << XmlNode.new('TrackID', :ID => tracking_number)
252
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
253
+ xml.TrackRequest('USERID' => @options[:login]) do
254
+ xml.TrackID('ID' => tracking_number)
255
+ end
254
256
  end
255
- URI.encode(xml_request.to_s)
257
+ xml_builder.to_xml
256
258
  end
257
259
 
258
260
  def us_rates(origin, destination, packages, options = {})
259
261
  request = build_us_rate_request(packages, origin.zip, destination.zip, options)
260
262
  # never use test mode; rate requests just won't work on test servers
261
- parse_rate_response origin, destination, packages, commit(:us_rates, request, false), options
263
+ parse_rate_response(origin, destination, packages, commit(:us_rates, request, false), options)
262
264
  end
263
265
 
264
266
  def world_rates(origin, destination, packages, options = {})
265
267
  request = build_world_rate_request(packages, destination, options)
266
268
  # never use test mode; rate requests just won't work on test servers
267
- parse_rate_response origin, destination, packages, commit(:world_rates, request, false), options
269
+ parse_rate_response(origin, destination, packages, commit(:world_rates, request, false), options)
268
270
  end
269
271
 
270
272
  # Once the address verification API is implemented, remove this and have valid_credentials? build the request using that instead.
@@ -283,8 +285,8 @@ module ActiveShipping
283
285
  <ZIP4>9411</ZIP4>
284
286
  </CarrierPickupAvailabilityRequest>
285
287
  EOF
286
- xml = REXML::Document.new(commit(:test, URI.encode(request), true))
287
- xml.get_text('/CarrierPickupAvailabilityResponse/City').to_s == 'SAN FRANCISCO' && xml.get_text('/CarrierPickupAvailabilityResponse/Address2').to_s == '18 FAIR AVE'
288
+ xml = Nokogiri.XML(commit(:test, request, true)) { |config| config.strict }
289
+ xml.at('/CarrierPickupAvailabilityResponse/City').text == 'SAN FRANCISCO' && xml.at('/CarrierPickupAvailabilityResponse/Address2').text == '18 FAIR AVE'
288
290
  end
289
291
 
290
292
  # options[:service] -- One of [:first_class, :priority, :express, :bpm, :parcel,
@@ -296,40 +298,41 @@ module ActiveShipping
296
298
  # package.options[:machinable] -- Either true or false. Overrides the detection of
297
299
  # "machinability" entirely.
298
300
  def build_us_rate_request(packages, origin_zip, destination_zip, options = {})
299
- packages = Array(packages)
300
- request = XmlNode.new('RateV4Request', :USERID => @options[:login]) do |rate_request|
301
- packages.each_with_index do |p, id|
302
- rate_request << XmlNode.new('Package', :ID => id.to_s) do |package|
303
- commercial_type = commercial_type(options)
304
- default_service = DEFAULT_SERVICE[commercial_type]
305
- service = options.fetch(:service, default_service).to_sym
306
-
307
- if commercial_type && service != default_service
308
- raise ArgumentError, "Commercial #{commercial_type} rates are only provided with the #{default_service.inspect} service."
309
- end
310
-
311
- package << XmlNode.new('Service', US_SERVICES[service])
312
- package << XmlNode.new('FirstClassMailType', FIRST_CLASS_MAIL_TYPES[options[:first_class_mail_type].try(:to_sym)])
313
- package << XmlNode.new('ZipOrigination', strip_zip(origin_zip))
314
- package << XmlNode.new('ZipDestination', strip_zip(destination_zip))
315
- package << XmlNode.new('Pounds', 0)
316
- package << XmlNode.new('Ounces', "%0.1f" % [p.ounces, 1].max)
317
- package << XmlNode.new('Container', CONTAINERS[p.options[:container]])
318
- package << XmlNode.new('Size', USPS.size_code_for(p))
319
- package << XmlNode.new('Width', "%0.2f" % p.inches(:width))
320
- package << XmlNode.new('Length', "%0.2f" % p.inches(:length))
321
- package << XmlNode.new('Height', "%0.2f" % p.inches(:height))
322
- package << XmlNode.new('Girth', "%0.2f" % p.inches(:girth))
323
- is_machinable = if p.options.has_key?(:machinable)
324
- p.options[:machinable] ? true : false
325
- else
326
- USPS.package_machinable?(p)
301
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
302
+ xml.RateV4Request('USERID' => @options[:login]) do
303
+ Array(packages).each_with_index do |package, id|
304
+ xml.Package('ID' => id) do
305
+ commercial_type = commercial_type(options)
306
+ default_service = DEFAULT_SERVICE[commercial_type]
307
+ service = options.fetch(:service, default_service).to_sym
308
+
309
+ if commercial_type && service != default_service
310
+ raise ArgumentError, "Commercial #{commercial_type} rates are only provided with the #{default_service.inspect} service."
311
+ end
312
+
313
+ xml.Service(US_SERVICES[service])
314
+ xml.FirstClassMailType(FIRST_CLASS_MAIL_TYPES[options[:first_class_mail_type].try(:to_sym)])
315
+ xml.ZipOrigination(strip_zip(origin_zip))
316
+ xml.ZipDestination(strip_zip(destination_zip))
317
+ xml.Pounds(0)
318
+ xml.Ounces("%0.1f" % [package.ounces, 1].max)
319
+ xml.Container(CONTAINERS[package.options[:container]])
320
+ xml.Size(USPS.size_code_for(package))
321
+ xml.Width("%0.2f" % package.inches(:width))
322
+ xml.Length("%0.2f" % package.inches(:length))
323
+ xml.Height("%0.2f" % package.inches(:height))
324
+ xml.Girth("%0.2f" % package.inches(:girth))
325
+ is_machinable = if package.options.has_key?(:machinable)
326
+ package.options[:machinable] ? true : false
327
+ else
328
+ USPS.package_machinable?(package)
329
+ end
330
+ xml.Machinable(is_machinable.to_s.upcase)
327
331
  end
328
- package << XmlNode.new('Machinable', is_machinable.to_s.upcase)
329
332
  end
330
333
  end
331
334
  end
332
- URI.encode(save_request(request.to_s))
335
+ save_request(xml_builder.to_xml)
333
336
  end
334
337
 
335
338
  # important difference with international rate requests:
@@ -343,39 +346,40 @@ module ActiveShipping
343
346
  # Defaults to :package.
344
347
  def build_world_rate_request(packages, destination, options)
345
348
  country = COUNTRY_NAME_CONVERSIONS[destination.country.code(:alpha2).value] || destination.country.name
346
- request = XmlNode.new('IntlRateV2Request', :USERID => @options[:login]) do |rate_request|
347
- packages.each_index do |id|
348
- p = packages[id]
349
- rate_request << XmlNode.new('Package', :ID => id.to_s) do |package|
350
- package << XmlNode.new('Pounds', 0)
351
- package << XmlNode.new('Ounces', [p.ounces, 1].max.ceil) # takes an integer for some reason, must be rounded UP
352
- package << XmlNode.new('MailType', MAIL_TYPES[p.options[:mail_type]] || 'Package')
353
- package << XmlNode.new('GXG') do |gxg|
354
- gxg << XmlNode.new('POBoxFlag', destination.po_box? ? 'Y' : 'N')
355
- gxg << XmlNode.new('GiftFlag', p.gift? ? 'Y' : 'N')
356
- end
357
- value = if p.value && p.value > 0 && p.currency && p.currency != 'USD'
358
- 0.0
359
- else
360
- (p.value || 0) / 100.0
361
- end
362
- package << XmlNode.new('ValueOfContents', value)
363
- package << XmlNode.new('Country') do |node|
364
- node.cdata = country
365
- end
366
- package << XmlNode.new('Container', p.cylinder? ? 'NONRECTANGULAR' : 'RECTANGULAR')
367
- package << XmlNode.new('Size', USPS.size_code_for(p))
368
- package << XmlNode.new('Width', "%0.2f" % [p.inches(:width), 0.01].max)
369
- package << XmlNode.new('Length', "%0.2f" % [p.inches(:length), 0.01].max)
370
- package << XmlNode.new('Height', "%0.2f" % [p.inches(:height), 0.01].max)
371
- package << XmlNode.new('Girth', "%0.2f" % [p.inches(:girth), 0.01].max)
372
- if commercial_type = commercial_type(options)
373
- package << XmlNode.new(COMMERCIAL_FLAG_NAME.fetch(commercial_type), 'Y')
349
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
350
+ xml.IntlRateV2Request('USERID' => @options[:login]) do
351
+ Array(packages).each_with_index do |package, id|
352
+ xml.Package('ID' => id) do
353
+ xml.Pounds(0)
354
+ xml.Ounces([package.ounces, 1].max.ceil) # takes an integer for some reason, must be rounded UP
355
+ xml.MailType(MAIL_TYPES[package.options[:mail_type]] || 'Package')
356
+ xml.GXG do
357
+ xml.POBoxFlag(destination.po_box? ? 'Y' : 'N')
358
+ xml.GiftFlag(package.gift? ? 'Y' : 'N')
359
+ end
360
+
361
+ value = if package.value && package.value > 0 && package.currency && package.currency != 'USD'
362
+ 0.0
363
+ else
364
+ (package.value || 0) / 100.0
365
+ end
366
+ xml.ValueOfContents(value)
367
+
368
+ xml.Country(country)
369
+ xml.Container(package.cylinder? ? 'NONRECTANGULAR' : 'RECTANGULAR')
370
+ xml.Size(USPS.size_code_for(package))
371
+ xml.Width("%0.2f" % [package.inches(:width), 0.01].max)
372
+ xml.Length("%0.2f" % [package.inches(:length), 0.01].max)
373
+ xml.Height("%0.2f" % [package.inches(:height), 0.01].max)
374
+ xml.Girth("%0.2f" % [package.inches(:girth), 0.01].max)
375
+ if commercial_type = commercial_type(options)
376
+ xml.public_send(COMMERCIAL_FLAG_NAME.fetch(commercial_type), 'Y')
377
+ end
374
378
  end
375
379
  end
376
380
  end
377
381
  end
378
- URI.encode(save_request(request.to_s))
382
+ save_request(xml_builder.to_xml)
379
383
  end
380
384
 
381
385
  def parse_rate_response(origin, destination, packages, response, options = {})
@@ -383,16 +387,16 @@ module ActiveShipping
383
387
  message = ''
384
388
  rate_hash = {}
385
389
 
386
- xml = REXML::Document.new(response)
390
+ xml = Nokogiri.XML(response)
387
391
 
388
- if error = xml.elements['/Error']
392
+ if error = xml.at('/Error')
389
393
  success = false
390
- message = error.elements['Description'].text
394
+ message = error.at('Description').text
391
395
  else
392
- xml.elements.each('/*/Package') do |package|
393
- if package.elements['Error']
396
+ xml.root.xpath('Package').each do |package|
397
+ if package.at('Error')
394
398
  success = false
395
- message = package.get_text('Error/Description').to_s
399
+ message = package.at('Error/Description').text
396
400
  break
397
401
  end
398
402
  end
@@ -423,7 +427,7 @@ module ActiveShipping
423
427
 
424
428
  def rates_from_response_node(response_node, packages, options = {})
425
429
  rate_hash = {}
426
- return false unless (root_node = response_node.elements['/IntlRateV2Response | /RateV4Response'])
430
+ return false unless (root_node = response_node.at_xpath('/IntlRateV2Response | /RateV4Response'))
427
431
 
428
432
  commercial_type = commercial_type(options)
429
433
  service_node, service_code_node, service_name_node, rate_node = if root_node.name == 'RateV4Response'
@@ -432,20 +436,19 @@ module ActiveShipping
432
436
  %w(Service ID SvcDescription) << INTERNATIONAL_RATE_FIELD[commercial_type]
433
437
  end
434
438
 
435
- root_node.each_element('Package') do |package_node|
436
- this_package = packages[package_node.attributes['ID'].to_i]
439
+ root_node.xpath('Package').each do |package_node|
440
+ this_package = packages[package_node['ID'].to_i]
437
441
 
438
- package_node.each_element(service_node) do |service_response_node|
439
- service_name = service_response_node.get_text(service_name_node).to_s
442
+ package_node.xpath(service_node).each do |service_response_node|
443
+ service_name = service_response_node.at(service_name_node).text
440
444
 
441
445
  service_name.gsub!(SERVICE_NAME_SUBSTITUTIONS, '')
442
- service_name.strip!
443
446
 
444
447
  # aggregate specific package rates into a service-centric RateEstimate
445
448
  # first package with a given service name will initialize these;
446
449
  # later packages with same service will add to them
447
450
  this_service = rate_hash[service_name] ||= {}
448
- this_service[:service_code] ||= service_response_node.attributes[service_code_node]
451
+ this_service[:service_code] ||= service_response_node.attributes[service_code_node].value
449
452
  package_rates = this_service[:package_rates] ||= []
450
453
  this_package_rate = {:package => this_package,
451
454
  :rate => Package.cents_from(rate_value(rate_node, service_response_node, commercial_type))}
@@ -457,9 +460,9 @@ module ActiveShipping
457
460
  end
458
461
 
459
462
  def package_valid_for_service(package, service_node)
460
- return true if service_node.elements['MaxWeight'].nil?
461
- max_weight = service_node.get_text('MaxWeight').to_s.to_f
462
- name = service_node.get_text('SvcDescription | MailService').to_s.downcase
463
+ return true if service_node.at('MaxWeight').nil?
464
+ max_weight = service_node.at('MaxWeight').text.to_f
465
+ name = service_node.at_xpath('SvcDescription | MailService').text.downcase
463
466
 
464
467
  if name =~ /flat.rate.box/ # domestic or international flat rate box
465
468
  # flat rate dimensions from http://www.usps.com/shipping/flatrate.htm
@@ -479,7 +482,7 @@ module ActiveShipping
479
482
  :length => 12.5,
480
483
  :width => 9.5,
481
484
  :height => 0.75)
482
- elsif service_node.elements['MailService'] # domestic non-flat rates
485
+ elsif service_node.at('MailService') # domestic non-flat rates
483
486
  return true
484
487
  else # international non-flat rates
485
488
  # Some sample english that this is required to parse:
@@ -487,7 +490,7 @@ module ActiveShipping
487
490
  # 'Max. length 46", width 35", height 46" and max. length plus girth 108"'
488
491
  # 'Max. length 24", Max. length, height, depth combined 36"'
489
492
  #
490
- sentence = CGI.unescapeHTML(service_node.get_text('MaxDimensions').to_s)
493
+ sentence = CGI.unescapeHTML(service_node.at('MaxDimensions').text)
491
494
  tokens = sentence.downcase.split(/[^\d]*"/).reject(&:empty?)
492
495
  max_dimensions = {:weight => max_weight}
493
496
  single_axis_values = []
@@ -525,8 +528,8 @@ module ActiveShipping
525
528
 
526
529
  def parse_tracking_response(response, options)
527
530
  actual_delivery_date, status = nil
528
- xml = REXML::Document.new(response)
529
- root_node = xml.elements['TrackResponse']
531
+ xml = Nokogiri.XML(response)
532
+ root_node = xml.root
530
533
 
531
534
  success = response_success?(xml)
532
535
  message = response_message(xml)
@@ -534,15 +537,15 @@ module ActiveShipping
534
537
  if success
535
538
  destination = nil
536
539
  shipment_events = []
537
- tracking_details = xml.elements.collect('*/*/TrackDetail') { |e| e }
540
+ tracking_details = xml.root.xpath('TrackInfo/TrackDetail')
538
541
 
539
- tracking_summary = xml.elements.collect('*/*/TrackSummary') { |e| e }.first
542
+ tracking_summary = xml.root.at('TrackInfo/TrackSummary')
540
543
  tracking_details << tracking_summary
541
544
 
542
- tracking_number = root_node.elements['TrackInfo'].attributes['ID'].to_s
545
+ tracking_number = xml.root.at('TrackInfo').attributes['ID'].value
543
546
 
544
547
  tracking_details.each do |event|
545
- details = extract_event_details(event.get_text.to_s)
548
+ details = extract_event_details(event.text)
546
549
  shipment_events << ShipmentEvent.new(details.description, details.zoneless_time, details.location) if details.location
547
550
  end
548
551
 
@@ -567,7 +570,7 @@ module ActiveShipping
567
570
  end
568
571
 
569
572
  def track_summary_node(document)
570
- document.elements['*/*/TrackSummary']
573
+ document.root.xpath('TrackInfo/TrackSummary')
571
574
  end
572
575
 
573
576
  def error_description_node(document)
@@ -583,13 +586,13 @@ module ActiveShipping
583
586
  end
584
587
 
585
588
  def has_error?(document)
586
- !!document.elements['Error']
589
+ !document.at('Error').nil?
587
590
  end
588
591
 
589
592
  def no_record?(document)
590
593
  summary_node = track_summary_node(document)
591
594
  if summary_node
592
- summary = summary_node.get_text.to_s
595
+ summary = summary_node.text
593
596
  RESPONSE_ERROR_MESSAGES.detect { |re| summary =~ re }
594
597
  summary =~ /There is no record of that mail item/ || summary =~ /This Information has not been included in this Test Server\./
595
598
  else
@@ -598,7 +601,7 @@ module ActiveShipping
598
601
  end
599
602
 
600
603
  def tracking_info_error?(document)
601
- document.elements['*/TrackInfo/Error']
604
+ !document.root.at('TrackInfo/Error').nil?
602
605
  end
603
606
 
604
607
  def response_success?(document)
@@ -606,8 +609,7 @@ module ActiveShipping
606
609
  end
607
610
 
608
611
  def response_message(document)
609
- response_node = response_status_node(document)
610
- response_node.get_text.to_s
612
+ response_status_node(document).text
611
613
  end
612
614
 
613
615
  def commit(action, request, test = false)
@@ -618,7 +620,7 @@ module ActiveShipping
618
620
  scheme = USE_SSL[action] ? 'https://' : 'http://'
619
621
  host = test ? TEST_DOMAINS[USE_SSL[action]] : LIVE_DOMAIN
620
622
  resource = test ? TEST_RESOURCE : LIVE_RESOURCE
621
- "#{scheme}#{host}/#{resource}?API=#{API_CODES[action]}&XML=#{request}"
623
+ "#{scheme}#{host}/#{resource}?API=#{API_CODES[action]}&XML=#{URI.encode(request)}"
622
624
  end
623
625
 
624
626
  def strip_zip(zip)
@@ -628,7 +630,7 @@ module ActiveShipping
628
630
  private
629
631
 
630
632
  def rate_value(rate_node, service_response_node, commercial_type)
631
- service_response_node.get_text(rate_node).to_s.to_f
633
+ service_response_node.at(rate_node).try(:text).to_f
632
634
  end
633
635
 
634
636
  def commercial_type(options)