solidus_easypost 1.0.3 → 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 (97) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +21 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +7 -0
  9. data/.rubocop_todo.yml +36 -0
  10. data/CHANGELOG.md +113 -0
  11. data/Gemfile +33 -0
  12. data/LICENSE +26 -0
  13. data/README.md +194 -0
  14. data/Rakefile +6 -0
  15. data/app/controllers/spree/admin/postage_labels_controller.rb +20 -0
  16. data/app/decorators/models/solidus_easypost/spree/carton_decorator.rb +38 -0
  17. data/app/decorators/models/solidus_easypost/spree/shipment_decorator.rb +47 -0
  18. data/app/decorators/models/solidus_easypost/spree/shipping_rate_decorator.rb +13 -0
  19. data/app/models/solidus_easypost/parcel_dimension.rb +34 -0
  20. data/app/models/solidus_easypost/return_authorization.rb +27 -0
  21. data/app/overrides/spree/admin/orders/_shipment/add_postage_label.html.erb.deface +10 -0
  22. data/bin/console +17 -0
  23. data/bin/rails +7 -0
  24. data/bin/rails-engine +13 -0
  25. data/bin/rails-sandbox +16 -0
  26. data/bin/rake +7 -0
  27. data/bin/sandbox +86 -0
  28. data/bin/setup +8 -0
  29. data/config/initializers/webhooks.rb +7 -0
  30. data/config/locales/en.yml +8 -0
  31. data/config/routes.rb +9 -0
  32. data/db/migrate/20140515024440_add_easy_post_fields_to_shipping_rate.rb +9 -0
  33. data/db/migrate/20201025110912_add_tracker_id_to_cartons.rb +6 -0
  34. data/lib/generators/solidus_easypost/install/install_generator.rb +28 -0
  35. data/lib/generators/solidus_easypost/install/templates/initializer.rb +26 -0
  36. data/lib/solidus_easypost/address_builder.rb +45 -0
  37. data/lib/solidus_easypost/calculator/base_dimension_calculator.rb +28 -0
  38. data/lib/solidus_easypost/calculator/weight_dimension_calculator.rb +22 -0
  39. data/lib/solidus_easypost/configuration.rb +34 -0
  40. data/lib/solidus_easypost/engine.rb +19 -0
  41. data/lib/solidus_easypost/errors/unknown_partial_resource_error.rb +11 -0
  42. data/lib/solidus_easypost/estimator.rb +37 -0
  43. data/lib/solidus_easypost/parcel_builder.rb +27 -0
  44. data/lib/solidus_easypost/shipment_builder.rb +32 -0
  45. data/lib/solidus_easypost/shipping_method_selector.rb +17 -0
  46. data/lib/solidus_easypost/shipping_rate_calculator.rb +9 -0
  47. data/lib/solidus_easypost/testing_support/factories/address_factory.rb +10 -0
  48. data/lib/solidus_easypost/testing_support/factories/product_factory.rb +7 -0
  49. data/lib/solidus_easypost/testing_support/factories/shipment_factory.rb +30 -0
  50. data/lib/solidus_easypost/testing_support/factories/shipping_method_factory.rb +8 -0
  51. data/lib/solidus_easypost/testing_support/factories/stock_location_factory.rb +10 -0
  52. data/lib/solidus_easypost/testing_support/factories/variant_factory.rb +7 -0
  53. data/lib/solidus_easypost/testing_support/factories.rb +3 -0
  54. data/lib/solidus_easypost/tracker_webhook_handler.rb +14 -0
  55. data/lib/solidus_easypost/version.rb +5 -0
  56. data/lib/solidus_easypost.rb +32 -0
  57. data/solidus_easypost.gemspec +41 -0
  58. data/spec/cassettes/address_builder/from_address.yml +65 -0
  59. data/spec/cassettes/address_builder/from_stock_location.yml +65 -0
  60. data/spec/cassettes/estimator.yml +269 -0
  61. data/spec/cassettes/integration/checkout.yml +377 -0
  62. data/spec/cassettes/integration/order_shipping/with_purchase_labels.yml +373 -0
  63. data/spec/cassettes/integration/order_shipping/without_purchase_labels.yml +251 -0
  64. data/spec/cassettes/parcel_builder/from_package.yml +63 -0
  65. data/spec/cassettes/parcel_builder/from_return_authorization.yml +63 -0
  66. data/spec/cassettes/postage_labels/show.yml +72 -0
  67. data/spec/cassettes/return_authorization.yml +310 -0
  68. data/spec/cassettes/shipment_builder/from_package.yml +251 -0
  69. data/spec/cassettes/shipment_builder/from_return_authorization.yml +251 -0
  70. data/spec/cassettes/shipment_builder/from_shipment.yml +250 -0
  71. data/spec/controllers/spree/admin/postage_labels_controller_spec.rb +18 -0
  72. data/spec/features/admin/postage_labels_spec.rb +18 -0
  73. data/spec/integration/checkout_spec.rb +45 -0
  74. data/spec/integration/order_shipping_spec.rb +33 -0
  75. data/spec/models/solidus_easypost/parcel_dimension_spec.rb +80 -0
  76. data/spec/models/solidus_easypost/return_authorization_spec.rb +17 -0
  77. data/spec/models/spree/carton_spec.rb +57 -0
  78. data/spec/models/spree/shipment_spec.rb +69 -0
  79. data/spec/models/spree/shipping_rate_spec.rb +23 -0
  80. data/spec/solidus_easypost/address_builder_spec.rb +17 -0
  81. data/spec/solidus_easypost/calculator/weight_dimension_calculator_spec.rb +39 -0
  82. data/spec/solidus_easypost/estimator_spec.rb +52 -0
  83. data/spec/solidus_easypost/parcel_builder_spec.rb +113 -0
  84. data/spec/solidus_easypost/shipment_builder_spec.rb +28 -0
  85. data/spec/solidus_easypost/shipping_method_selector_spec.rb +33 -0
  86. data/spec/solidus_easypost/shipping_rate_calculator_spec.rb +12 -0
  87. data/spec/solidus_easypost/tracker_webhook_handler_spec.rb +58 -0
  88. data/spec/solidus_easypost_spec.rb +19 -0
  89. data/spec/spec_helper.rb +31 -0
  90. data/spec/support/easypost.rb +5 -0
  91. data/spec/support/factory_bot.rb +3 -0
  92. data/spec/support/helpers/api_stubs.rb +40 -0
  93. data/spec/support/helpers/configuration.rb +28 -0
  94. data/spec/support/helpers/shipping_methods.rb +26 -0
  95. data/spec/support/solidus.rb +1 -0
  96. data/spec/support/vcr.rb +12 -0
  97. metadata +170 -142
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class ParcelDimension
5
+ def initialize(**params)
6
+ raise ArgumentError, 'The weight param is mandatory!' unless valid_value?(value: params[:weight])
7
+
8
+ @weight = params[:weight]
9
+ @width = params[:width]
10
+ @height = params[:height]
11
+ @depth = params[:depth]
12
+ end
13
+
14
+ def to_h
15
+ hash = {
16
+ weight: weight
17
+ }
18
+
19
+ hash[:width] = width if valid_value?(value: width)
20
+ hash[:height] = height if valid_value?(value: height)
21
+ hash[:length] = depth if valid_value?(value: depth)
22
+
23
+ hash
24
+ end
25
+
26
+ private
27
+
28
+ def valid_value?(value:)
29
+ value.present? && !value.zero?
30
+ end
31
+
32
+ attr_reader :width, :height, :depth, :weight
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module SolidusEasypost
6
+ class ReturnAuthorization
7
+ extend Forwardable
8
+
9
+ attr_reader :return_authorization
10
+
11
+ def_delegators :@return_authorization, :stock_location, :order, :inventory_units
12
+
13
+ def initialize(return_authorization)
14
+ @return_authorization = return_authorization
15
+ end
16
+
17
+ def easypost_shipment
18
+ @easypost_shipment ||= ShipmentBuilder.from_return_authorization(self)
19
+ end
20
+
21
+ def return_label(rate)
22
+ easypost_shipment.buy(rate) unless easypost_shipment.postage_label
23
+
24
+ easypost_shipment.postage_label
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ <!-- insert_bottom '[data-hook="stock-contents"] tbody' -->
2
+
3
+ <% if shipment.selected_easy_post_rate_id && shipment.shipped? %>
4
+ <tr class="postage-label">
5
+ <td colspan="5">
6
+ <strong><%= t('spree.postage_label') %>:</strong>
7
+ <%= link_to_with_icon('external-link', t('spree.open_postage_label'), admin_shipment_postage_label_path(shipment)) %>
8
+ </td>
9
+ </tr>
10
+ <% end %>
data/bin/console ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "bundler/setup"
6
+ require "solidus_easypost"
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+ $LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"])
11
+
12
+ # (If you use this, don't forget to add pry to your Gemfile!)
13
+ # require "pry"
14
+ # Pry.start
15
+
16
+ require "irb"
17
+ IRB.start(__FILE__)
data/bin/rails ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if %w[g generate].include? ARGV.first
4
+ exec "#{__dir__}/rails-engine", *ARGV
5
+ else
6
+ exec "#{__dir__}/rails-sandbox", *ARGV
7
+ end
data/bin/rails-engine ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/solidus_easypost/engine', __dir__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'rails/all'
13
+ require 'rails/engine/commands'
data/bin/rails-sandbox ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ app_root = 'sandbox'
4
+
5
+ unless File.exist? "#{app_root}/bin/rails"
6
+ warn 'Creating the sandbox app...'
7
+ Dir.chdir "#{__dir__}/.." do
8
+ system "#{__dir__}/sandbox" or begin
9
+ warn 'Automatic creation of the sandbox app failed'
10
+ exit 1
11
+ end
12
+ end
13
+ end
14
+
15
+ Dir.chdir app_root
16
+ exec 'bin/rails', *ARGV
data/bin/rake ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ load Gem.bin_path("rake", "rake")
data/bin/sandbox ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ case "$DB" in
6
+ postgres|postgresql)
7
+ RAILSDB="postgresql"
8
+ ;;
9
+ mysql)
10
+ RAILSDB="mysql"
11
+ ;;
12
+ sqlite|'')
13
+ RAILSDB="sqlite3"
14
+ ;;
15
+ *)
16
+ echo "Invalid DB specified: $DB"
17
+ exit 1
18
+ ;;
19
+ esac
20
+
21
+ if [ ! -z $SOLIDUS_BRANCH ]
22
+ then
23
+ BRANCH=$SOLIDUS_BRANCH
24
+ else
25
+ BRANCH="master"
26
+ fi
27
+
28
+ extension_name="solidus_easypost"
29
+
30
+ # Stay away from the bundler env of the containing extension.
31
+ function unbundled {
32
+ ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@
33
+ }
34
+
35
+ rm -rf ./sandbox
36
+ unbundled bundle exec rails new sandbox --database="$RAILSDB" \
37
+ --skip-bundle \
38
+ --skip-git \
39
+ --skip-keeps \
40
+ --skip-rc \
41
+ --skip-spring \
42
+ --skip-test \
43
+ --skip-javascript
44
+
45
+ if [ ! -d "sandbox" ]; then
46
+ echo 'sandbox rails application failed'
47
+ exit 1
48
+ fi
49
+
50
+ cd ./sandbox
51
+ cat <<RUBY >> Gemfile
52
+ gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH'
53
+ gem 'solidus_auth_devise', '>= 2.1.0'
54
+ gem 'rails-i18n'
55
+ gem 'solidus_i18n'
56
+
57
+ gem '$extension_name', path: '..'
58
+
59
+ group :test, :development do
60
+ platforms :mri do
61
+ gem 'pry-byebug'
62
+ end
63
+ end
64
+ RUBY
65
+
66
+ unbundled bundle install --gemfile Gemfile
67
+
68
+ unbundled bundle exec rake db:drop db:create
69
+
70
+ unbundled bundle exec rails generate spree:install \
71
+ --auto-accept \
72
+ --user_class=Spree::User \
73
+ --enforce_available_locales=true \
74
+ --with-authentication=false \
75
+ --payment-method=none \
76
+ $@
77
+
78
+ unbundled bundle exec rails generate solidus:auth:install
79
+ unbundled bundle exec rails generate ${extension_name}:install
80
+
81
+ echo
82
+ echo "🚀 Sandbox app successfully created for $extension_name!"
83
+ echo "🚀 Using $RAILSDB and Solidus $BRANCH"
84
+ echo "🚀 Use 'export DB=[postgres|mysql|sqlite]' to control the DB adapter"
85
+ echo "🚀 Use 'export SOLIDUS_BRANCH=<BRANCH-NAME>' to control the Solidus version"
86
+ echo "🚀 This app is intended for test purposes."
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ gem install bundler --conservative
7
+ bundle update
8
+ bin/rake clobber
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(SolidusWebhooks)
4
+ SolidusWebhooks.config.register_webhook_handler :easypost_trackers, ->(payload) do
5
+ SolidusEasypost.configuration.webhook_handler_class.call(payload)
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ en:
2
+ spree:
3
+ postage_label: 'Postage Label'
4
+ open_postage_label: 'Open'
5
+ admin:
6
+ postage_labels:
7
+ show:
8
+ no_postage_label: 'No postage label available for shipment %{shipment_number}.'
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Core::Engine.routes.draw do
4
+ namespace :admin do
5
+ resources :shipments, only: [] do
6
+ resource :postage_label, only: :show
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddEasyPostFieldsToShippingRate < SolidusSupport::Migration[4.2]
4
+ def change
5
+ add_column :spree_shipping_rates, :name, :string
6
+ add_column :spree_shipping_rates, :easy_post_shipment_id, :string
7
+ add_column :spree_shipping_rates, :easy_post_rate_id, :string
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class AddTrackerIdToCartons < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :spree_cartons, :easy_post_tracker_id, :string
4
+ add_index :spree_cartons, :easy_post_tracker_id
5
+ end
6
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ class_option :auto_run_migrations, type: :boolean, default: false
9
+
10
+ def copy_initializer
11
+ template 'initializer.rb', 'config/initializers/solidus_easypost.rb'
12
+ end
13
+
14
+ def add_migrations
15
+ run 'bin/rails railties:install:migrations FROM=solidus_easypost'
16
+ end
17
+
18
+ def run_migrations
19
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
20
+ if run_migrations
21
+ run 'bin/rails db:migrate'
22
+ else
23
+ puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ EasyPost.api_key = 'YOUR_API_KEY_HERE'
4
+
5
+ SolidusEasypost.configure do |config|
6
+ # Purchase labels from EasyPost when shipping shipments in Solidus?
7
+ # config.purchase_labels = true
8
+
9
+ # Create a tracker in EasyPost and receive webhooks for all cartons?
10
+ # config.track_all_cartons = false
11
+
12
+ # A class that responds to `#compute`, accepting an `EasyPost::Rate`
13
+ # instance and returning the cost for that rate.
14
+ # config.shipping_rate_calculator_class = 'SolidusEasypost::ShippingRateCalculator'
15
+
16
+ # A class that responds to `#shipping_method_for`, accepting an
17
+ # `EasyPost::Rate` instance and returning the shipping method for
18
+ # that rate.
19
+ # config.shipping_method_selector_class = 'SolidusEasypost::ShippingMethodSelector'
20
+
21
+ # A class that responds to '#compute', accepting a `SolidusEasypost::ReturnAuthorization`
22
+ # instance or a `Spree::Stock::Package` instance and returing the `SolidusEasypost::ParcelDimension` object.
23
+ # The `SolidusEasypost::Calculator::BaseDimensionCalculator` class can be extended to have a common
24
+ # functionality.
25
+ # config.parcel_dimension_calculator_class = 'SolidusEasypost::Calculator::WeightDimensionCalculator'
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class AddressBuilder
5
+ class << self
6
+ def from_address(address)
7
+ attributes = {
8
+ street1: address.address1,
9
+ street2: address.address2,
10
+ city: address.city,
11
+ zip: address.zipcode,
12
+ phone: address.phone
13
+ }
14
+
15
+ attributes[:company] = address.company if address.respond_to?(:company)
16
+ attributes[:name] = if address.respond_to?(:name)
17
+ address.name
18
+ elsif respond_to?(:full_name)
19
+ address.full_name
20
+ end
21
+ attributes[:state] = address.state ? address.state.abbr : address.state_name
22
+ attributes[:country] = address.country&.iso
23
+
24
+ ::EasyPost::Address.create attributes
25
+ end
26
+
27
+ def from_stock_location(stock_location)
28
+ attributes = {
29
+ street1: stock_location.address1,
30
+ street2: stock_location.address2,
31
+ city: stock_location.city,
32
+ zip: stock_location.zipcode,
33
+ phone: stock_location.phone,
34
+ name: stock_location.name,
35
+ company: stock_location.name
36
+ }
37
+
38
+ attributes[:state] = stock_location.state ? stock_location.state.abbr : stock_location.state_name
39
+ attributes[:country] = stock_location.country&.iso
40
+
41
+ ::EasyPost::Address.create attributes
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ module Calculator
5
+ class BaseDimensionCalculator
6
+ def compute(resource)
7
+ case resource
8
+ when ::SolidusEasypost::ReturnAuthorization
9
+ compute_for_return_authorization(resource)
10
+ when ::Spree::Stock::Package
11
+ compute_for_package(resource)
12
+ else
13
+ raise SolidusEasypost::Errors::UnknownPartialResourceError
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def compute_for_return_authorization(return_authorization)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def compute_for_package(package)
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ module Calculator
5
+ class WeightDimensionCalculator < BaseDimensionCalculator
6
+ protected
7
+
8
+ def compute_for_return_authorization(return_authorization)
9
+ total_weight = return_authorization.inventory_units.joins(:variant).sum(:weight)
10
+ SolidusEasypost::ParcelDimension.new(weight: total_weight)
11
+ end
12
+
13
+ def compute_for_package(package)
14
+ total_weight = package.contents.sum do |item|
15
+ item.quantity * item.variant.weight
16
+ end
17
+
18
+ SolidusEasypost::ParcelDimension.new(weight: total_weight)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class Configuration
5
+ attr_accessor :purchase_labels, :track_all_cartons
6
+ attr_writer :shipping_rate_calculator_class, :shipping_method_selector_class, :parcel_dimension_calculator_class,
7
+ :webhook_handler_class
8
+
9
+ def initialize
10
+ self.purchase_labels = true
11
+ self.track_all_cartons = false
12
+ end
13
+
14
+ def shipping_rate_calculator_class
15
+ @shipping_rate_calculator_class ||= 'SolidusEasypost::ShippingRateCalculator'
16
+ @shipping_rate_calculator_class.constantize
17
+ end
18
+
19
+ def shipping_method_selector_class
20
+ @shipping_method_selector_class ||= 'SolidusEasypost::ShippingMethodSelector'
21
+ @shipping_method_selector_class.constantize
22
+ end
23
+
24
+ def parcel_dimension_calculator_class
25
+ @parcel_dimension_calculator_class ||= 'SolidusEasypost::Calculator::WeightDimensionCalculator'
26
+ @parcel_dimension_calculator_class.constantize
27
+ end
28
+
29
+ def webhook_handler_class
30
+ @webhook_handler_class ||= 'SolidusEasypost::TrackerWebhookHandler'
31
+ @webhook_handler_class.constantize
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spree/core'
4
+ require 'solidus_easypost'
5
+
6
+ module SolidusEasypost
7
+ class Engine < Rails::Engine
8
+ include SolidusSupport::EngineExtensions
9
+
10
+ isolate_namespace ::Spree
11
+
12
+ engine_name 'solidus_easypost'
13
+
14
+ # use rspec for tests
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ module Errors
5
+ class UnknownPartialResourceError < StandardError
6
+ def message
7
+ 'The passed resource must be a Spree::ReturnAuthorization or a Spree::Stock::Package!'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class Estimator
5
+ def shipping_rates(package, _frontend_only = true)
6
+ easypost_rates = ShipmentBuilder.from_package(package).rates.sort_by(&:rate)
7
+
8
+ shipping_rates = easypost_rates.map { |rate| build_shipping_rate(rate) }.compact
9
+ shipping_rates.min_by(&:cost)&.selected = true
10
+
11
+ shipping_rates
12
+ end
13
+
14
+ private
15
+
16
+ def build_shipping_rate(rate)
17
+ shipping_method = shipping_method_selector.shipping_method_for(rate)
18
+ return unless shipping_method.available_to_users?
19
+
20
+ ::Spree::ShippingRate.new(
21
+ name: "#{rate.carrier} #{rate.service}",
22
+ cost: shipping_rate_calculator.compute(rate),
23
+ easy_post_shipment_id: rate.shipment_id,
24
+ easy_post_rate_id: rate.id,
25
+ shipping_method: shipping_method,
26
+ )
27
+ end
28
+
29
+ def shipping_rate_calculator
30
+ @shipping_rate_calculator ||= SolidusEasypost.configuration.shipping_rate_calculator_class.new
31
+ end
32
+
33
+ def shipping_method_selector
34
+ @shipping_method_selector ||= SolidusEasypost.configuration.shipping_method_selector_class.new
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class ParcelBuilder
5
+ class << self
6
+ def from_package(package)
7
+ parcel_dimension = SolidusEasypost
8
+ .configuration
9
+ .parcel_dimension_calculator_class
10
+ .new
11
+ .compute(package)
12
+
13
+ ::EasyPost::Parcel.create(parcel_dimension.to_h)
14
+ end
15
+
16
+ def from_return_authorization(return_authorization)
17
+ parcel_dimension = SolidusEasypost
18
+ .configuration
19
+ .parcel_dimension_calculator_class
20
+ .new
21
+ .compute(return_authorization)
22
+
23
+ ::EasyPost::Parcel.create(parcel_dimension.to_h)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class ShipmentBuilder
5
+ class << self
6
+ def from_package(package)
7
+ ::EasyPost::Shipment.create(
8
+ to_address: AddressBuilder.from_address(package.order.ship_address),
9
+ from_address: AddressBuilder.from_stock_location(package.stock_location),
10
+ parcel: ParcelBuilder.from_package(package),
11
+ )
12
+ end
13
+
14
+ def from_shipment(shipment)
15
+ ::EasyPost::Shipment.create(
16
+ to_address: AddressBuilder.from_address(shipment.order.ship_address),
17
+ from_address: AddressBuilder.from_stock_location(shipment.stock_location),
18
+ parcel: ParcelBuilder.from_package(shipment.to_package),
19
+ )
20
+ end
21
+
22
+ def from_return_authorization(return_authorization)
23
+ ::EasyPost::Shipment.create(
24
+ from_address: AddressBuilder.from_stock_location(return_authorization.stock_location),
25
+ to_address: AddressBuilder.from_address(return_authorization.order.ship_address),
26
+ parcel: ParcelBuilder.from_return_authorization(return_authorization),
27
+ is_return: true
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class ShippingMethodSelector
5
+ def shipping_method_for(rate)
6
+ ::Spree::ShippingMethod.find_or_create_by(
7
+ carrier: rate.carrier,
8
+ service_level: rate.service,
9
+ ) do |shipping_method|
10
+ shipping_method.name = "#{rate.carrier} #{rate.service}"
11
+ shipping_method.calculator = ::Spree::Calculator::Shipping::FlatRate.create
12
+ shipping_method.shipping_categories = [::Spree::ShippingCategory.first]
13
+ shipping_method.available_to_users = false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusEasypost
4
+ class ShippingRateCalculator
5
+ def compute(rate)
6
+ rate.rate
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.modify do
4
+ factory :address do
5
+ address1 { '215 N 7th Ave' }
6
+ city { 'Manville' }
7
+ association(:state, name: 'New Jersey', abbr: 'NJ')
8
+ zipcode { '08835' }
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.modify do
4
+ factory :base_product do
5
+ weight { 10.0 }
6
+ end
7
+ end