solidus_backtracs 2.2.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/.bundle/config +2 -0
- data/.circleci/config.yml +41 -0
- data/.gem_release.yml +5 -0
- data/.github/stale.yml +17 -0
- data/.github_changelog_generator +2 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +40 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +33 -0
- data/LICENSE +26 -0
- data/README.md +208 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/spree/backend/solidus_backtracs.js +2 -0
- data/app/assets/javascripts/spree/frontend/solidus_backtracs.js +2 -0
- data/app/assets/stylesheets/spree/backend/solidus_backtracs.css +4 -0
- data/app/assets/stylesheets/spree/frontend/solidus_backtracs.css +4 -0
- data/app/controllers/spree/backtracs_controller.rb +46 -0
- data/app/decorators/models/solidus_backtracs/spree/shipment_decorator.rb +33 -0
- data/app/helpers/solidus_backtracs/export_helper.rb +52 -0
- data/app/jobs/solidus_backtracs/api/schedule_shipment_syncs_job.rb +28 -0
- data/app/jobs/solidus_backtracs/api/sync_shipment_job.rb +17 -0
- data/app/jobs/solidus_backtracs/api/sync_shipments_job.rb +41 -0
- data/app/queries/solidus_backtracs/shipment/between_query.rb +14 -0
- data/app/queries/solidus_backtracs/shipment/exportable_query.rb +24 -0
- data/app/queries/solidus_backtracs/shipment/pending_api_sync_query.rb +51 -0
- data/app/views/spree/backtracs/export.xml.builder +58 -0
- data/bin/console +17 -0
- data/bin/rails +7 -0
- data/bin/rails-engine +13 -0
- data/bin/rails-sandbox +16 -0
- data/bin/rake +7 -0
- data/bin/sandbox +86 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20210220093010_add_backtracs_api_sync_fields.rb +8 -0
- data/lib/generators/solidus_backtracs/install/install_generator.rb +27 -0
- data/lib/generators/solidus_backtracs/install/templates/initializer.rb +91 -0
- data/lib/solidus_backtracs/api/batch_syncer.rb +45 -0
- data/lib/solidus_backtracs/api/client.rb +36 -0
- data/lib/solidus_backtracs/api/rate_limited_error.rb +23 -0
- data/lib/solidus_backtracs/api/request_error.rb +33 -0
- data/lib/solidus_backtracs/api/request_runner.rb +87 -0
- data/lib/solidus_backtracs/api/shipment_serializer.rb +103 -0
- data/lib/solidus_backtracs/api/threshold_verifier.rb +28 -0
- data/lib/solidus_backtracs/configuration.rb +62 -0
- data/lib/solidus_backtracs/engine.rb +19 -0
- data/lib/solidus_backtracs/errors.rb +23 -0
- data/lib/solidus_backtracs/shipment_notice.rb +58 -0
- data/lib/solidus_backtracs/testing_support/factories.rb +4 -0
- data/lib/solidus_backtracs/version.rb +5 -0
- data/lib/solidus_backtracs.rb +16 -0
- data/solidus_shipstation.gemspec +39 -0
- data/spec/controllers/spree/backtracs_controller_spec.rb +103 -0
- data/spec/fixtures/backtracs_xml_schema.xsd +171 -0
- data/spec/jobs/solidus_backtracs/api/schedule_shipment_syncs_job_spec.rb +32 -0
- data/spec/jobs/solidus_backtracs/api/sync_shipments_job_spec.rb +102 -0
- data/spec/lib/solidus_backtracs/api/batch_syncer_spec.rb +228 -0
- data/spec/lib/solidus_backtracs/api/client_spec.rb +120 -0
- data/spec/lib/solidus_backtracs/api/rate_limited_error_spec.rb +21 -0
- data/spec/lib/solidus_backtracs/api/request_error_spec.rb +20 -0
- data/spec/lib/solidus_backtracs/api/request_runner_spec.rb +65 -0
- data/spec/lib/solidus_backtracs/api/shipment_serializer_spec.rb +25 -0
- data/spec/lib/solidus_backtracs/api/threshold_verifier_spec.rb +61 -0
- data/spec/lib/solidus_backtracs/shipment_notice_spec.rb +111 -0
- data/spec/lib/solidus_backtracs_spec.rb +9 -0
- data/spec/models/spree/shipment_spec.rb +49 -0
- data/spec/queries/solidus_backtracs/shipment/between_query_spec.rb +53 -0
- data/spec/queries/solidus_backtracs/shipment/exportable_query_spec.rb +53 -0
- data/spec/queries/solidus_backtracs/shipment/pending_api_sync_query_spec.rb +37 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/configuration_helper.rb +13 -0
- data/spec/support/controllers.rb +1 -0
- data/spec/support/webmock.rb +3 -0
- data/spec/support/xsd.rb +5 -0
- metadata +248 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Api
|
5
|
+
class SyncShipmentsJob < ApplicationJob
|
6
|
+
queue_as :default
|
7
|
+
|
8
|
+
def perform(shipments)
|
9
|
+
shipments = select_shipments(shipments)
|
10
|
+
return if shipments.empty?
|
11
|
+
|
12
|
+
sync_shipments(shipments)
|
13
|
+
rescue RateLimitedError => e
|
14
|
+
self.class.set(wait: e.retry_in).perform_later
|
15
|
+
rescue StandardError => e
|
16
|
+
SolidusBacktracs.config.error_handler.call(e, {})
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def select_shipments(shipments)
|
22
|
+
shipments.select do |shipment|
|
23
|
+
if ThresholdVerifier.call(shipment)
|
24
|
+
true
|
25
|
+
else
|
26
|
+
::Spree::Event.fire(
|
27
|
+
'solidus_backtracs.api.sync_skipped',
|
28
|
+
shipment: shipment,
|
29
|
+
)
|
30
|
+
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def sync_shipments(shipments)
|
37
|
+
BatchSyncer.from_config.call(shipments)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Shipment
|
5
|
+
class BetweenQuery
|
6
|
+
def self.apply(scope, from:, to:)
|
7
|
+
scope.joins(:order).where(<<~SQL.squish, from: from, to: to)
|
8
|
+
(spree_shipments.updated_at > :from AND spree_shipments.updated_at < :to) OR
|
9
|
+
(spree_orders.updated_at > :from AND spree_orders.updated_at < :to)
|
10
|
+
SQL
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Shipment
|
5
|
+
class ExportableQuery
|
6
|
+
def self.apply(scope)
|
7
|
+
scope = scope
|
8
|
+
.order(:updated_at)
|
9
|
+
.joins(:order)
|
10
|
+
.merge(::Spree::Order.complete)
|
11
|
+
|
12
|
+
unless SolidusBacktracs.configuration.capture_at_notification
|
13
|
+
scope = scope.where(spree_shipments: { state: ['ready', 'canceled'] })
|
14
|
+
end
|
15
|
+
|
16
|
+
unless SolidusBacktracs.configuration.export_canceled_shipments
|
17
|
+
scope = scope.where.not(spree_shipments: { state: 'canceled' })
|
18
|
+
end
|
19
|
+
|
20
|
+
scope
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Shipment
|
5
|
+
class PendingApiSyncQuery
|
6
|
+
SQLITE_CONDITION = <<~SQL.squish
|
7
|
+
(
|
8
|
+
spree_shipments.backtracs_synced_at IS NULL
|
9
|
+
) AND ((JULIANDAY(CURRENT_TIMESTAMP) - JULIANDAY(spree_orders.updated_at)) * 86400.0) < :threshold
|
10
|
+
SQL
|
11
|
+
|
12
|
+
POSTGRES_CONDITION = <<~SQL.squish
|
13
|
+
(
|
14
|
+
spree_shipments.backtracs_synced_at IS NULL
|
15
|
+
) AND (EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - spree_orders.updated_at))) < :threshold
|
16
|
+
SQL
|
17
|
+
|
18
|
+
MYSQL2_CONDITION = <<~SQL.squish
|
19
|
+
(
|
20
|
+
spree_shipments.backtracs_synced_at IS NULL
|
21
|
+
) AND (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(spree_orders.updated_at)) < :threshold
|
22
|
+
SQL
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def apply(scope)
|
26
|
+
scope
|
27
|
+
.joins(:order)
|
28
|
+
.merge(::Spree::Order.complete)
|
29
|
+
.where(condition_for_adapter, threshold: SolidusBacktracs.config.api_sync_threshold / 1.second)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def condition_for_adapter
|
35
|
+
db_adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
36
|
+
|
37
|
+
case db_adapter
|
38
|
+
when /sqlite/
|
39
|
+
SQLITE_CONDITION
|
40
|
+
when /postgres/
|
41
|
+
POSTGRES_CONDITION
|
42
|
+
when /mysql2/
|
43
|
+
MYSQL2_CONDITION
|
44
|
+
else
|
45
|
+
fail "Backtracs API sync not supported for DB adapter #{db_adapter}!"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
xml = Builder::XmlMarkup.new
|
4
|
+
xml.instruct!
|
5
|
+
xml.Orders(pages: (@shipments.total_count / 50.0).ceil) {
|
6
|
+
@shipments.each do |shipment|
|
7
|
+
order = shipment.order
|
8
|
+
|
9
|
+
xml.Order {
|
10
|
+
xml.OrderID shipment.id
|
11
|
+
xml.OrderNumber shipment.number # do not use shipment.order.number as this presents lookup issues
|
12
|
+
xml.OrderDate order.completed_at.strftime(SolidusBacktracs::ExportHelper::DATE_FORMAT)
|
13
|
+
xml.OrderStatus shipment.state
|
14
|
+
xml.LastModified [order.completed_at, shipment.updated_at].max.strftime(SolidusBacktracs::ExportHelper::DATE_FORMAT)
|
15
|
+
xml.ShippingMethod shipment.shipping_method.try(:name)
|
16
|
+
xml.OrderTotal order.total
|
17
|
+
xml.TaxAmount order.tax_total
|
18
|
+
xml.ShippingAmount order.ship_total
|
19
|
+
xml.CustomField1 order.number
|
20
|
+
|
21
|
+
# if order.gift?
|
22
|
+
# xml.Gift
|
23
|
+
# xml.GiftMessage
|
24
|
+
# end
|
25
|
+
|
26
|
+
xml.Customer {
|
27
|
+
xml.CustomerCode order.email.slice(0, 50)
|
28
|
+
SolidusBacktracs::ExportHelper.address(xml, order, :bill)
|
29
|
+
SolidusBacktracs::ExportHelper.address(xml, order, :ship)
|
30
|
+
}
|
31
|
+
xml.Items {
|
32
|
+
shipment.line_items.each do |line|
|
33
|
+
variant = line.variant
|
34
|
+
xml.Item {
|
35
|
+
xml.SKU variant.sku
|
36
|
+
xml.Name [variant.product.name, variant.options_text].join(' ')
|
37
|
+
xml.ImageUrl variant.images.first.try(:attachment).try(:url)
|
38
|
+
xml.Weight variant.weight.to_f
|
39
|
+
xml.WeightUnits SolidusBacktracs.configuration.weight_units
|
40
|
+
xml.Quantity line.quantity
|
41
|
+
xml.UnitPrice line.price
|
42
|
+
|
43
|
+
if variant.option_values.present?
|
44
|
+
xml.Options {
|
45
|
+
variant.option_values.each do |value|
|
46
|
+
xml.Option {
|
47
|
+
xml.Name value.option_type.presentation
|
48
|
+
xml.Value value.name
|
49
|
+
}
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
}
|
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_backtracs"
|
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
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_backtracs/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
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_backtracs"
|
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 solidus: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
data/config/routes.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# NOTE: This migration is only required if you use the API integration strategy.
|
2
|
+
# If you're using the XML file instead, you can safely skip these columns.
|
3
|
+
|
4
|
+
class AddBacktracsApiSyncFields < ActiveRecord::Migration[5.2]
|
5
|
+
def change
|
6
|
+
add_column :spree_shipments, :backtracs_synced_at, :datetime
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
class_option :auto_run_migrations, type: :boolean, default: false
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
template 'initializer.rb', 'config/initializers/solidus_backtracs.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_migrations
|
14
|
+
run 'bin/rails railties:install:migrations FROM=solidus_backtracs'
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_migrations
|
18
|
+
run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
|
19
|
+
if run_migrations
|
20
|
+
run 'bin/rails db:migrate'
|
21
|
+
else
|
22
|
+
puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
SolidusBacktracs.configure do |config|
|
4
|
+
# Choose between Grams, Ounces or Pounds.
|
5
|
+
config.weight_units = "Grams"
|
6
|
+
|
7
|
+
# Capture payment when Backtracs notifies a shipping label creation.
|
8
|
+
# Set this to `true` and `Spree::Config.require_payment_to_ship` to `false` if you
|
9
|
+
# want to charge your customers at the time of shipment.
|
10
|
+
config.capture_at_notification = false
|
11
|
+
|
12
|
+
## API Configuration
|
13
|
+
config.api_base = ENV['BACKTRACS_API_BASE'] || 'https://bactracstest.andlor.com'
|
14
|
+
|
15
|
+
# Backtracs expects the endpoint to be protected by HTTP Basic Auth.
|
16
|
+
# Set the username and password you desire for Backtracs to use.
|
17
|
+
config.webhook_username = "smoking_jay_cutler"
|
18
|
+
config.webhook_password = "my-awesome-password"
|
19
|
+
|
20
|
+
## Proxy
|
21
|
+
config.proxy_address = ENV['PROXY_ADDRESS']
|
22
|
+
config.proxy_port = ENV['PROXY_PORT']
|
23
|
+
config.proxy_username = ENV['PROXY_USER']
|
24
|
+
config.proxy_password = ENV['PROXY_PASS']
|
25
|
+
|
26
|
+
## Authentication Service Credentials
|
27
|
+
config.authentication_username = "red_blue_jay"
|
28
|
+
config.authentication_password = "my-secret-other-password"
|
29
|
+
|
30
|
+
|
31
|
+
## Shipment Serializer Configuration
|
32
|
+
config.sku_map = {}
|
33
|
+
config.default_rma_type = "W"
|
34
|
+
config.default_carrier = "FedExGrnd"
|
35
|
+
config.default_ship_method = "GROUND"
|
36
|
+
config.default_status = "OPEN"
|
37
|
+
config.default_rp_location = "FG-NEW"
|
38
|
+
config.shippable_skus = []
|
39
|
+
config.default_property_name = "XYZ"
|
40
|
+
|
41
|
+
####### XML integration
|
42
|
+
# Only uncomment these lines if you're going to use the XML integration.
|
43
|
+
|
44
|
+
# Export canceled shipments to Backtracs
|
45
|
+
# Set this to `true` if you want canceled shipments included in the endpoint.
|
46
|
+
# config.export_canceled_shipments = false
|
47
|
+
|
48
|
+
# You can customize the class used to receive notifications from the POST request
|
49
|
+
# Make sure it has a class method `from_payload` which receives the notification hash
|
50
|
+
# and an instance method `apply`
|
51
|
+
# config.shipment_notice_class = 'SolidusBacktracs::ShipmentNotice'
|
52
|
+
|
53
|
+
####### API integration
|
54
|
+
# Only uncomment these lines if you're going to use the API integration.
|
55
|
+
|
56
|
+
# Override the shipment serializer used for API sync. This can be any object
|
57
|
+
# that responds to `#call`. At the very least, you'll need to uncomment the
|
58
|
+
# following lines and customize your store ID.
|
59
|
+
# config.api_shipment_serializer = proc do |shipment|
|
60
|
+
# SolidusBacktracs::Api::ShipmentSerializer.new(store_id: '12345678').call(shipment)
|
61
|
+
# end
|
62
|
+
|
63
|
+
# Override the logic used to match a Backtracs order to a shipment from a
|
64
|
+
# given collection. This can be useful when you override the default serializer
|
65
|
+
# and change the logic used to generate the order number.
|
66
|
+
# config.api_shipment_matcher = proc do |backtracs_order, shipments|
|
67
|
+
# shipments.find { |shipment| shipment.number == backtracs_order['orderNumber'] }
|
68
|
+
# end
|
69
|
+
|
70
|
+
# API key and secret for accessing the Backtracs API.
|
71
|
+
# config.api_key = "api-key"
|
72
|
+
# config.api_secret = "api-secret"
|
73
|
+
|
74
|
+
# Number of shipments to import into Backtracs at once.
|
75
|
+
# If unsure, leave this set to 100, which is the maximum
|
76
|
+
# number of shipments that can be imported at once.
|
77
|
+
config.api_batch_size = 100
|
78
|
+
|
79
|
+
# Period of time after which the integration will "drop" shipments and stop
|
80
|
+
# trying to create/update them. This prevents the API from retrying indefinitely
|
81
|
+
# in case an error prevents some shipments from being created/updated.
|
82
|
+
config.api_sync_threshold = 7.days
|
83
|
+
|
84
|
+
# Error handler used by the API integration for certain non-critical errors (e.g.
|
85
|
+
# a failure when serializing a shipment from a batch). This should be a proc that
|
86
|
+
# accepts an exception and a context hash. Popular options for error handling are
|
87
|
+
# logging or sending the error to an error tracking tool such as Sentry.
|
88
|
+
config.error_handler = -> (error, context = {}) {
|
89
|
+
Sentry.capture_exception(error, extra: context)
|
90
|
+
}
|
91
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Api
|
5
|
+
class BatchSyncer
|
6
|
+
class << self
|
7
|
+
def from_config
|
8
|
+
new(
|
9
|
+
client: SolidusBacktracs::Api::Client.from_config,
|
10
|
+
shipment_matcher: SolidusBacktracs.config.api_shipment_matcher,
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :client, :shipment_matcher
|
16
|
+
|
17
|
+
def initialize(client:, shipment_matcher:)
|
18
|
+
@client = client
|
19
|
+
@shipment_matcher = shipment_matcher
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(shipments)
|
23
|
+
begin
|
24
|
+
response = client.bulk_create_orders(shipments)
|
25
|
+
rescue RateLimitedError => e
|
26
|
+
::Spree::Event.fire(
|
27
|
+
'solidus_backtracs.api.rate_limited',
|
28
|
+
shipments: shipments,
|
29
|
+
error: e,
|
30
|
+
)
|
31
|
+
|
32
|
+
raise e
|
33
|
+
rescue RequestError => e
|
34
|
+
::Spree::Event.fire(
|
35
|
+
'solidus_backtracs.api.sync_errored',
|
36
|
+
shipments: shipments,
|
37
|
+
error: e,
|
38
|
+
)
|
39
|
+
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Api
|
5
|
+
class Client
|
6
|
+
class << self
|
7
|
+
def from_config
|
8
|
+
new(
|
9
|
+
request_runner: RequestRunner.new,
|
10
|
+
error_handler: SolidusBacktracs.config.error_handler,
|
11
|
+
shipment_serializer: SolidusBacktracs.config.api_shipment_serializer,
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :request_runner, :error_handler, :shipment_serializer
|
17
|
+
|
18
|
+
def initialize(request_runner:, error_handler:, shipment_serializer:)
|
19
|
+
@request_runner = request_runner
|
20
|
+
@error_handler = error_handler
|
21
|
+
@shipment_serializer = shipment_serializer
|
22
|
+
end
|
23
|
+
|
24
|
+
def bulk_create_orders(shipments)
|
25
|
+
shipments.each do |shipment|
|
26
|
+
SolidusBacktracs::Api::SyncShipmentJob.perform_now(
|
27
|
+
shipment_id: shipment.id,
|
28
|
+
error_handler: @error_handler,
|
29
|
+
shipment_serializer: @shipment_serializer,
|
30
|
+
request_runner: @request_runner
|
31
|
+
)
|
32
|
+
end.compact
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Api
|
5
|
+
class RateLimitedError < RequestError
|
6
|
+
attr_reader :retry_in
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def options_from_response(response)
|
10
|
+
super.merge(
|
11
|
+
retry_in: response.headers['X-Rate-Limit-Reset'].to_i.seconds,
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(retry_in:, **options)
|
17
|
+
super(**options)
|
18
|
+
|
19
|
+
@retry_in = retry_in
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusBacktracs
|
4
|
+
module Api
|
5
|
+
class RequestError < RuntimeError
|
6
|
+
attr_reader :response_code, :response_body, :response_headers
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_response(response)
|
10
|
+
new(**options_from_response(response))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def options_from_response(response)
|
16
|
+
{
|
17
|
+
response_code: response.code,
|
18
|
+
response_headers: response.headers,
|
19
|
+
response_body: response.body,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(response_code:, response_body:, response_headers:)
|
25
|
+
@response_code = response_code
|
26
|
+
@response_body = response_body
|
27
|
+
@response_headers = response_headers
|
28
|
+
|
29
|
+
super(response_body)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|