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