reactive_shipping 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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