active_shipping 1.0.0.pre2 → 1.0.0.pre3

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 (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)