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.
Files changed (247) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +33 -0
  4. data/.yardopts +13 -0
  5. data/CHANGELOG.md +225 -0
  6. data/CONTRIBUTING.md +23 -0
  7. data/Gemfile +3 -0
  8. data/MIT-LICENSE +21 -0
  9. data/README.md +158 -0
  10. data/Rakefile +35 -0
  11. data/dev.yml +17 -0
  12. data/gemfiles/activesupport42.gemfile +5 -0
  13. data/gemfiles/activesupport50.gemfile +6 -0
  14. data/gemfiles/activesupport51.gemfile +5 -0
  15. data/gemfiles/activesupport52.gemfile +5 -0
  16. data/gemfiles/activesupport_master.gemfile +5 -0
  17. data/lib/certs/eParcel.dtd +111 -0
  18. data/lib/reactive_shipping.rb +26 -0
  19. data/lib/reactive_shipping/address_validation_response.rb +30 -0
  20. data/lib/reactive_shipping/carrier.rb +184 -0
  21. data/lib/reactive_shipping/carriers.rb +35 -0
  22. data/lib/reactive_shipping/carriers/australia_post.rb +248 -0
  23. data/lib/reactive_shipping/carriers/benchmark_carrier.rb +31 -0
  24. data/lib/reactive_shipping/carriers/bogus_carrier.rb +12 -0
  25. data/lib/reactive_shipping/carriers/canada_post.rb +263 -0
  26. data/lib/reactive_shipping/carriers/canada_post_pws.rb +908 -0
  27. data/lib/reactive_shipping/carriers/fedex.rb +797 -0
  28. data/lib/reactive_shipping/carriers/kunaki.rb +155 -0
  29. data/lib/reactive_shipping/carriers/new_zealand_post.rb +260 -0
  30. data/lib/reactive_shipping/carriers/shipwire.rb +178 -0
  31. data/lib/reactive_shipping/carriers/stamps.rb +860 -0
  32. data/lib/reactive_shipping/carriers/ups.rb +1060 -0
  33. data/lib/reactive_shipping/carriers/usps.rb +708 -0
  34. data/lib/reactive_shipping/carriers/usps_returns.rb +86 -0
  35. data/lib/reactive_shipping/delivery_date_estimate.rb +20 -0
  36. data/lib/reactive_shipping/delivery_date_estimates_response.rb +11 -0
  37. data/lib/reactive_shipping/errors.rb +35 -0
  38. data/lib/reactive_shipping/external_return_label_request.rb +417 -0
  39. data/lib/reactive_shipping/external_return_label_response.rb +26 -0
  40. data/lib/reactive_shipping/label.rb +10 -0
  41. data/lib/reactive_shipping/label_response.rb +10 -0
  42. data/lib/reactive_shipping/location.rb +166 -0
  43. data/lib/reactive_shipping/package.rb +165 -0
  44. data/lib/reactive_shipping/package_item.rb +60 -0
  45. data/lib/reactive_shipping/rate_estimate.rb +197 -0
  46. data/lib/reactive_shipping/rate_response.rb +33 -0
  47. data/lib/reactive_shipping/response.rb +44 -0
  48. data/lib/reactive_shipping/shipment_event.rb +22 -0
  49. data/lib/reactive_shipping/shipment_packer.rb +108 -0
  50. data/lib/reactive_shipping/shipping_response.rb +34 -0
  51. data/lib/reactive_shipping/tracking_response.rb +120 -0
  52. data/lib/reactive_shipping/version.rb +3 -0
  53. data/reactive_shipping.gemspec +38 -0
  54. data/shipit.rubygems.yml +1 -0
  55. data/test/console.rb +39 -0
  56. data/test/credentials.yml +76 -0
  57. data/test/fixtures/files/label1.pdf +0 -0
  58. data/test/fixtures/files/ups-shipping-label.gif +0 -0
  59. data/test/fixtures/json/australia_post/calculate_domestic.json +13 -0
  60. data/test/fixtures/json/australia_post/calculate_domestic_2.json +19 -0
  61. data/test/fixtures/json/australia_post/calculate_international.json +12 -0
  62. data/test/fixtures/json/australia_post/calculate_international_2.json +15 -0
  63. data/test/fixtures/json/australia_post/error_message.json +5 -0
  64. data/test/fixtures/json/australia_post/service_domestic.json +117 -0
  65. data/test/fixtures/json/australia_post/service_domestic_2.json +117 -0
  66. data/test/fixtures/json/australia_post/service_international.json +76 -0
  67. data/test/fixtures/json/australia_post/service_international_2.json +59 -0
  68. data/test/fixtures/json/newzealandpost/domestic_book.json +1 -0
  69. data/test/fixtures/json/newzealandpost/domestic_default.json +1 -0
  70. data/test/fixtures/json/newzealandpost/domestic_error.json +1 -0
  71. data/test/fixtures/json/newzealandpost/domestic_poster.json +1 -0
  72. data/test/fixtures/json/newzealandpost/domestic_small_half_pound.json +1 -0
  73. data/test/fixtures/json/newzealandpost/international_book.json +1 -0
  74. data/test/fixtures/json/newzealandpost/international_new_zealand_wii.json +1 -0
  75. data/test/fixtures/json/newzealandpost/international_small_half_pound.json +1 -0
  76. data/test/fixtures/json/newzealandpost/international_wii.json +1 -0
  77. data/test/fixtures/xml/canadapost/example_request.xml +25 -0
  78. data/test/fixtures/xml/canadapost/example_response.xml +130 -0
  79. data/test/fixtures/xml/canadapost/example_response_error.xml +16 -0
  80. data/test/fixtures/xml/canadapost/example_response_french.xml +122 -0
  81. data/test/fixtures/xml/canadapost/example_response_with_nil_value.xml +164 -0
  82. data/test/fixtures/xml/canadapost/example_response_with_postal_outlet.xml +155 -0
  83. data/test/fixtures/xml/canadapost/example_response_with_postal_outlet_french.xml +274 -0
  84. data/test/fixtures/xml/canadapost/example_response_with_strange_delivery_date.xml +130 -0
  85. data/test/fixtures/xml/canadapost_pws/dnc_tracking_details_en.xml +112 -0
  86. data/test/fixtures/xml/canadapost_pws/merchant_details_error.xml +7 -0
  87. data/test/fixtures/xml/canadapost_pws/merchant_details_response.xml +7 -0
  88. data/test/fixtures/xml/canadapost_pws/option_response.xml +13 -0
  89. data/test/fixtures/xml/canadapost_pws/option_response_no_conflicts.xml +7 -0
  90. data/test/fixtures/xml/canadapost_pws/rates_info.xml +190 -0
  91. data/test/fixtures/xml/canadapost_pws/rates_info_error.xml +7 -0
  92. data/test/fixtures/xml/canadapost_pws/receipt_response.xml +42 -0
  93. data/test/fixtures/xml/canadapost_pws/receipt_response_no_priced_options.xml +36 -0
  94. data/test/fixtures/xml/canadapost_pws/register_token_error.xml +7 -0
  95. data/test/fixtures/xml/canadapost_pws/register_token_response.xml +3 -0
  96. data/test/fixtures/xml/canadapost_pws/service_options_response.xml +42 -0
  97. data/test/fixtures/xml/canadapost_pws/services_error.xml +6 -0
  98. data/test/fixtures/xml/canadapost_pws/services_response.xml +32 -0
  99. data/test/fixtures/xml/canadapost_pws/shipment_domestic.xml +69 -0
  100. data/test/fixtures/xml/canadapost_pws/shipment_response.xml +20 -0
  101. data/test/fixtures/xml/canadapost_pws/shipment_us.xml +69 -0
  102. data/test/fixtures/xml/canadapost_pws/tracking_details_en.xml +152 -0
  103. data/test/fixtures/xml/canadapost_pws/tracking_details_en_error.xml +7 -0
  104. data/test/fixtures/xml/canadapost_pws/tracking_details_en_undelivered.xml +116 -0
  105. data/test/fixtures/xml/canadapost_pws/tracking_details_fr.xml +156 -0
  106. data/test/fixtures/xml/canadapost_pws/tracking_details_no_expected_delivery_date.xml +40 -0
  107. data/test/fixtures/xml/fedex/create_shipment_response.xml +2 -0
  108. data/test/fixtures/xml/fedex/freight_rate_request.xml +82 -0
  109. data/test/fixtures/xml/fedex/freight_rate_response.xml +506 -0
  110. data/test/fixtures/xml/fedex/invalid_fedex_reply.xml +27 -0
  111. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_commercial_rate_request.xml +79 -0
  112. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_no_saturday_rate_request.xml +79 -0
  113. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_request.xml +80 -0
  114. data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_response.xml +214 -0
  115. data/test/fixtures/xml/fedex/raterequest_reply.xml +213 -0
  116. data/test/fixtures/xml/fedex/raterequest_response_with_ground_home_delivery.xml +206 -0
  117. data/test/fixtures/xml/fedex/reply_without_notifications.xml +185 -0
  118. data/test/fixtures/xml/fedex/tracking_request.xml +29 -0
  119. data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
  120. data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
  121. data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
  122. data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
  123. data/test/fixtures/xml/fedex/tracking_response_empty_status_detail.xml +84 -0
  124. data/test/fixtures/xml/fedex/tracking_response_failure_code_9045.xml +52 -0
  125. data/test/fixtures/xml/fedex/tracking_response_failure_code_9080.xml +51 -0
  126. data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
  127. data/test/fixtures/xml/fedex/tracking_response_invalid_tracking_number.xml +52 -0
  128. data/test/fixtures/xml/fedex/tracking_response_missing_status_code.xml +89 -0
  129. data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
  130. data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
  131. data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
  132. data/test/fixtures/xml/fedex/tracking_response_unable_to_process.xml +32 -0
  133. data/test/fixtures/xml/fedex/tracking_response_with_blank_state.xml +107 -0
  134. data/test/fixtures/xml/fedex/unknown_fedex_document_reply.xml +3 -0
  135. data/test/fixtures/xml/kunaki/invalid_state_response.xml +3 -0
  136. data/test/fixtures/xml/kunaki/no_valid_items_response.xml +3 -0
  137. data/test/fixtures/xml/kunaki/successful_rates_response.xml +3 -0
  138. data/test/fixtures/xml/kunaki/unsuccessful_rates_response.xml +9 -0
  139. data/test/fixtures/xml/shipwire/international_rates_response.xml +17 -0
  140. data/test/fixtures/xml/shipwire/new_carrier_rate_response.xml +18 -0
  141. data/test/fixtures/xml/shipwire/no_rates_response.xml +7 -0
  142. data/test/fixtures/xml/shipwire/rates_response.xml +36 -0
  143. data/test/fixtures/xml/shipwire/rates_response_no_estimate.xml +14 -0
  144. data/test/fixtures/xml/stamps/authenticate_user_request.xml +15 -0
  145. data/test/fixtures/xml/stamps/authenticate_user_response.xml +10 -0
  146. data/test/fixtures/xml/stamps/cleanse_address_request.xml +19 -0
  147. data/test/fixtures/xml/stamps/cleanse_address_response.xml +27 -0
  148. data/test/fixtures/xml/stamps/create_indicium_request.xml +69 -0
  149. data/test/fixtures/xml/stamps/create_indicium_response.xml +40 -0
  150. data/test/fixtures/xml/stamps/expired_authenticator_response.xml +15 -0
  151. data/test/fixtures/xml/stamps/get_account_info_request.xml +11 -0
  152. data/test/fixtures/xml/stamps/get_account_info_response.xml +36 -0
  153. data/test/fixtures/xml/stamps/get_purchase_status_request.xml +12 -0
  154. data/test/fixtures/xml/stamps/get_purchase_status_response.xml +16 -0
  155. data/test/fixtures/xml/stamps/get_rates_request.xml +19 -0
  156. data/test/fixtures/xml/stamps/get_rates_response.xml +351 -0
  157. data/test/fixtures/xml/stamps/purchase_postage_request.xml +13 -0
  158. data/test/fixtures/xml/stamps/purchase_postage_response.xml +17 -0
  159. data/test/fixtures/xml/stamps/track_shipment_request.xml +12 -0
  160. data/test/fixtures/xml/stamps/track_shipment_response.xml +45 -0
  161. data/test/fixtures/xml/ups/access_request.xml +6 -0
  162. data/test/fixtures/xml/ups/delivered_shipment_with_refund.xml +290 -0
  163. data/test/fixtures/xml/ups/delivered_shipment_without_events_tracking_response.xml +62 -0
  164. data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
  165. data/test/fixtures/xml/ups/example_tracking_response.xml +53 -0
  166. data/test/fixtures/xml/ups/in_transit_shipment.xml +183 -0
  167. data/test/fixtures/xml/ups/out_for_delivery_shipment.xml +165 -0
  168. data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
  169. data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
  170. data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
  171. data/test/fixtures/xml/ups/shipment_accept_response.xml +42 -0
  172. data/test/fixtures/xml/ups/shipment_confirm_response.xml +33 -0
  173. data/test/fixtures/xml/ups/shipment_from_tiger_direct.xml +222 -0
  174. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -0
  175. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response_with_insured.xml +289 -0
  176. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_with_origin_account_response.xml +311 -0
  177. data/test/fixtures/xml/ups/tracking_request.xml +9 -0
  178. data/test/fixtures/xml/ups/triple_accept_response.xml +72 -0
  179. data/test/fixtures/xml/ups/triple_confirm_response.xml +32 -0
  180. data/test/fixtures/xml/ups/void_shipment_response.xml +11 -0
  181. data/test/fixtures/xml/usps/api_error_rate_response.xml +53 -0
  182. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_base_rate_response.xml +2 -0
  183. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_plus_rate_response.xml +258 -0
  184. data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_rate_response.xml +108 -0
  185. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_base_rate_response.xml +84 -0
  186. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_plus_rate_response.xml +212 -0
  187. data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_rate_response.xml +230 -0
  188. data/test/fixtures/xml/usps/first_class_packages_with_invalid_mail_type_response.xml +12 -0
  189. data/test/fixtures/xml/usps/first_class_packages_with_mail_type_response.xml +16 -0
  190. data/test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml +12 -0
  191. data/test/fixtures/xml/usps/invalid_xml_response.xml +10 -0
  192. data/test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml +2 -0
  193. data/test/fixtures/xml/usps/tracking_request.xml +10 -0
  194. data/test/fixtures/xml/usps/tracking_request_batch.xml +12 -0
  195. data/test/fixtures/xml/usps/tracking_response.xml +162 -0
  196. data/test/fixtures/xml/usps/tracking_response_alt.xml +53 -0
  197. data/test/fixtures/xml/usps/tracking_response_batch.xml +231 -0
  198. data/test/fixtures/xml/usps/tracking_response_failure.xml +11 -0
  199. data/test/fixtures/xml/usps/tracking_response_not_available.xml +12 -0
  200. data/test/fixtures/xml/usps/tracking_response_test_error.xml +8 -0
  201. data/test/fixtures/xml/usps/us_rate_request.xml +18 -0
  202. data/test/fixtures/xml/usps/us_rate_request_large.xml +18 -0
  203. data/test/fixtures/xml/usps/world_rate_request_only_country.xml +22 -0
  204. data/test/fixtures/xml/usps/world_rate_request_with_value.xml +24 -0
  205. data/test/fixtures/xml/usps/world_rate_request_without_value.xml +24 -0
  206. data/test/fixtures/xml/usps_returns/external_return_label_response.xml +2 -0
  207. data/test/fixtures/xml/usps_returns/external_return_label_response_failure.xml +10 -0
  208. data/test/remote/australia_post_test.rb +140 -0
  209. data/test/remote/canada_post_pws_platform_test.rb +259 -0
  210. data/test/remote/canada_post_pws_test.rb +169 -0
  211. data/test/remote/canada_post_test.rb +55 -0
  212. data/test/remote/fedex_test.rb +400 -0
  213. data/test/remote/kunaki_test.rb +37 -0
  214. data/test/remote/new_zealand_post_test.rb +149 -0
  215. data/test/remote/shipwire_test.rb +84 -0
  216. data/test/remote/stamps_test.rb +396 -0
  217. data/test/remote/usps_returns_test.rb +72 -0
  218. data/test/remote/usps_test.rb +243 -0
  219. data/test/test_helper.rb +296 -0
  220. data/test/unit/carrier_test.rb +130 -0
  221. data/test/unit/carriers/australia_post_test.rb +181 -0
  222. data/test/unit/carriers/benchmark_test.rb +18 -0
  223. data/test/unit/carriers/canada_post_pws_rating_test.rb +379 -0
  224. data/test/unit/carriers/canada_post_pws_register_test.rb +76 -0
  225. data/test/unit/carriers/canada_post_pws_shipping_test.rb +258 -0
  226. data/test/unit/carriers/canada_post_pws_test.rb +59 -0
  227. data/test/unit/carriers/canada_post_pws_tracking_test.rb +154 -0
  228. data/test/unit/carriers/canada_post_test.rb +148 -0
  229. data/test/unit/carriers/fedex_test.rb +693 -0
  230. data/test/unit/carriers/kunaki_test.rb +56 -0
  231. data/test/unit/carriers/new_zealand_post_test.rb +177 -0
  232. data/test/unit/carriers/shipwire_test.rb +188 -0
  233. data/test/unit/carriers/stamps_test.rb +245 -0
  234. data/test/unit/carriers/ups_test.rb +580 -0
  235. data/test/unit/carriers/usps_returns_test.rb +45 -0
  236. data/test/unit/carriers/usps_test.rb +633 -0
  237. data/test/unit/carriers_test.rb +16 -0
  238. data/test/unit/external_return_label_request_test.rb +258 -0
  239. data/test/unit/location_test.rb +234 -0
  240. data/test/unit/package_item_test.rb +232 -0
  241. data/test/unit/package_test.rb +404 -0
  242. data/test/unit/rate_estimate_test.rb +93 -0
  243. data/test/unit/response_test.rb +38 -0
  244. data/test/unit/shipment_event_test.rb +20 -0
  245. data/test/unit/shipment_packer_test.rb +212 -0
  246. data/test/unit/tracking_response_test.rb +41 -0
  247. 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,3 @@
1
+ module ReactiveShipping
2
+ VERSION = "3.0.0"
3
+ 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
@@ -0,0 +1 @@
1
+ # using the default shipit config
@@ -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