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,33 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
|
|
3
|
+
# The `RateResponse` object is returned by the {ReactiveShipping::Carrier#find_rates}
|
|
4
|
+
# call. The most important method is {#rates}, which will return a list of possible
|
|
5
|
+
# shipping options with an estimated price.
|
|
6
|
+
#
|
|
7
|
+
# @note Some carriers provide more information than others, so not all attributes
|
|
8
|
+
# will be set, depending on what carrier you are using.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute rates
|
|
11
|
+
# The available rate options for the shipment, with an estimated price.
|
|
12
|
+
# @return [Array<ReactiveShipping::RateEstimate>]
|
|
13
|
+
class RateResponse < Response
|
|
14
|
+
|
|
15
|
+
attr_reader :rates
|
|
16
|
+
|
|
17
|
+
# Initializes a new RateResponse instance.
|
|
18
|
+
#
|
|
19
|
+
# @param success (see ReactiveShipping::Response#initialize)
|
|
20
|
+
# @param message (see ReactiveShipping::Response#initialize)
|
|
21
|
+
# @param params (see ReactiveShipping::Response#initialize)
|
|
22
|
+
# @option options (see ReactiveShipping::Response#initialize)
|
|
23
|
+
# @option options [Array<ReactiveShipping::RateEstimate>] :rates The rate estimates to
|
|
24
|
+
# populate the {#rates} method with.
|
|
25
|
+
def initialize(success, message, params = {}, options = {})
|
|
26
|
+
@rates = Array(options[:estimates] || options[:rates] || options[:rate_estimates])
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias_method :estimates, :rates
|
|
31
|
+
alias_method :rate_estimates, :rates
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ReactiveShipping #:nodoc:
|
|
2
|
+
|
|
3
|
+
# Basic Response class for requests against a carrier's API.
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :params
|
|
6
|
+
attr_reader :message
|
|
7
|
+
attr_reader :test
|
|
8
|
+
attr_reader :xml
|
|
9
|
+
attr_reader :request
|
|
10
|
+
|
|
11
|
+
# @param success [Boolean] Whether the request was considered successful, i.e. this
|
|
12
|
+
# response object will have the expected data set.
|
|
13
|
+
# @param message [String] A status message. Usuaully set when `success` is `false`,
|
|
14
|
+
# but can also be set for successful responses.
|
|
15
|
+
# @param params [Hash] Response parameters
|
|
16
|
+
# @param options [Hash]
|
|
17
|
+
# @option options [Boolean] :test (default: false) Whether this reponse was a result
|
|
18
|
+
# of a request executed against the sandbox or test environment of the carrier's API.
|
|
19
|
+
# @option options [String] :xml The raw XML of the response.
|
|
20
|
+
# @option options [String] :request The payload of the request.
|
|
21
|
+
# @option options [Boolean] :allow_failure Allows a failed response without raising.
|
|
22
|
+
def initialize(success, message, params = {}, options = {})
|
|
23
|
+
@success, @message, @params = success, message, params.stringify_keys
|
|
24
|
+
@test = options[:test] || false
|
|
25
|
+
@xml = options[:xml]
|
|
26
|
+
@request = options[:request]
|
|
27
|
+
raise ResponseError.new(self) unless success || options[:allow_failure]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Whether the request was executed successfully or not.
|
|
31
|
+
# @return [Boolean] Should only return `true` if the attributes of teh response
|
|
32
|
+
# instance are set with useful values.
|
|
33
|
+
def success?
|
|
34
|
+
@success ? true : false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Whether this request was executed against the sandbox or test environment instead of
|
|
38
|
+
# the production environment of the carrier.
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def test?
|
|
41
|
+
@test ? true : false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class ShipmentEvent
|
|
3
|
+
attr_reader :name, :time, :location, :message, :type_code
|
|
4
|
+
|
|
5
|
+
def initialize(name, time, location, message = nil, type_code = nil)
|
|
6
|
+
@name, @time, @location, @message, @type_code = name, time, location, message, type_code
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def delivered?
|
|
10
|
+
status == :delivered
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def status
|
|
14
|
+
@status ||= name.downcase.gsub("\s", "_").to_sym
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ==(other)
|
|
18
|
+
attributes = %i(name time location message type_code)
|
|
19
|
+
attributes.all? { |attr| self.public_send(attr) == other.public_send(attr) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class ShipmentPacker
|
|
3
|
+
class OverweightItem < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
EXCESS_PACKAGE_QUANTITY_THRESHOLD = 10_000
|
|
7
|
+
class ExcessPackageQuantity < StandardError; end
|
|
8
|
+
|
|
9
|
+
# items - array of hashes containing quantity, grams and price.
|
|
10
|
+
# ex. `[{:quantity => 2, :price => 1.0, :grams => 50}]`
|
|
11
|
+
# dimensions - `[5.0, 15.0, 30.0]`
|
|
12
|
+
# maximum_weight - maximum weight in grams
|
|
13
|
+
# currency - ISO currency code
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def pack(items, dimensions, maximum_weight, currency)
|
|
17
|
+
return [] if items.empty?
|
|
18
|
+
packages = []
|
|
19
|
+
items.map!(&:symbolize_keys)
|
|
20
|
+
|
|
21
|
+
# Naive in that it assumes weight is equally distributed across all items
|
|
22
|
+
# Should raise early enough in most cases
|
|
23
|
+
validate_total_weight(items, maximum_weight)
|
|
24
|
+
items_to_pack = items.map(&:dup).sort_by! { |i| i[:grams].to_i }
|
|
25
|
+
|
|
26
|
+
state = :package_empty
|
|
27
|
+
while state != :packing_finished
|
|
28
|
+
case state
|
|
29
|
+
when :package_empty
|
|
30
|
+
package_weight, package_value = 0, 0
|
|
31
|
+
state = :filling_package
|
|
32
|
+
when :filling_package
|
|
33
|
+
validate_package_quantity(packages.count)
|
|
34
|
+
|
|
35
|
+
items_to_pack.each do |item|
|
|
36
|
+
quantity = determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
|
|
37
|
+
package_weight += item_weight(quantity, item[:grams])
|
|
38
|
+
package_value += item_value(quantity, item[:price])
|
|
39
|
+
item[:quantity] = item[:quantity].to_i - quantity
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
items_to_pack.reject! { |i| i[:quantity].to_i == 0 }
|
|
43
|
+
state = :package_full
|
|
44
|
+
when :package_full
|
|
45
|
+
packages << ReactiveShipping::Package.new(package_weight, dimensions, value: package_value, currency: currency)
|
|
46
|
+
state = items_to_pack.any? ? :package_empty : :packing_finished
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
packages
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def validate_total_weight(items, maximum_weight)
|
|
56
|
+
total_weight = 0
|
|
57
|
+
items.each do |item|
|
|
58
|
+
total_weight += item[:quantity].to_i * item[:grams].to_i
|
|
59
|
+
|
|
60
|
+
if overweight_item?(item[:grams], maximum_weight)
|
|
61
|
+
raise OverweightItem, "The item with weight of #{item[:grams]}g is heavier than the allowable package weight of #{maximum_weight}g"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
raise_excess_quantity_error if maybe_excess_package_quantity?(total_weight, maximum_weight)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def validate_package_quantity(number_of_packages)
|
|
69
|
+
raise_excess_quantity_error if number_of_packages >= EXCESS_PACKAGE_QUANTITY_THRESHOLD
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def raise_excess_quantity_error
|
|
73
|
+
raise ExcessPackageQuantity, "Unable to pack more than #{EXCESS_PACKAGE_QUANTITY_THRESHOLD} packages"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def overweight_item?(grams, maximum_weight)
|
|
77
|
+
grams.to_i > maximum_weight
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def maybe_excess_package_quantity?(total_weight, maximum_weight)
|
|
81
|
+
total_weight > (maximum_weight * EXCESS_PACKAGE_QUANTITY_THRESHOLD)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
|
|
85
|
+
item_grams = item[:grams].to_i
|
|
86
|
+
item_quantity = item[:quantity].to_i
|
|
87
|
+
|
|
88
|
+
if item_grams <= 0
|
|
89
|
+
item_quantity
|
|
90
|
+
else
|
|
91
|
+
# Grab the max amount of this item we can fit into this package
|
|
92
|
+
# Or, if there are fewer than the max for this item, put
|
|
93
|
+
# what is left into this package
|
|
94
|
+
available_grams = (maximum_weight - package_weight).to_i
|
|
95
|
+
[available_grams / item_grams, item_quantity].min
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def item_weight(quantity, grams)
|
|
100
|
+
quantity * grams.to_i
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def item_value(quantity, price)
|
|
104
|
+
quantity * Package.cents_from(price)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
|
|
3
|
+
# Responce object class for calls to {ReactiveShipping::Carrier#create_shipment}.
|
|
4
|
+
#
|
|
5
|
+
# @note Some carriers provide more information that others, so not all attributes
|
|
6
|
+
# will be set, depending on what carrier you are using.
|
|
7
|
+
#
|
|
8
|
+
# @!attribute shipping_id
|
|
9
|
+
# The unique identifier of the shipment, which can be used to further interact
|
|
10
|
+
# with the carrier's API.
|
|
11
|
+
# @return [String]
|
|
12
|
+
#
|
|
13
|
+
# @!attribute tracking_number
|
|
14
|
+
# The tracking number of the shipments, which can be shared with the customer and
|
|
15
|
+
# be used for {ReactiveShipping::Carrier#find_tracking_info}.
|
|
16
|
+
# @return [String]
|
|
17
|
+
class ShippingResponse < Response
|
|
18
|
+
attr_reader :shipping_id, :tracking_number
|
|
19
|
+
|
|
20
|
+
# Initializes a new ShippingResponse instance.
|
|
21
|
+
#
|
|
22
|
+
# @param success (see ReactiveShipping::Response#initialize)
|
|
23
|
+
# @param message (see ReactiveShipping::Response#initialize)
|
|
24
|
+
# @param params (see ReactiveShipping::Response#initialize)
|
|
25
|
+
# @option options (see ReactiveShipping::Response#initialize)
|
|
26
|
+
# @option options [String] :shipping_id Populates {#shipping_id}.
|
|
27
|
+
# @option options [String] :tracking_number Populates {#tracking_number}.
|
|
28
|
+
def initialize(success, message, params = {}, options = {})
|
|
29
|
+
@shipping_id = options[:shipping_id]
|
|
30
|
+
@tracking_number = options[:tracking_number]
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
|
|
3
|
+
# Represents the response to a {ReactiveShipping::Carrier#find_tracking_info} call.
|
|
4
|
+
#
|
|
5
|
+
# @note Some carriers provide more information than others, so not all attributes
|
|
6
|
+
# will be set, depending on what carrier you are using.
|
|
7
|
+
#
|
|
8
|
+
# @!attribute carrier
|
|
9
|
+
# @return [Symbol]
|
|
10
|
+
#
|
|
11
|
+
# @!attribute carrier_name
|
|
12
|
+
# @return [String]
|
|
13
|
+
#
|
|
14
|
+
# @!attribute status
|
|
15
|
+
# @return [Symbol]
|
|
16
|
+
#
|
|
17
|
+
# @!attribute status_code
|
|
18
|
+
# @return [string]
|
|
19
|
+
#
|
|
20
|
+
# @!attribute status_description
|
|
21
|
+
# @return [String]
|
|
22
|
+
#
|
|
23
|
+
# @!attribute ship_time
|
|
24
|
+
# @return [Date, Time]
|
|
25
|
+
#
|
|
26
|
+
# @!attribute scheduled_delivery_date
|
|
27
|
+
# @return [Date, Time]
|
|
28
|
+
#
|
|
29
|
+
# @!attribute actual_delivery_date
|
|
30
|
+
# @return [Date, Time]
|
|
31
|
+
#
|
|
32
|
+
# @!attribute attempted_delivery_date
|
|
33
|
+
# @return [Date, Time]
|
|
34
|
+
#
|
|
35
|
+
# @!attribute delivery_signature
|
|
36
|
+
# @return [String]
|
|
37
|
+
#
|
|
38
|
+
# @!attribute tracking_number
|
|
39
|
+
# @return [String]
|
|
40
|
+
#
|
|
41
|
+
# @!attribute shipment_events
|
|
42
|
+
# @return [Array<ReactiveShipping::ShipmentEvent>]
|
|
43
|
+
#
|
|
44
|
+
# @!attribute shipper_address
|
|
45
|
+
# @return [ReactiveShipping::Location]
|
|
46
|
+
#
|
|
47
|
+
# @!attribute origin
|
|
48
|
+
# @return [ReactiveShipping::Location]
|
|
49
|
+
#
|
|
50
|
+
# @!attribute destination
|
|
51
|
+
# @return [ReactiveShipping::Location]
|
|
52
|
+
class TrackingResponse < Response
|
|
53
|
+
attr_reader :carrier,:carrier_name,
|
|
54
|
+
:status,:status_code, :status_description,
|
|
55
|
+
:ship_time, :scheduled_delivery_date, :actual_delivery_date, :attempted_delivery_date,
|
|
56
|
+
:delivery_signature, :tracking_number, :shipment_events,
|
|
57
|
+
:shipper_address, :origin, :destination
|
|
58
|
+
|
|
59
|
+
# @params (see ReactiveShipping::Response#initialize)
|
|
60
|
+
def initialize(success, message, params = {}, options = {})
|
|
61
|
+
@carrier = options[:carrier].parameterize.to_sym
|
|
62
|
+
@carrier_name = options[:carrier]
|
|
63
|
+
@status = options[:status]
|
|
64
|
+
@status_code = options[:status_code]
|
|
65
|
+
@status_description = options[:status_description]
|
|
66
|
+
@ship_time = options[:ship_time]
|
|
67
|
+
@scheduled_delivery_date = options[:scheduled_delivery_date]
|
|
68
|
+
@actual_delivery_date = options[:actual_delivery_date]
|
|
69
|
+
@attempted_delivery_date = options[:attempted_delivery_date]
|
|
70
|
+
@delivery_signature = options[:delivery_signature]
|
|
71
|
+
@tracking_number = options[:tracking_number]
|
|
72
|
+
@shipment_events = Array(options[:shipment_events])
|
|
73
|
+
@shipper_address = options[:shipper_address]
|
|
74
|
+
@origin = options[:origin]
|
|
75
|
+
@destination = options[:destination]
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The latest tracking event for this shipment, i.e. the current status.
|
|
80
|
+
# @return [ReactiveShipping::ShipmentEvent]
|
|
81
|
+
def latest_event
|
|
82
|
+
@shipment_events.last
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns `true` if the shipment has arrived at the destination.
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def is_delivered?
|
|
88
|
+
@status == :delivered
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns `true` if something out of the ordinary has happened during
|
|
92
|
+
# the delivery of this package.
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def has_exception?
|
|
95
|
+
@status == :exception
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias_method :exception_event, :latest_event
|
|
99
|
+
alias_method :delivered?, :is_delivered?
|
|
100
|
+
alias_method :exception?, :has_exception?
|
|
101
|
+
alias_method :scheduled_delivery_time, :scheduled_delivery_date
|
|
102
|
+
alias_method :actual_delivery_time, :actual_delivery_date
|
|
103
|
+
alias_method :attempted_delivery_time, :attempted_delivery_date
|
|
104
|
+
|
|
105
|
+
def ==(other)
|
|
106
|
+
attributes = %i(carrier carrier_name status status_code status_description ship_time scheduled_delivery_date
|
|
107
|
+
actual_delivery_date attempted_delivery_date delivery_signature tracking_number shipper_address
|
|
108
|
+
origin destination
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
attributes.all? { |attr| self.public_send(attr) == other.public_send(attr) } && compare_shipment_events(other)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
# Ensure order doesn't matter when comparing shipment_events
|
|
116
|
+
def compare_shipment_events(other)
|
|
117
|
+
shipment_events.sort_by(&:time) == other.shipment_events.sort_by(&:time)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
lib = File.expand_path("../lib/", __FILE__)
|
|
2
|
+
$:.unshift(lib) unless $:.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "reactive_shipping/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = "reactive_shipping"
|
|
8
|
+
s.version = ReactiveShipping::VERSION
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
s.authors = ["Sub Pop Records"]
|
|
11
|
+
s.email = ["webmaster@subpop.com"]
|
|
12
|
+
s.homepage = "http://github.com/realsubpop/reactive_shipping"
|
|
13
|
+
s.summary = "Simple shipping abstraction library"
|
|
14
|
+
s.description = "Get rates and tracking info from various shipping carriers. A fork of Shopify's original ActiveShipping gem."
|
|
15
|
+
s.license = "MIT"
|
|
16
|
+
s.files = `git ls-files`.split($/)
|
|
17
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
s.require_path = "lib"
|
|
20
|
+
s.post_install_message = "Thanks for installing ReactiveShipping! https://github.com/realsubpop/reactive_shipping/blob/master/CHANGELOG.md."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
s.add_dependency("measured", ">= 2.0")
|
|
24
|
+
s.add_dependency("activesupport", ">= 4.2", "< 6.1")
|
|
25
|
+
s.add_dependency("active_utils", "~> 3.3.1")
|
|
26
|
+
s.add_dependency("nokogiri", ">= 1.6")
|
|
27
|
+
|
|
28
|
+
s.add_development_dependency("minitest")
|
|
29
|
+
s.add_development_dependency("minitest-reporters")
|
|
30
|
+
s.add_development_dependency("rake")
|
|
31
|
+
s.add_development_dependency("mocha", "~> 1")
|
|
32
|
+
s.add_development_dependency("timecop")
|
|
33
|
+
s.add_development_dependency("business_time")
|
|
34
|
+
s.add_development_dependency("pry")
|
|
35
|
+
s.add_development_dependency("pry-byebug")
|
|
36
|
+
s.add_development_dependency("vcr")
|
|
37
|
+
s.add_development_dependency("webmock")
|
|
38
|
+
end
|
data/shipit.rubygems.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# using the default shipit config
|
data/test/console.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'pry'
|
|
2
|
+
require 'reactive_shipping'
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
include ReactiveShipping
|
|
6
|
+
include ReactiveShipping::Test::Credentials
|
|
7
|
+
include ReactiveShipping::Test::Fixtures
|
|
8
|
+
|
|
9
|
+
def create_carrier(klass, creds)
|
|
10
|
+
options = credentials(creds).merge(:test => true)
|
|
11
|
+
klass.new(options)
|
|
12
|
+
rescue NoCredentialsFound
|
|
13
|
+
STDERR.puts "No credentials found for #{creds}"
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def px(xml_s)
|
|
18
|
+
puts Nokogiri.XML(xml_s)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reload!
|
|
22
|
+
# Supress a billion constant redefinition warnings
|
|
23
|
+
warn_level = $VERBOSE
|
|
24
|
+
$VERBOSE = nil
|
|
25
|
+
|
|
26
|
+
files = $LOADED_FEATURES.select { |feat| feat =~ /\/reactive_shipping\// }
|
|
27
|
+
files.each { |file| load file }
|
|
28
|
+
|
|
29
|
+
$VERBOSE = warn_level
|
|
30
|
+
files
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Prebuilt objects for most common carriers
|
|
34
|
+
@canpo = create_carrier(CanadaPost,:canada_post)
|
|
35
|
+
@fedex = create_carrier(FedEx,:fedex)
|
|
36
|
+
@shipwire = create_carrier(Shipwire,:shipwire)
|
|
37
|
+
@usps = create_carrier(USPS,:usps)
|
|
38
|
+
# Tips: call reload! to reload all the reactive_shipping files, use fixtures from test_helpers for parameters
|
|
39
|
+
binding.pry
|