reactive_shipping 3.0.0
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 +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,248 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class AustraliaPost < Carrier
|
|
3
|
+
cattr_reader :name
|
|
4
|
+
@@name = 'Australia Post'
|
|
5
|
+
|
|
6
|
+
HOST = 'digitalapi.auspost.com.au'
|
|
7
|
+
|
|
8
|
+
PARCEL_ENDPOINTS = {
|
|
9
|
+
service: {
|
|
10
|
+
domestic: '/postage/parcel/domestic/service.json',
|
|
11
|
+
international: '/postage/parcel/international/service.json'
|
|
12
|
+
},
|
|
13
|
+
calculate: {
|
|
14
|
+
domestic: '/postage/parcel/domestic/calculate.json',
|
|
15
|
+
international: '/postage/parcel/international/calculate.json'
|
|
16
|
+
}
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def requirements
|
|
20
|
+
[:api_key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_rates(origin, destination, packages, options = {})
|
|
24
|
+
packages = Array(packages)
|
|
25
|
+
|
|
26
|
+
service_requests = packages.map do |package|
|
|
27
|
+
service_request = ServiceRequest.new(origin, destination, package, options)
|
|
28
|
+
|
|
29
|
+
service_request.parse(commit(service_request.url))
|
|
30
|
+
service_request
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
combined_response = CombinedResponse.new(origin, destination, packages, service_requests)
|
|
34
|
+
|
|
35
|
+
RateResponse.new(true, 'success', combined_response.params, combined_response.options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def calculate_rates(origin, destination, packages, service_code, options = {})
|
|
39
|
+
packages = Array(packages)
|
|
40
|
+
|
|
41
|
+
calculate_requests = packages.map do |package|
|
|
42
|
+
calculate_request = CalculateRequest.new(origin, destination, package, service_code, options)
|
|
43
|
+
|
|
44
|
+
calculate_request.parse(commit(calculate_request.url))
|
|
45
|
+
calculate_request
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
combined_response = CombinedResponse.new(origin, destination, packages, calculate_requests)
|
|
49
|
+
|
|
50
|
+
RateResponse.new(true, 'success', combined_response.params, combined_response.options)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def commit(request_url)
|
|
56
|
+
ssl_get(request_url, headers)
|
|
57
|
+
|
|
58
|
+
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
|
|
59
|
+
data = JSON.parse(e.response.body)
|
|
60
|
+
error_message = data['error'] && data['error']['errorMessage'] ? data['error']['errorMessage'] : 'unknown'
|
|
61
|
+
|
|
62
|
+
RateResponse.new(false, error_message, data)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def headers
|
|
66
|
+
{
|
|
67
|
+
'Content-type' => 'application/json',
|
|
68
|
+
'auth-key' => @options[:api_key]
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class CombinedResponse
|
|
73
|
+
|
|
74
|
+
def initialize(origin, destination, packages, requests)
|
|
75
|
+
@requests = requests
|
|
76
|
+
@origin = origin
|
|
77
|
+
@destination = destination
|
|
78
|
+
@packages = packages
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def options
|
|
82
|
+
{
|
|
83
|
+
rates: rates,
|
|
84
|
+
raw_responses: @requests.map(&:raw_response),
|
|
85
|
+
request: @requests.map(&:url)
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def params
|
|
90
|
+
{
|
|
91
|
+
responses: @requests.map(&:response)
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def rate_options(rates)
|
|
98
|
+
{
|
|
99
|
+
service_name: rates.first[:service_name],
|
|
100
|
+
service_code: rates.first[:service_code],
|
|
101
|
+
total_price: rates.sum { |rate| rate[:total_price] },
|
|
102
|
+
currency: 'AUD',
|
|
103
|
+
delivery_time_text: rates.first[:delivery_time_text]
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def rates
|
|
108
|
+
rates = @requests.map(&:rates).flatten
|
|
109
|
+
|
|
110
|
+
rates.group_by { |rate| rate[:service_name] }.map do |service_name, service_rates|
|
|
111
|
+
next unless service_rates.size == @packages.size
|
|
112
|
+
|
|
113
|
+
AustraliaPostRateEstimate.new(@origin, @destination, AustraliaPost.name, service_name, rate_options(service_rates))
|
|
114
|
+
end.compact
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class AustraliaPostRequest
|
|
120
|
+
attr_reader :raw_response
|
|
121
|
+
attr_reader :response
|
|
122
|
+
attr_reader :rates
|
|
123
|
+
|
|
124
|
+
def initialize(origin, destination, package, options)
|
|
125
|
+
@origin = Location.from(origin)
|
|
126
|
+
@destination = Location.from(destination)
|
|
127
|
+
@package = package
|
|
128
|
+
@rates = []
|
|
129
|
+
@options = options
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def url
|
|
133
|
+
endpoint = domestic_destination? ? @endpoints[:domestic] : @endpoints[:international]
|
|
134
|
+
params = domestic_destination? ? domestic_params : international_params
|
|
135
|
+
|
|
136
|
+
URI::HTTPS.build(host: HOST, path: endpoint, query: params.to_query).to_s
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse(data)
|
|
140
|
+
@raw_response = data
|
|
141
|
+
@response = JSON.parse(data)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
protected
|
|
145
|
+
|
|
146
|
+
def domestic_destination?
|
|
147
|
+
@destination.country_code == 'AU'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def domestic_params
|
|
151
|
+
{
|
|
152
|
+
length: @package.cm(:length),
|
|
153
|
+
width: @package.cm(:width),
|
|
154
|
+
height: @package.cm(:height),
|
|
155
|
+
weight: @package.kilograms.to_f.round(2),
|
|
156
|
+
from_postcode: @origin.postal_code,
|
|
157
|
+
to_postcode: @destination.postal_code
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def international_params
|
|
162
|
+
{
|
|
163
|
+
weight: @package.kilograms.to_f.round(2),
|
|
164
|
+
country_code: @destination.country_code
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class ServiceRequest < AustraliaPostRequest
|
|
171
|
+
|
|
172
|
+
def initialize(origin, destination, package, options)
|
|
173
|
+
super
|
|
174
|
+
@endpoints = PARCEL_ENDPOINTS[:service]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def parse(data)
|
|
178
|
+
super
|
|
179
|
+
|
|
180
|
+
@rates = response['services']['service'].map do |service|
|
|
181
|
+
{
|
|
182
|
+
service_name: service['name'],
|
|
183
|
+
service_code: service['code'],
|
|
184
|
+
total_price: service['price'].to_f,
|
|
185
|
+
currency: 'AUD'
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
class CalculateRequest < AustraliaPostRequest
|
|
193
|
+
attr_reader :service_code
|
|
194
|
+
|
|
195
|
+
def initialize(origin, destination, package, service_code, options)
|
|
196
|
+
super(origin, destination, package, options)
|
|
197
|
+
|
|
198
|
+
@service_code = service_code
|
|
199
|
+
@endpoints = PARCEL_ENDPOINTS[:calculate]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def parse(data)
|
|
203
|
+
super
|
|
204
|
+
postage_result = response['postage_result']
|
|
205
|
+
|
|
206
|
+
@rates = [{
|
|
207
|
+
service_name: postage_result['service'],
|
|
208
|
+
service_code: service_code,
|
|
209
|
+
total_price: postage_result['total_cost'].to_f,
|
|
210
|
+
currency: 'AUD',
|
|
211
|
+
delivery_time_text: postage_result['delivery_time']
|
|
212
|
+
}]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
def calculate_params
|
|
218
|
+
{
|
|
219
|
+
service_code: @service_code,
|
|
220
|
+
option_code: @options[:option_code],
|
|
221
|
+
suboption_code: @options[:suboption_code],
|
|
222
|
+
extra_cover: @options[:extra_cover]
|
|
223
|
+
}.
|
|
224
|
+
# INFO: equivalent of .compact
|
|
225
|
+
select { |_, value| !value.nil? }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def domestic_params
|
|
229
|
+
super.merge(calculate_params)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def international_params
|
|
233
|
+
super.merge(calculate_params)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
class AustraliaPostRateEstimate < RateEstimate
|
|
239
|
+
attr_reader :delivery_time_text
|
|
240
|
+
|
|
241
|
+
def initialize(origin, destination, carrier, service_name, options = {})
|
|
242
|
+
super
|
|
243
|
+
@delivery_time_text = options[:delivery_time_text]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# The time a 3rd-party shipping provider takes to respond to a request varies greatly.
|
|
2
|
+
# This class simulates these unpredictable delays in shipping rate retrieval so that
|
|
3
|
+
# load-testing tools run into situations that more accurately reflect the real world.
|
|
4
|
+
|
|
5
|
+
module ReactiveShipping
|
|
6
|
+
class BenchmarkCarrier < Carrier
|
|
7
|
+
cattr_reader :name
|
|
8
|
+
@@name = "Benchmark Carrier"
|
|
9
|
+
|
|
10
|
+
def find_rates(origin, destination, packages, options = {})
|
|
11
|
+
origin = Location.from(origin)
|
|
12
|
+
destination = Location.from(destination)
|
|
13
|
+
packages = Array(packages)
|
|
14
|
+
|
|
15
|
+
delay_time = generate_simulated_lag
|
|
16
|
+
|
|
17
|
+
bogus_estimate = RateEstimate.new(
|
|
18
|
+
origin, destination, @@name,
|
|
19
|
+
"Free Benchmark Shipping", :total_price => 0, :currency => 'USD',
|
|
20
|
+
:packages => packages, :delivery_range => [Time.now.utc.strftime("%Y-%d-%m"), Time.now.utc.strftime("%Y-%d-%m")]
|
|
21
|
+
)
|
|
22
|
+
RateResponse.new(true, "Success (delayed #{delay_time} seconds)", {:rate => 'free'}, :rates => [bogus_estimate], :xml => "<rate>free</rate>")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def generate_simulated_lag(max_delay = 30)
|
|
28
|
+
sleep Random.rand * max_delay
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class BogusCarrier < Carrier
|
|
3
|
+
cattr_reader :name
|
|
4
|
+
@@name = "Bogus Carrier"
|
|
5
|
+
|
|
6
|
+
def find_rates(origin, destination, packages, options = {})
|
|
7
|
+
origin = Location.from(origin)
|
|
8
|
+
destination = Location.from(destination)
|
|
9
|
+
packages = Array(packages)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class CanadaPost < Carrier
|
|
3
|
+
# NOTE!
|
|
4
|
+
# A Merchant CPC Id must be assigned to you by Canada Post
|
|
5
|
+
# CPC_DEMO_XML is just a public domain account for testing
|
|
6
|
+
|
|
7
|
+
class CanadaPostRateResponse < RateResponse
|
|
8
|
+
attr_reader :boxes, :postal_outlets
|
|
9
|
+
|
|
10
|
+
def initialize(success, message, params = {}, options = {})
|
|
11
|
+
@boxes = options[:boxes]
|
|
12
|
+
@postal_outlets = options[:postal_outlets]
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
cattr_reader :name, :name_french
|
|
18
|
+
@@name = "Canada Post"
|
|
19
|
+
@@name_french = "Postes Canada"
|
|
20
|
+
|
|
21
|
+
Box = Struct.new(:name, :weight, :expediter_weight, :length, :width, :height, :packedItems)
|
|
22
|
+
PackedItem = Struct.new(:quantity, :description)
|
|
23
|
+
PostalOutlet = Struct.new(:sequence_no, :distance, :name, :business_name, :postal_address, :business_hours)
|
|
24
|
+
|
|
25
|
+
URL = "http://sellonline.canadapost.ca:30000"
|
|
26
|
+
DTD_NAME = 'eparcel'
|
|
27
|
+
DTD_URI = "http://sellonline.canadapost.ca/DevelopersResources/protocolV3/eParcel.dtd"
|
|
28
|
+
|
|
29
|
+
RESPONSE_CODES = {
|
|
30
|
+
'1' => "All calculation was done",
|
|
31
|
+
'2' => "Default shipping rates are returned due to a problem during the processing of the request.",
|
|
32
|
+
'-2' => "Missing argument when calling module",
|
|
33
|
+
'-5' => "No Item to ship",
|
|
34
|
+
'-6' => "Illegal Item weight",
|
|
35
|
+
'-7' => "Illegal item dimension",
|
|
36
|
+
'-12' => "Can't open IM config file",
|
|
37
|
+
'-13' => "Can't create log files",
|
|
38
|
+
'-15' => "Invalid config file format",
|
|
39
|
+
'-102' => "Invalid socket connection",
|
|
40
|
+
'-106' => "Can't connect to server",
|
|
41
|
+
'-1000' => "Unknow request type sent by client",
|
|
42
|
+
'-1002' => "MAS Timed out",
|
|
43
|
+
'-1004' => "Socket communication break",
|
|
44
|
+
'-1005' => "Did not receive required data on socket.",
|
|
45
|
+
'-2000' => "Unable to estabish socket connection with RSSS",
|
|
46
|
+
'-2001' => "Merchant Id not found on server",
|
|
47
|
+
'-2002' => "One or more parameter was not sent by the IM to the MAS",
|
|
48
|
+
'-2003' => "Did not receive required data on socket.",
|
|
49
|
+
'-2004' => "The request contains to many items to process it.",
|
|
50
|
+
'-2005' => "The request received on socket is larger than the maximum allowed.",
|
|
51
|
+
'-3000' => "Origin Postal Code is illegal",
|
|
52
|
+
'-3001' => "Destination Postal Code/State Name/ Country is illegal",
|
|
53
|
+
'-3002' => "Parcel too large to be shipped with CPC",
|
|
54
|
+
'-3003' => "Parcel too small to be shipped with CPC",
|
|
55
|
+
'-3004' => "Parcel too heavy to be shipped with CPC",
|
|
56
|
+
'-3005' => "Internal error code returned by the rating DLL",
|
|
57
|
+
'-3006' => "The pick up time format is invalid or not defined.",
|
|
58
|
+
'-4000' => "Volumetric internal error",
|
|
59
|
+
'-4001' => "Volumetric time out calculation error.",
|
|
60
|
+
'-4002' => "No bins provided to the volumetric engine.",
|
|
61
|
+
'-4003' => "No items provided to the volumetric engine.",
|
|
62
|
+
'-4004' => "Item is too large to be packed",
|
|
63
|
+
'-4005' => "Number of item more than maximum allowed",
|
|
64
|
+
'-5000' => "XML Parsing error",
|
|
65
|
+
'-5001' => "XML Tag not found",
|
|
66
|
+
'-5002' => "Node Value Number format error",
|
|
67
|
+
'-5003' => "Node value is empty",
|
|
68
|
+
'-5004' => "Unable to create/parse XML Document",
|
|
69
|
+
'-6000' => "Unable to open the database",
|
|
70
|
+
'-6001' => "Unable to read from the database",
|
|
71
|
+
'-6002' => "Unable to write to the database",
|
|
72
|
+
'-50000' => "Internal problem - Please contact Sell Online Help Desk"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
NON_ISO_COUNTRY_NAMES = {
|
|
76
|
+
'Russian Federation' => 'Russia'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def requirements
|
|
80
|
+
[:login]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_rates(origin, destination, line_items = [], options = {})
|
|
84
|
+
rate_request = build_rate_request(origin, destination, line_items, options)
|
|
85
|
+
commit(rate_request, origin, destination, options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def maximum_weight
|
|
89
|
+
Measured::Weight.new(30, :kg)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def maximum_address_field_length
|
|
93
|
+
# https://www.canadapost.ca/cpo/mc/business/productsservices/developers/services/shippingmanifest/createshipment.jsf
|
|
94
|
+
44
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.default_location
|
|
98
|
+
{
|
|
99
|
+
:country => 'CA',
|
|
100
|
+
:province => 'ON',
|
|
101
|
+
:city => 'Ottawa',
|
|
102
|
+
:address1 => '61A York St',
|
|
103
|
+
:postal_code => 'K1N5T2'
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
protected
|
|
108
|
+
|
|
109
|
+
def commit(request, origin, destination, options = {})
|
|
110
|
+
parse_rate_response(ssl_post(URL, request), origin, destination, options)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def generate_xml(&block)
|
|
116
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
|
117
|
+
xml.doc.create_internal_subset(DTD_NAME, nil, DTD_URI)
|
|
118
|
+
yield(xml)
|
|
119
|
+
end
|
|
120
|
+
builder.to_xml
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_rate_request(origin, destination, line_items = [], options = {})
|
|
124
|
+
line_items = [line_items] unless line_items.is_a?(Array)
|
|
125
|
+
origin = origin.is_a?(Location) ? origin : Location.new(origin)
|
|
126
|
+
destination = destination.is_a?(Location) ? destination : Location.new(destination)
|
|
127
|
+
|
|
128
|
+
generate_xml do |xml|
|
|
129
|
+
xml.eparcel do
|
|
130
|
+
xml.language(@options[:french] ? 'fr' : 'en')
|
|
131
|
+
xml.ratesAndServicesRequest do
|
|
132
|
+
xml.merchantCPCID(@options[:login])
|
|
133
|
+
xml.fromPostalCode(origin.postal_code)
|
|
134
|
+
xml.turnAroundTime(options[:turn_around_time]) if options[:turn_around_time]
|
|
135
|
+
xml.itemsPrice(dollar_amount(line_items.map(&:value).compact.sum))
|
|
136
|
+
|
|
137
|
+
build_line_items(xml, line_items)
|
|
138
|
+
|
|
139
|
+
xml.city(destination.city)
|
|
140
|
+
xml.provOrState(destination.province)
|
|
141
|
+
xml.country(handle_non_iso_country_names(destination.country))
|
|
142
|
+
xml.postalCode(destination.postal_code)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def parse_rate_response(response, origin, destination, options = {})
|
|
149
|
+
xml = Nokogiri.XML(response)
|
|
150
|
+
success = response_success?(xml)
|
|
151
|
+
message = response_message(xml)
|
|
152
|
+
|
|
153
|
+
rate_estimates = []
|
|
154
|
+
boxes = []
|
|
155
|
+
if success
|
|
156
|
+
xml.xpath('eparcel/ratesAndServicesResponse/product').each do |product|
|
|
157
|
+
service_name = (@options[:french] ? @@name_french : @@name) + " " + product.at('name').text
|
|
158
|
+
service_code = product['id']
|
|
159
|
+
|
|
160
|
+
rate_estimates << RateEstimate.new(origin, destination, @@name, service_name,
|
|
161
|
+
:service_code => service_code,
|
|
162
|
+
:total_price => product.at('rate').text,
|
|
163
|
+
:currency => 'CAD',
|
|
164
|
+
:shipping_date => product.at('shippingDate').text,
|
|
165
|
+
:delivery_range => [product.at('deliveryDate').text] * 2
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
boxes = xml.xpath('eparcel/ratesAndServicesResponse/packing/box').map do |box|
|
|
170
|
+
b = Box.new
|
|
171
|
+
b.packedItems = []
|
|
172
|
+
b.name = box.at('name').text
|
|
173
|
+
b.weight = box.at('weight').text.to_f
|
|
174
|
+
b.expediter_weight = box.at('expediterWeight').text.to_f
|
|
175
|
+
b.length = box.at('length').text.to_f
|
|
176
|
+
b.width = box.at('width').text.to_f
|
|
177
|
+
b.height = box.at('height').text.to_f
|
|
178
|
+
b.packedItems = box.xpath('packedItem').map do |item|
|
|
179
|
+
p = PackedItem.new
|
|
180
|
+
p.quantity = item.at('quantity').text.to_i
|
|
181
|
+
p.description = item.at('description').text
|
|
182
|
+
p
|
|
183
|
+
end
|
|
184
|
+
b
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
postal_outlets = xml.xpath('eparcel/ratesAndServicesResponse/nearestPostalOutlet').map do |outlet|
|
|
188
|
+
postal_outlet = PostalOutlet.new
|
|
189
|
+
postal_outlet.sequence_no = outlet.at('postalOutletSequenceNo').text
|
|
190
|
+
postal_outlet.distance = outlet.at('distance').text
|
|
191
|
+
postal_outlet.name = outlet.at('outletName').text
|
|
192
|
+
postal_outlet.business_name = outlet.at('businessName').text
|
|
193
|
+
|
|
194
|
+
postal_outlet.postal_address = Location.new(
|
|
195
|
+
:address1 => outlet.at('postalAddress/addressLine').text,
|
|
196
|
+
:postal_code => outlet.at('postalAddress/postal_code').text,
|
|
197
|
+
:city => outlet.at('postalAddress/municipality').text,
|
|
198
|
+
:province => outlet.at('postalAddress/province').text,
|
|
199
|
+
:country => 'Canada',
|
|
200
|
+
:phone_number => outlet.at('phoneNumber').text
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
postal_outlet.business_hours = outlet.elements.collect('businessHours') do |hour|
|
|
204
|
+
{ :day_of_week => hour.at('dayOfWeek').text, :time => hour.at('time').text }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
postal_outlet
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
CanadaPostRateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :boxes => boxes, :postal_outlets => postal_outlets)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def response_success?(xml)
|
|
215
|
+
return false unless xml.at('eparcel/error').nil?
|
|
216
|
+
|
|
217
|
+
value = xml.at('eparcel/ratesAndServicesResponse/statusCode').text
|
|
218
|
+
value == '1' || value == '2'
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def response_message(xml)
|
|
222
|
+
if response_success?(xml)
|
|
223
|
+
xml.at('eparcel/ratesAndServicesResponse/statusMessage').text
|
|
224
|
+
else
|
|
225
|
+
xml.at('eparcel/error/statusMessage').text
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# <!-- List of items in the shopping -->
|
|
230
|
+
# <!-- cart -->
|
|
231
|
+
# <!-- Each item is defined by : -->
|
|
232
|
+
# <!-- - quantity (mandatory) -->
|
|
233
|
+
# <!-- - size (mandatory) -->
|
|
234
|
+
# <!-- - weight (mandatory) -->
|
|
235
|
+
# <!-- - description (mandatory) -->
|
|
236
|
+
# <!-- - ready to ship (optional) -->
|
|
237
|
+
|
|
238
|
+
def build_line_items(xml, line_items)
|
|
239
|
+
xml.lineItems do
|
|
240
|
+
line_items.each do |line_item|
|
|
241
|
+
xml.item do
|
|
242
|
+
xml.quantity(1)
|
|
243
|
+
xml.weight(line_item.kilograms)
|
|
244
|
+
xml.length(line_item.cm(:length).to_s)
|
|
245
|
+
xml.width(line_item.cm(:width).to_s)
|
|
246
|
+
xml.height(line_item.cm(:height).to_s)
|
|
247
|
+
xml.description(line_item.options[:description] || ' ')
|
|
248
|
+
xml.readyToShip(line_item.options[:ready_to_ship] || nil)
|
|
249
|
+
# By setting the 'readyToShip' tag to true, Sell Online will not pack this item in the boxes defined in the merchant profile.
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def dollar_amount(cents)
|
|
256
|
+
"%0.2f" % (cents / 100.0)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def handle_non_iso_country_names(country)
|
|
260
|
+
NON_ISO_COUNTRY_NAMES[country.to_s] || country
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|