reactive_shipping 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +33 -0
- data/.yardopts +13 -0
- data/CHANGELOG.md +225 -0
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +21 -0
- data/README.md +158 -0
- data/Rakefile +35 -0
- data/dev.yml +17 -0
- data/gemfiles/activesupport42.gemfile +5 -0
- data/gemfiles/activesupport50.gemfile +6 -0
- data/gemfiles/activesupport51.gemfile +5 -0
- data/gemfiles/activesupport52.gemfile +5 -0
- data/gemfiles/activesupport_master.gemfile +5 -0
- data/lib/certs/eParcel.dtd +111 -0
- data/lib/reactive_shipping.rb +26 -0
- data/lib/reactive_shipping/address_validation_response.rb +30 -0
- data/lib/reactive_shipping/carrier.rb +184 -0
- data/lib/reactive_shipping/carriers.rb +35 -0
- data/lib/reactive_shipping/carriers/australia_post.rb +248 -0
- data/lib/reactive_shipping/carriers/benchmark_carrier.rb +31 -0
- data/lib/reactive_shipping/carriers/bogus_carrier.rb +12 -0
- data/lib/reactive_shipping/carriers/canada_post.rb +263 -0
- data/lib/reactive_shipping/carriers/canada_post_pws.rb +908 -0
- data/lib/reactive_shipping/carriers/fedex.rb +797 -0
- data/lib/reactive_shipping/carriers/kunaki.rb +155 -0
- data/lib/reactive_shipping/carriers/new_zealand_post.rb +260 -0
- data/lib/reactive_shipping/carriers/shipwire.rb +178 -0
- data/lib/reactive_shipping/carriers/stamps.rb +860 -0
- data/lib/reactive_shipping/carriers/ups.rb +1060 -0
- data/lib/reactive_shipping/carriers/usps.rb +708 -0
- data/lib/reactive_shipping/carriers/usps_returns.rb +86 -0
- data/lib/reactive_shipping/delivery_date_estimate.rb +20 -0
- data/lib/reactive_shipping/delivery_date_estimates_response.rb +11 -0
- data/lib/reactive_shipping/errors.rb +35 -0
- data/lib/reactive_shipping/external_return_label_request.rb +417 -0
- data/lib/reactive_shipping/external_return_label_response.rb +26 -0
- data/lib/reactive_shipping/label.rb +10 -0
- data/lib/reactive_shipping/label_response.rb +10 -0
- data/lib/reactive_shipping/location.rb +166 -0
- data/lib/reactive_shipping/package.rb +165 -0
- data/lib/reactive_shipping/package_item.rb +60 -0
- data/lib/reactive_shipping/rate_estimate.rb +197 -0
- data/lib/reactive_shipping/rate_response.rb +33 -0
- data/lib/reactive_shipping/response.rb +44 -0
- data/lib/reactive_shipping/shipment_event.rb +22 -0
- data/lib/reactive_shipping/shipment_packer.rb +108 -0
- data/lib/reactive_shipping/shipping_response.rb +34 -0
- data/lib/reactive_shipping/tracking_response.rb +120 -0
- data/lib/reactive_shipping/version.rb +3 -0
- data/reactive_shipping.gemspec +38 -0
- data/shipit.rubygems.yml +1 -0
- data/test/console.rb +39 -0
- data/test/credentials.yml +76 -0
- data/test/fixtures/files/label1.pdf +0 -0
- data/test/fixtures/files/ups-shipping-label.gif +0 -0
- data/test/fixtures/json/australia_post/calculate_domestic.json +13 -0
- data/test/fixtures/json/australia_post/calculate_domestic_2.json +19 -0
- data/test/fixtures/json/australia_post/calculate_international.json +12 -0
- data/test/fixtures/json/australia_post/calculate_international_2.json +15 -0
- data/test/fixtures/json/australia_post/error_message.json +5 -0
- data/test/fixtures/json/australia_post/service_domestic.json +117 -0
- data/test/fixtures/json/australia_post/service_domestic_2.json +117 -0
- data/test/fixtures/json/australia_post/service_international.json +76 -0
- data/test/fixtures/json/australia_post/service_international_2.json +59 -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/create_shipment_response.xml +2 -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_no_saturday_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/raterequest_response_with_ground_home_delivery.xml +206 -0
- data/test/fixtures/xml/fedex/reply_without_notifications.xml +185 -0
- data/test/fixtures/xml/fedex/tracking_request.xml +29 -0
- data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
- data/test/fixtures/xml/fedex/tracking_response_empty_status_detail.xml +84 -0
- data/test/fixtures/xml/fedex/tracking_response_failure_code_9045.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_failure_code_9080.xml +51 -0
- data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
- data/test/fixtures/xml/fedex/tracking_response_invalid_tracking_number.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_missing_status_code.xml +89 -0
- data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
- data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
- data/test/fixtures/xml/fedex/tracking_response_unable_to_process.xml +32 -0
- data/test/fixtures/xml/fedex/tracking_response_with_blank_state.xml +107 -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/access_request.xml +6 -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/delivery_dates_response.xml +140 -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/package_exceeds_maximum_length.xml +12 -0
- data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
- data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -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 +290 -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/tracking_request.xml +9 -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/ups/void_shipment_response.xml +11 -0
- data/test/fixtures/xml/usps/api_error_rate_response.xml +53 -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/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_response.xml +10 -0
- data/test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml +2 -0
- data/test/fixtures/xml/usps/tracking_request.xml +10 -0
- data/test/fixtures/xml/usps/tracking_request_batch.xml +12 -0
- data/test/fixtures/xml/usps/tracking_response.xml +162 -0
- data/test/fixtures/xml/usps/tracking_response_alt.xml +53 -0
- data/test/fixtures/xml/usps/tracking_response_batch.xml +231 -0
- data/test/fixtures/xml/usps/tracking_response_failure.xml +11 -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/us_rate_request_large.xml +18 -0
- data/test/fixtures/xml/usps/world_rate_request_only_country.xml +22 -0
- data/test/fixtures/xml/usps/world_rate_request_with_value.xml +24 -0
- data/test/fixtures/xml/usps/world_rate_request_without_value.xml +24 -0
- data/test/fixtures/xml/usps_returns/external_return_label_response.xml +2 -0
- data/test/fixtures/xml/usps_returns/external_return_label_response_failure.xml +10 -0
- data/test/remote/australia_post_test.rb +140 -0
- data/test/remote/canada_post_pws_platform_test.rb +259 -0
- data/test/remote/canada_post_pws_test.rb +169 -0
- data/test/remote/canada_post_test.rb +55 -0
- data/test/remote/fedex_test.rb +400 -0
- data/test/remote/kunaki_test.rb +37 -0
- data/test/remote/new_zealand_post_test.rb +149 -0
- data/test/remote/shipwire_test.rb +84 -0
- data/test/remote/stamps_test.rb +396 -0
- data/test/remote/usps_returns_test.rb +72 -0
- data/test/remote/usps_test.rb +243 -0
- data/test/test_helper.rb +296 -0
- data/test/unit/carrier_test.rb +130 -0
- data/test/unit/carriers/australia_post_test.rb +181 -0
- data/test/unit/carriers/benchmark_test.rb +18 -0
- data/test/unit/carriers/canada_post_pws_rating_test.rb +379 -0
- data/test/unit/carriers/canada_post_pws_register_test.rb +76 -0
- data/test/unit/carriers/canada_post_pws_shipping_test.rb +258 -0
- data/test/unit/carriers/canada_post_pws_test.rb +59 -0
- data/test/unit/carriers/canada_post_pws_tracking_test.rb +154 -0
- data/test/unit/carriers/canada_post_test.rb +148 -0
- data/test/unit/carriers/fedex_test.rb +693 -0
- data/test/unit/carriers/kunaki_test.rb +56 -0
- data/test/unit/carriers/new_zealand_post_test.rb +177 -0
- data/test/unit/carriers/shipwire_test.rb +188 -0
- data/test/unit/carriers/stamps_test.rb +245 -0
- data/test/unit/carriers/ups_test.rb +580 -0
- data/test/unit/carriers/usps_returns_test.rb +45 -0
- data/test/unit/carriers/usps_test.rb +633 -0
- data/test/unit/carriers_test.rb +16 -0
- data/test/unit/external_return_label_request_test.rb +258 -0
- data/test/unit/location_test.rb +234 -0
- data/test/unit/package_item_test.rb +232 -0
- data/test/unit/package_test.rb +404 -0
- data/test/unit/rate_estimate_test.rb +93 -0
- data/test/unit/response_test.rb +38 -0
- data/test/unit/shipment_event_test.rb +20 -0
- data/test/unit/shipment_packer_test.rb +212 -0
- data/test/unit/tracking_response_test.rb +41 -0
- metadata +684 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
module ReactiveShipping
|
2
|
+
class Kunaki < Carrier
|
3
|
+
self.retry_safe = true
|
4
|
+
|
5
|
+
cattr_reader :name
|
6
|
+
@@name = "Kunaki"
|
7
|
+
|
8
|
+
URL = 'http://Kunaki.com/XMLService.ASP'
|
9
|
+
|
10
|
+
CARRIERS = ["UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL"]
|
11
|
+
|
12
|
+
COUNTRIES = {
|
13
|
+
'AR' => 'Argentina',
|
14
|
+
'AU' => 'Australia',
|
15
|
+
'AT' => 'Austria',
|
16
|
+
'BE' => 'Belgium',
|
17
|
+
'BR' => 'Brazil',
|
18
|
+
'BG' => 'Bulgaria',
|
19
|
+
'CA' => 'Canada',
|
20
|
+
'CN' => 'China',
|
21
|
+
'CY' => 'Cyprus',
|
22
|
+
'CZ' => 'Czech Republic',
|
23
|
+
'DK' => 'Denmark',
|
24
|
+
'EE' => 'Estonia',
|
25
|
+
'FI' => 'Finland',
|
26
|
+
'FR' => 'France',
|
27
|
+
'DE' => 'Germany',
|
28
|
+
'GI' => 'Gibraltar',
|
29
|
+
'GR' => 'Greece',
|
30
|
+
'GL' => 'Greenland',
|
31
|
+
'HK' => 'Hong Kong',
|
32
|
+
'HU' => 'Hungary',
|
33
|
+
'IS' => 'Iceland',
|
34
|
+
'IE' => 'Ireland',
|
35
|
+
'IL' => 'Israel',
|
36
|
+
'IT' => 'Italy',
|
37
|
+
'JP' => 'Japan',
|
38
|
+
'LV' => 'Latvia',
|
39
|
+
'LI' => 'Liechtenstein',
|
40
|
+
'LT' => 'Lithuania',
|
41
|
+
'LU' => 'Luxembourg',
|
42
|
+
'MX' => 'Mexico',
|
43
|
+
'NL' => 'Netherlands',
|
44
|
+
'NZ' => 'New Zealand',
|
45
|
+
'NO' => 'Norway',
|
46
|
+
'PL' => 'Poland',
|
47
|
+
'PT' => 'Portugal',
|
48
|
+
'RO' => 'Romania',
|
49
|
+
'RU' => 'Russia',
|
50
|
+
'SG' => 'Singapore',
|
51
|
+
'SK' => 'Slovakia',
|
52
|
+
'SI' => 'Slovenia',
|
53
|
+
'ES' => 'Spain',
|
54
|
+
'SE' => 'Sweden',
|
55
|
+
'CH' => 'Switzerland',
|
56
|
+
'TW' => 'Taiwan',
|
57
|
+
'TR' => 'Turkey',
|
58
|
+
'UA' => 'Ukraine',
|
59
|
+
'GB' => 'United Kingdom',
|
60
|
+
'US' => 'United States',
|
61
|
+
'VA' => 'Vatican City',
|
62
|
+
'RS' => 'Yugoslavia',
|
63
|
+
'ME' => 'Yugoslavia'
|
64
|
+
}
|
65
|
+
|
66
|
+
def find_rates(origin, destination, packages, options = {})
|
67
|
+
requires!(options, :items)
|
68
|
+
commit(origin, destination, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def valid_credentials?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def build_request(destination, options)
|
78
|
+
country = COUNTRIES[destination.country_code]
|
79
|
+
state_province = %w(US CA).include?(destination.country_code.to_s) ? destination.state : ''
|
80
|
+
|
81
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
82
|
+
xml.ShippingOptions do
|
83
|
+
xml.AddressInfo do
|
84
|
+
xml.Country(country)
|
85
|
+
xml.State_Province(state_province)
|
86
|
+
xml.PostalCode(destination.zip)
|
87
|
+
end
|
88
|
+
|
89
|
+
options[:items].each do |item|
|
90
|
+
xml.Product do
|
91
|
+
xml.ProductId(item[:sku])
|
92
|
+
xml.Quantity(item[:quantity])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
builder.to_xml
|
98
|
+
end
|
99
|
+
|
100
|
+
def commit(origin, destination, options)
|
101
|
+
request = build_request(destination, options)
|
102
|
+
response = parse( ssl_post(URL, request, "Content-Type" => "text/xml") )
|
103
|
+
|
104
|
+
RateResponse.new(success?(response), message_from(response), response,
|
105
|
+
:rates => build_rate_estimates(response, origin, destination)
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_rate_estimates(response, origin, destination)
|
110
|
+
response["Options"].collect do |quote|
|
111
|
+
RateEstimate.new(origin, destination, carrier_for(quote["Description"]), quote["Description"],
|
112
|
+
:total_price => quote["Price"],
|
113
|
+
:currency => "USD"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def carrier_for(service)
|
119
|
+
CARRIERS.dup.find { |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse(xml)
|
123
|
+
response = {}
|
124
|
+
response["Options"] = []
|
125
|
+
|
126
|
+
document = Nokogiri.XML(sanitize(xml))
|
127
|
+
|
128
|
+
response["ErrorCode"] = document.at('/Response/ErrorCode').text
|
129
|
+
response["ErrorText"] = document.at('/Response/ErrorText').text
|
130
|
+
|
131
|
+
document.xpath("Response/Option").each do |node|
|
132
|
+
rate = {}
|
133
|
+
rate["Description"] = node.at("Description").text
|
134
|
+
rate["Price"] = node.at("Price").text
|
135
|
+
response["Options"] << rate
|
136
|
+
end
|
137
|
+
response
|
138
|
+
end
|
139
|
+
|
140
|
+
def sanitize(response)
|
141
|
+
result = response.to_s
|
142
|
+
result.gsub!("\r\n", "")
|
143
|
+
result.gsub!(/<(\/)?(BODY|HTML)>/, '')
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
def success?(response)
|
148
|
+
response["ErrorCode"] == "0"
|
149
|
+
end
|
150
|
+
|
151
|
+
def message_from(response)
|
152
|
+
response["ErrorText"]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
module ReactiveShipping
|
2
|
+
class NewZealandPost < Carrier
|
3
|
+
cattr_reader :name
|
4
|
+
@@name = "New Zealand Post"
|
5
|
+
|
6
|
+
URL = "http://api.nzpost.co.nz/ratefinder"
|
7
|
+
|
8
|
+
def requirements
|
9
|
+
[:key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_rates(origin, destination, packages, options = {})
|
13
|
+
options = @options.merge(options)
|
14
|
+
request = RateRequest.from(origin, destination, packages, options)
|
15
|
+
request.raw_responses = commit(request.urls) if request.new_zealand_origin?
|
16
|
+
request.rate_response
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def commit(urls)
|
22
|
+
save_request(urls).map { |url| ssl_get(url) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.default_location
|
26
|
+
Location.new(
|
27
|
+
:country => "NZ",
|
28
|
+
:city => "Wellington",
|
29
|
+
:address1 => "22 Waterloo Quay",
|
30
|
+
:address2 => "Pipitea",
|
31
|
+
:postal_code => "6011"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
class NewZealandPostRateResponse < RateResponse
|
36
|
+
attr_reader :raw_responses
|
37
|
+
|
38
|
+
def initialize(success, message, params = {}, options = {})
|
39
|
+
@raw_responses = options[:raw_responses]
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class RateRequest
|
45
|
+
attr_reader :urls
|
46
|
+
attr_writer :raw_responses
|
47
|
+
|
48
|
+
def self.from(*args)
|
49
|
+
return International.new(*args) unless domestic?(args[0..1])
|
50
|
+
Domestic.new(*args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(origin, destination, packages, options)
|
54
|
+
@origin = Location.from(origin)
|
55
|
+
@destination = Location.from(destination)
|
56
|
+
@packages = Array(packages).map { |package| NewZealandPostPackage.new(package, api) }
|
57
|
+
@params = { :format => "json", :api_key => options[:key] }
|
58
|
+
@test = options[:test]
|
59
|
+
@rates = @responses = @raw_responses = []
|
60
|
+
@urls = @packages.map { |package| url(package) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def rate_response
|
64
|
+
@rates = rates
|
65
|
+
NewZealandPostRateResponse.new(true, "success", response_params, response_options)
|
66
|
+
rescue => error
|
67
|
+
NewZealandPostRateResponse.new(false, error.message, response_params, response_options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def new_zealand_origin?
|
71
|
+
self.class.new_zealand?(@origin)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def self.new_zealand?(location)
|
77
|
+
['NZ', nil].include?(Location.from(location).country_code)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.domestic?(locations)
|
81
|
+
locations.select { |location| new_zealand?(location) }.size == 2
|
82
|
+
end
|
83
|
+
|
84
|
+
def response_options
|
85
|
+
{
|
86
|
+
:rates => @rates,
|
87
|
+
:raw_responses => @raw_responses,
|
88
|
+
:request => @urls,
|
89
|
+
:test => @test
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def response_params
|
94
|
+
{ :responses => @responses }
|
95
|
+
end
|
96
|
+
|
97
|
+
def rate_options(products)
|
98
|
+
{
|
99
|
+
:total_price => products.sum { |product| price(product) },
|
100
|
+
:currency => "NZD",
|
101
|
+
:service_code => products.first["code"]
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def rates
|
106
|
+
rates_hash.map do |service, products|
|
107
|
+
RateEstimate.new(@origin, @destination, NewZealandPost.name, service, rate_options(products))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def rates_hash
|
112
|
+
products_hash.select { |_service, products| products.size == @packages.size }
|
113
|
+
end
|
114
|
+
|
115
|
+
def products_hash
|
116
|
+
product_arrays.flatten.group_by { |product| service_name(product) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def product_arrays
|
120
|
+
responses.map do |response|
|
121
|
+
raise(response["message"]) unless response["status"] == "success"
|
122
|
+
response["products"]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def responses
|
127
|
+
@responses = @raw_responses.map { |response| parse_response(response) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def parse_response(response)
|
131
|
+
JSON.parse(response)
|
132
|
+
end
|
133
|
+
|
134
|
+
def url(package)
|
135
|
+
"#{URL}/#{api}?#{params(package).to_query}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def params(package)
|
139
|
+
@params.merge(api_params).merge(package.params)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Domestic < RateRequest
|
144
|
+
def service_name(product)
|
145
|
+
[product["service_group_description"], product["description"]].join(" ")
|
146
|
+
end
|
147
|
+
|
148
|
+
def api
|
149
|
+
:domestic
|
150
|
+
end
|
151
|
+
|
152
|
+
def api_params
|
153
|
+
{
|
154
|
+
:postcode_src => @origin.postal_code,
|
155
|
+
:postcode_dest => @destination.postal_code,
|
156
|
+
:carrier => "all"
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def price(product)
|
161
|
+
product["cost"].to_f
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class International < RateRequest
|
166
|
+
def rates
|
167
|
+
raise "New Zealand Post packages must originate in New Zealand" unless new_zealand_origin?
|
168
|
+
super
|
169
|
+
end
|
170
|
+
|
171
|
+
def service_name(product)
|
172
|
+
[product["group"], product["name"]].join(" ")
|
173
|
+
end
|
174
|
+
|
175
|
+
def api
|
176
|
+
:international
|
177
|
+
end
|
178
|
+
|
179
|
+
def api_params
|
180
|
+
{ :country_code => @destination.country_code }
|
181
|
+
end
|
182
|
+
|
183
|
+
def price(product)
|
184
|
+
product["price"].to_f
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class NewZealandPostPackage
|
189
|
+
def initialize(package, api)
|
190
|
+
@package = package
|
191
|
+
@api = api
|
192
|
+
@params = { :weight => weight, :length => length }
|
193
|
+
end
|
194
|
+
|
195
|
+
def params
|
196
|
+
@params.merge(api_params).merge(shape_params)
|
197
|
+
end
|
198
|
+
|
199
|
+
protected
|
200
|
+
|
201
|
+
def weight
|
202
|
+
@package.kg
|
203
|
+
end
|
204
|
+
|
205
|
+
def length
|
206
|
+
mm(:length)
|
207
|
+
end
|
208
|
+
|
209
|
+
def height
|
210
|
+
mm(:height)
|
211
|
+
end
|
212
|
+
|
213
|
+
def width
|
214
|
+
mm(:width)
|
215
|
+
end
|
216
|
+
|
217
|
+
def shape
|
218
|
+
return :cylinder if @package.cylinder?
|
219
|
+
:cuboid
|
220
|
+
end
|
221
|
+
|
222
|
+
def api_params
|
223
|
+
send("#{@api}_params")
|
224
|
+
end
|
225
|
+
|
226
|
+
def international_params
|
227
|
+
{ :value => value }
|
228
|
+
end
|
229
|
+
|
230
|
+
def domestic_params
|
231
|
+
{}
|
232
|
+
end
|
233
|
+
|
234
|
+
def shape_params
|
235
|
+
send("#{shape}_params")
|
236
|
+
end
|
237
|
+
|
238
|
+
def cuboid_params
|
239
|
+
{ :height => height, :thickness => width }
|
240
|
+
end
|
241
|
+
|
242
|
+
def cylinder_params
|
243
|
+
{ :diameter => width }
|
244
|
+
end
|
245
|
+
|
246
|
+
def mm(measurement)
|
247
|
+
@package.cm(measurement) * 10
|
248
|
+
end
|
249
|
+
|
250
|
+
def value
|
251
|
+
return 0 unless @package.value && currency == "NZD"
|
252
|
+
@package.value / 100
|
253
|
+
end
|
254
|
+
|
255
|
+
def currency
|
256
|
+
@package.currency || "NZD"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module ReactiveShipping
|
2
|
+
class Shipwire < Carrier
|
3
|
+
self.retry_safe = true
|
4
|
+
|
5
|
+
cattr_reader :name
|
6
|
+
@@name = "Shipwire"
|
7
|
+
|
8
|
+
URL = 'https://api.shipwire.com/exec/RateServices.php'
|
9
|
+
SCHEMA_URL = 'http://www.shipwire.com/exec/download/RateRequest.dtd'
|
10
|
+
WAREHOUSES = { 'CHI' => 'Chicago',
|
11
|
+
'LAX' => 'Los Angeles',
|
12
|
+
'REN' => 'Reno',
|
13
|
+
'VAN' => 'Vancouver',
|
14
|
+
'TOR' => 'Toronto',
|
15
|
+
'UK' => 'United Kingdom'
|
16
|
+
}
|
17
|
+
|
18
|
+
CARRIERS = ["UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL"]
|
19
|
+
|
20
|
+
SUCCESS = "OK"
|
21
|
+
SUCCESS_MESSAGE = "Successfully received the shipping rates"
|
22
|
+
NO_RATES_MESSAGE = "No shipping rates could be found for the destination address"
|
23
|
+
REQUIRED_OPTIONS = [:login, :password].freeze
|
24
|
+
|
25
|
+
def find_rates(origin, destination, packages, options = {})
|
26
|
+
requires!(options, :items)
|
27
|
+
commit(origin, destination, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid_credentials?
|
31
|
+
location = self.class.default_location
|
32
|
+
find_rates(location, location, Package.new(100, [5, 15, 30]),
|
33
|
+
:items => [{ :sku => '', :quantity => 1 }]
|
34
|
+
)
|
35
|
+
rescue ReactiveShipping::ResponseError
|
36
|
+
true
|
37
|
+
rescue ActiveUtils::ResponseError => e
|
38
|
+
e.response.code != '401'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def requirements
|
44
|
+
REQUIRED_OPTIONS
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_request(destination, options)
|
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
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_credentials(xml)
|
58
|
+
xml.EmailAddress @options[:login]
|
59
|
+
xml.Password @options[:password]
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_order(xml, destination, options)
|
63
|
+
xml.Order :id => options[:order_id] do
|
64
|
+
xml.Warehouse options[:warehouse] || '00'
|
65
|
+
|
66
|
+
add_address(xml, destination)
|
67
|
+
Array(options[:items]).each_with_index do |line_item, index|
|
68
|
+
add_item(xml, line_item, index)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_address(xml, destination)
|
74
|
+
xml.AddressInfo :type => 'Ship' do
|
75
|
+
if destination.name.present?
|
76
|
+
xml.Name do
|
77
|
+
xml.Full destination.name
|
78
|
+
end
|
79
|
+
end
|
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?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Code is limited to 12 characters
|
92
|
+
def add_item(xml, item, index)
|
93
|
+
xml.Item :num => index do
|
94
|
+
xml.Code item[:sku]
|
95
|
+
xml.Quantity item[:quantity]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def commit(origin, destination, options)
|
100
|
+
request = build_request(destination, options)
|
101
|
+
save_request(request)
|
102
|
+
|
103
|
+
response = parse( ssl_post(URL, "RateRequestXML=#{CGI.escape(request)}") )
|
104
|
+
|
105
|
+
RateResponse.new(response["success"], response["message"], response,
|
106
|
+
:xml => response,
|
107
|
+
:rates => build_rate_estimates(response, origin, destination),
|
108
|
+
:request => last_request
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_rate_estimates(response, origin, destination)
|
113
|
+
response["rates"].collect do |quote|
|
114
|
+
RateEstimate.new(origin, destination, carrier_for(quote["service"]), quote["service"],
|
115
|
+
:service_code => quote["method"],
|
116
|
+
:total_price => quote["cost"],
|
117
|
+
:currency => quote["currency"],
|
118
|
+
:delivery_range => [timestamp_from_business_day(quote["delivery_min"]),
|
119
|
+
timestamp_from_business_day(quote["delivery_max"])]
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def carrier_for(service)
|
125
|
+
CARRIERS.dup.find { |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse(xml)
|
129
|
+
response = {}
|
130
|
+
response["rates"] = []
|
131
|
+
|
132
|
+
document = Nokogiri.XML(xml)
|
133
|
+
|
134
|
+
response["status"] = parse_child_text(document.root, "Status")
|
135
|
+
|
136
|
+
document.root.xpath("Order/Quotes/Quote").each do |e|
|
137
|
+
rate = {}
|
138
|
+
rate["method"] = e["method"]
|
139
|
+
rate["warehouse"] = parse_child_text(e, "Warehouse")
|
140
|
+
rate["service"] = parse_child_text(e, "Service")
|
141
|
+
rate["cost"] = parse_child_text(e, "Cost")
|
142
|
+
rate["currency"] = parse_child_attribute(e, "Cost", "currency")
|
143
|
+
if delivery_estimate = e.at("DeliveryEstimate")
|
144
|
+
rate["delivery_min"] = parse_child_text(delivery_estimate, "Minimum").to_i
|
145
|
+
rate["delivery_max"] = parse_child_text(delivery_estimate, "Maximum").to_i
|
146
|
+
end
|
147
|
+
response["rates"] << rate
|
148
|
+
end
|
149
|
+
|
150
|
+
if response["status"] == SUCCESS && response["rates"].any?
|
151
|
+
response["success"] = true
|
152
|
+
response["message"] = SUCCESS_MESSAGE
|
153
|
+
elsif response["status"] == SUCCESS && response["rates"].empty?
|
154
|
+
response["success"] = false
|
155
|
+
response["message"] = NO_RATES_MESSAGE
|
156
|
+
else
|
157
|
+
response["success"] = false
|
158
|
+
response["message"] = parse_child_text(document.root, "ErrorMessage")
|
159
|
+
end
|
160
|
+
|
161
|
+
response
|
162
|
+
rescue NoMethodError => e
|
163
|
+
raise ReactiveShipping::ResponseContentError.new(e, xml)
|
164
|
+
end
|
165
|
+
|
166
|
+
def parse_child_text(parent, name)
|
167
|
+
if element = parent.at(name)
|
168
|
+
element.text
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def parse_child_attribute(parent, name, attribute)
|
173
|
+
if element = parent.at(name)
|
174
|
+
element[attribute]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|