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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +12 -0
- data/.travis.yml +14 -0
- data/.yardopts +14 -0
- data/Gemfile +3 -0
- data/Gemfile.activesupport32 +4 -0
- data/Gemfile.activesupport40 +4 -0
- data/Gemfile.activesupport41 +4 -0
- data/Gemfile.activesupport42 +4 -0
- data/Rakefile +23 -0
- data/active_shipping.gemspec +32 -0
- data/lib/active_shipping.rb +1 -1
- data/lib/active_shipping/carrier.rb +1 -1
- data/lib/active_shipping/carriers/shipwire.rb +32 -35
- data/lib/active_shipping/carriers/ups.rb +2 -2
- data/lib/active_shipping/carriers/usps.rb +103 -101
- data/lib/active_shipping/label_response.rb +1 -1
- data/lib/active_shipping/rate_estimate.rb +76 -15
- data/lib/active_shipping/rate_response.rb +20 -0
- data/lib/active_shipping/response.rb +17 -0
- data/lib/active_shipping/shipment_packer.rb +2 -2
- data/lib/active_shipping/shipping_response.rb +24 -2
- data/lib/active_shipping/tracking_response.rb +65 -17
- data/lib/active_shipping/version.rb +1 -1
- data/shipit.rubygems.yml +1 -0
- data/test/credentials.yml +47 -0
- data/test/fixtures/files/label1.pdf +0 -0
- data/test/fixtures/files/ups-shipping-label.gif +0 -0
- data/test/fixtures/json/newzealandpost/domestic_book.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_default.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_error.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_poster.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_small_half_pound.json +1 -0
- data/test/fixtures/json/newzealandpost/international_book.json +1 -0
- data/test/fixtures/json/newzealandpost/international_new_zealand_wii.json +1 -0
- data/test/fixtures/json/newzealandpost/international_small_half_pound.json +1 -0
- data/test/fixtures/json/newzealandpost/international_wii.json +1 -0
- data/test/fixtures/xml/canadapost/example_request.xml +25 -0
- data/test/fixtures/xml/canadapost/example_response.xml +130 -0
- data/test/fixtures/xml/canadapost/example_response_error.xml +16 -0
- data/test/fixtures/xml/canadapost/example_response_french.xml +122 -0
- data/test/fixtures/xml/canadapost/example_response_with_nil_value.xml +164 -0
- data/test/fixtures/xml/canadapost/example_response_with_postal_outlet.xml +155 -0
- data/test/fixtures/xml/canadapost/example_response_with_postal_outlet_french.xml +274 -0
- data/test/fixtures/xml/canadapost/example_response_with_strange_delivery_date.xml +130 -0
- data/test/fixtures/xml/canadapost_pws/dnc_tracking_details_en.xml +112 -0
- data/test/fixtures/xml/canadapost_pws/merchant_details_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/merchant_details_response.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/option_response.xml +13 -0
- data/test/fixtures/xml/canadapost_pws/option_response_no_conflicts.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/rates_info.xml +190 -0
- data/test/fixtures/xml/canadapost_pws/rates_info_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/receipt_response.xml +42 -0
- data/test/fixtures/xml/canadapost_pws/receipt_response_no_priced_options.xml +36 -0
- data/test/fixtures/xml/canadapost_pws/register_token_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/register_token_response.xml +3 -0
- data/test/fixtures/xml/canadapost_pws/service_options_response.xml +42 -0
- data/test/fixtures/xml/canadapost_pws/services_error.xml +6 -0
- data/test/fixtures/xml/canadapost_pws/services_response.xml +32 -0
- data/test/fixtures/xml/canadapost_pws/shipment_domestic.xml +69 -0
- data/test/fixtures/xml/canadapost_pws/shipment_response.xml +20 -0
- data/test/fixtures/xml/canadapost_pws/shipment_us.xml +69 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en.xml +152 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en_undelivered.xml +116 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_fr.xml +156 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_no_expected_delivery_date.xml +40 -0
- data/test/fixtures/xml/fedex/freight_rate_request.xml +82 -0
- data/test/fixtures/xml/fedex/freight_rate_response.xml +506 -0
- data/test/fixtures/xml/fedex/invalid_fedex_reply.xml +27 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_commercial_rate_request.xml +79 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_request.xml +80 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_response.xml +214 -0
- data/test/fixtures/xml/fedex/raterequest_reply.xml +213 -0
- data/test/fixtures/xml/fedex/reply_without_notifications.xml +185 -0
- data/test/fixtures/xml/fedex/tracking_request.xml +27 -0
- data/test/fixtures/xml/fedex/tracking_response.xml +151 -0
- data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +76 -0
- data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +139 -0
- data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +150 -0
- data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +95 -0
- data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +71 -0
- data/test/fixtures/xml/fedex/unknown_fedex_document_reply.xml +3 -0
- data/test/fixtures/xml/kunaki/invalid_state_response.xml +3 -0
- data/test/fixtures/xml/kunaki/no_valid_items_response.xml +3 -0
- data/test/fixtures/xml/kunaki/successful_rates_response.xml +3 -0
- data/test/fixtures/xml/kunaki/unsuccessful_rates_response.xml +9 -0
- data/test/fixtures/xml/shipwire/international_rates_response.xml +17 -0
- data/test/fixtures/xml/shipwire/new_carrier_rate_response.xml +18 -0
- data/test/fixtures/xml/shipwire/no_rates_response.xml +7 -0
- data/test/fixtures/xml/shipwire/rates_response.xml +36 -0
- data/test/fixtures/xml/shipwire/rates_response_no_estimate.xml +14 -0
- data/test/fixtures/xml/stamps/authenticate_user_request.xml +15 -0
- data/test/fixtures/xml/stamps/authenticate_user_response.xml +10 -0
- data/test/fixtures/xml/stamps/cleanse_address_request.xml +19 -0
- data/test/fixtures/xml/stamps/cleanse_address_response.xml +27 -0
- data/test/fixtures/xml/stamps/create_indicium_request.xml +69 -0
- data/test/fixtures/xml/stamps/create_indicium_response.xml +40 -0
- data/test/fixtures/xml/stamps/expired_authenticator_response.xml +15 -0
- data/test/fixtures/xml/stamps/get_account_info_request.xml +11 -0
- data/test/fixtures/xml/stamps/get_account_info_response.xml +36 -0
- data/test/fixtures/xml/stamps/get_purchase_status_request.xml +12 -0
- data/test/fixtures/xml/stamps/get_purchase_status_response.xml +16 -0
- data/test/fixtures/xml/stamps/get_rates_request.xml +19 -0
- data/test/fixtures/xml/stamps/get_rates_response.xml +351 -0
- data/test/fixtures/xml/stamps/purchase_postage_request.xml +13 -0
- data/test/fixtures/xml/stamps/purchase_postage_response.xml +17 -0
- data/test/fixtures/xml/stamps/track_shipment_request.xml +12 -0
- data/test/fixtures/xml/stamps/track_shipment_response.xml +45 -0
- data/test/fixtures/xml/ups/delivered_shipment_with_refund.xml +290 -0
- data/test/fixtures/xml/ups/delivered_shipment_without_events_tracking_response.xml +62 -0
- data/test/fixtures/xml/ups/example_tracking_response.xml +53 -0
- data/test/fixtures/xml/ups/in_transit_shipment.xml +183 -0
- data/test/fixtures/xml/ups/out_for_delivery_shipment.xml +165 -0
- data/test/fixtures/xml/ups/shipment_accept_response.xml +42 -0
- data/test/fixtures/xml/ups/shipment_confirm_response.xml +33 -0
- data/test/fixtures/xml/ups/shipment_from_tiger_direct.xml +222 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +1 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response_with_insured.xml +289 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_with_origin_account_response.xml +311 -0
- data/test/fixtures/xml/ups/triple_accept_response.xml +72 -0
- data/test/fixtures/xml/ups/triple_confirm_response.xml +32 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_base_rate_response.xml +2 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_plus_rate_response.xml +258 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_rate_response.xml +108 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_base_rate_response.xml +84 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_plus_rate_response.xml +212 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_rate_response.xml +230 -0
- data/test/fixtures/xml/usps/delivered_tracking_response.xml +11 -0
- data/test/fixtures/xml/usps/first_class_packages_with_invalid_mail_type_response.xml +12 -0
- data/test/fixtures/xml/usps/first_class_packages_with_mail_type_response.xml +16 -0
- data/test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml +12 -0
- data/test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml +2 -0
- data/test/fixtures/xml/usps/tracking_request.xml +3 -0
- data/test/fixtures/xml/usps/tracking_response.xml +13 -0
- data/test/fixtures/xml/usps/tracking_response_failure.xml +3 -0
- data/test/fixtures/xml/usps/tracking_response_not_available.xml +12 -0
- data/test/fixtures/xml/usps/tracking_response_test_error.xml +8 -0
- data/test/fixtures/xml/usps/us_rate_request.xml +18 -0
- data/test/fixtures/xml/usps/world_rate_request_with_value.xml +20 -0
- data/test/fixtures/xml/usps/world_rate_request_without_value.xml +20 -0
- data/test/remote/canada_post_pws_platform_test.rb +246 -0
- data/test/remote/canada_post_pws_test.rb +171 -0
- data/test/remote/canada_post_test.rb +53 -0
- data/test/remote/fedex_test.rb +216 -0
- data/test/remote/kunaki_test.rb +36 -0
- data/test/remote/new_zealand_post_test.rb +147 -0
- data/test/remote/shipwire_test.rb +82 -0
- data/test/remote/stamps_test.rb +394 -0
- data/test/remote/ups_test.rb +257 -0
- data/test/remote/usps_test.rb +235 -0
- data/test/test_helper.rb +220 -0
- data/test/unit/carriers/benchmark_test.rb +18 -0
- data/test/unit/carriers/canada_post_pws_rating_test.rb +349 -0
- data/test/unit/carriers/canada_post_pws_register_test.rb +75 -0
- data/test/unit/carriers/canada_post_pws_shipping_test.rb +244 -0
- data/test/unit/carriers/canada_post_pws_tracking_test.rb +154 -0
- data/test/unit/carriers/canada_post_test.rb +145 -0
- data/test/unit/carriers/fedex_test.rb +408 -0
- data/test/unit/carriers/kunaki_test.rb +52 -0
- data/test/unit/carriers/new_zealand_post_test.rb +174 -0
- data/test/unit/carriers/shipwire_test.rb +187 -0
- data/test/unit/carriers/stamps_test.rb +241 -0
- data/test/unit/carriers/ups_test.rb +311 -0
- data/test/unit/carriers/usps_test.rb +484 -0
- data/test/unit/carriers_test.rb +17 -0
- data/test/unit/label_response_test.rb +15 -0
- data/test/unit/location_test.rb +138 -0
- data/test/unit/package_test.rb +68 -0
- data/test/unit/rate_estimate_test.rb +34 -0
- data/test/unit/response_test.rb +14 -0
- data/test/unit/shipment_packer_test.rb +174 -0
- metadata +331 -35
- metadata.gz.sig +0 -0
- data/lib/vendor/quantified/MIT-LICENSE +0 -22
- data/lib/vendor/quantified/README.markdown +0 -49
- data/lib/vendor/quantified/Rakefile +0 -13
- data/lib/vendor/quantified/init.rb +0 -0
- data/lib/vendor/quantified/lib/quantified.rb +0 -6
- data/lib/vendor/quantified/lib/quantified/attribute.rb +0 -211
- data/lib/vendor/quantified/lib/quantified/length.rb +0 -20
- data/lib/vendor/quantified/lib/quantified/mass.rb +0 -19
- data/lib/vendor/quantified/test/length_test.rb +0 -94
- data/lib/vendor/quantified/test/mass_test.rb +0 -96
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 893b67f5102c88b62a062968374250a09ac2e266
|
|
4
|
+
data.tar.gz: 4438e27c1d8d4f70abbcbdd59ba5a33f679d0a3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/.travis.yml
ADDED
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
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
|
data/lib/active_shipping.rb
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
62
|
-
xml.
|
|
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.
|
|
67
|
-
xml.
|
|
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.
|
|
74
|
+
xml.AddressInfo :type => 'Ship' do
|
|
78
75
|
if destination.name.present?
|
|
79
|
-
xml.
|
|
80
|
-
xml.
|
|
76
|
+
xml.Name do
|
|
77
|
+
xml.Full destination.name
|
|
81
78
|
end
|
|
82
79
|
end
|
|
83
|
-
xml.
|
|
84
|
-
xml.
|
|
85
|
-
xml.
|
|
86
|
-
xml.
|
|
87
|
-
xml.
|
|
88
|
-
xml.
|
|
89
|
-
xml.
|
|
90
|
-
xml.
|
|
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.
|
|
97
|
-
xml.
|
|
98
|
-
xml.
|
|
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 =
|
|
132
|
+
document = Nokogiri.XML(xml)
|
|
136
133
|
|
|
137
134
|
response["status"] = parse_child_text(document.root, "Status")
|
|
138
135
|
|
|
139
|
-
document.root.
|
|
136
|
+
document.root.xpath("Order/Quotes/Quote").each do |e|
|
|
140
137
|
rate = {}
|
|
141
|
-
rate["method"] = e
|
|
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.
|
|
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.
|
|
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.
|
|
177
|
-
element
|
|
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),
|
|
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),
|
|
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 = /&
|
|
172
|
-
LEADING_USPS = /^USPS/
|
|
171
|
+
ESCAPING_AND_SYMBOLS = /<\S*>/
|
|
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,
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
287
|
-
xml.
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
0.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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 =
|
|
390
|
+
xml = Nokogiri.XML(response)
|
|
387
391
|
|
|
388
|
-
if error = xml.
|
|
392
|
+
if error = xml.at('/Error')
|
|
389
393
|
success = false
|
|
390
|
-
message = error.
|
|
394
|
+
message = error.at('Description').text
|
|
391
395
|
else
|
|
392
|
-
xml.
|
|
393
|
-
if package.
|
|
396
|
+
xml.root.xpath('Package').each do |package|
|
|
397
|
+
if package.at('Error')
|
|
394
398
|
success = false
|
|
395
|
-
message = package.
|
|
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.
|
|
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.
|
|
436
|
-
this_package = packages[package_node
|
|
439
|
+
root_node.xpath('Package').each do |package_node|
|
|
440
|
+
this_package = packages[package_node['ID'].to_i]
|
|
437
441
|
|
|
438
|
-
package_node.
|
|
439
|
-
service_name = service_response_node.
|
|
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.
|
|
461
|
-
max_weight = service_node.
|
|
462
|
-
name = service_node.
|
|
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.
|
|
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.
|
|
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 =
|
|
529
|
-
root_node = xml.
|
|
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.
|
|
540
|
+
tracking_details = xml.root.xpath('TrackInfo/TrackDetail')
|
|
538
541
|
|
|
539
|
-
tracking_summary = xml.
|
|
542
|
+
tracking_summary = xml.root.at('TrackInfo/TrackSummary')
|
|
540
543
|
tracking_details << tracking_summary
|
|
541
544
|
|
|
542
|
-
tracking_number =
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
633
|
+
service_response_node.at(rate_node).try(:text).to_f
|
|
632
634
|
end
|
|
633
635
|
|
|
634
636
|
def commercial_type(options)
|