super_good-solidus_taxjar 0.18.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +58 -0
  3. data/CHANGELOG.md +118 -5
  4. data/Gemfile +28 -10
  5. data/README.md +300 -45
  6. data/Rakefile +3 -0
  7. data/app/controllers/spree/admin/taxjar_settings_controller.rb +93 -0
  8. data/app/controllers/spree/admin/taxjar_transactions_controller.rb +37 -0
  9. data/app/controllers/spree/admin/transaction_sync_batches_controller.rb +18 -0
  10. data/app/jobs/super_good/solidus_taxjar/backfill_transaction_sync_batch_job.rb +23 -0
  11. data/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb +26 -0
  12. data/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb +22 -0
  13. data/app/models/super_good/solidus_taxjar/configuration.rb +27 -0
  14. data/app/models/super_good/solidus_taxjar/order_transaction.rb +16 -0
  15. data/app/models/super_good/solidus_taxjar/refund_transaction.rb +13 -0
  16. data/app/models/super_good/solidus_taxjar/transaction_sync_batch.rb +11 -0
  17. data/app/models/super_good/solidus_taxjar/transaction_sync_log.rb +8 -0
  18. data/app/overrides/spree/admin/orders_controller_override.rb +12 -0
  19. data/app/overrides/spree/admin/shared/_order_submenu/add_taxjar_sync_history_tab.html.erb.deface +6 -0
  20. data/app/overrides/spree/admin/shared/_order_summary/add_taxjar_reported_at.html.erb.deface +30 -0
  21. data/app/overrides/spree/admin/shared/_taxes_tabs/add_configuration_menu_items.html.erb.deface +5 -0
  22. data/app/overrides/super_good/solidus_taxjar/spree/order_override.rb +21 -0
  23. data/app/views/spree/admin/orders/taxjar_transactions.html.erb +4 -0
  24. data/app/views/spree/admin/shared/_transaction_sync_log_table.html.erb +35 -0
  25. data/app/views/spree/admin/taxjar_settings/_nexus_regions.html.erb +23 -0
  26. data/app/views/spree/admin/taxjar_settings/_tax_categories.html.erb +41 -0
  27. data/app/views/spree/admin/taxjar_settings/edit.html.erb +17 -0
  28. data/app/views/spree/admin/taxjar_settings/edit_no_api_key.html.erb +21 -0
  29. data/app/views/spree/admin/transaction_sync_batches/index.html.erb +50 -0
  30. data/app/views/spree/admin/transaction_sync_batches/show.html.erb +7 -0
  31. data/bin/console +2 -0
  32. data/bin/rails-engine +1 -1
  33. data/bin/sandbox +43 -36
  34. data/bin/setup +3 -3
  35. data/config/routes.rb +19 -0
  36. data/db/migrate/20210908205201_create_taxjar_order_transactions.rb +16 -0
  37. data/db/migrate/20211008175113_create_taxjar_refund_transaction.rb +15 -0
  38. data/db/migrate/20211008183858_add_transaction_date_to_order_transaction.rb +5 -0
  39. data/db/migrate/20211119143354_create_configuration.rb +8 -0
  40. data/db/migrate/20220405213958_create_transaction_sync_batches.rb +7 -0
  41. data/db/migrate/20220405215225_create_transaction_sync_logs.rb +14 -0
  42. data/db/migrate/20220908181655_add_dates_to_transaction_sync_batch.rb +6 -0
  43. data/db/migrate/20220912182210_allow_null_transaction_sync_batches_on_logs.rb +5 -0
  44. data/db/migrate/20230320211309_add_refund_transaction_to_sync_logs.rb +5 -0
  45. data/lib/generators/super_good/solidus_taxjar/install/install_generator.rb +68 -0
  46. data/lib/super_good/engine.rb +2 -0
  47. data/lib/super_good/solidus_taxjar/addresses.rb +3 -3
  48. data/lib/super_good/solidus_taxjar/api.rb +45 -9
  49. data/lib/super_good/solidus_taxjar/api_params.rb +93 -26
  50. data/lib/super_good/solidus_taxjar/backfill_transactions.rb +11 -0
  51. data/lib/super_good/solidus_taxjar/cached_api.rb +23 -0
  52. data/lib/super_good/solidus_taxjar/calculator_helper.rb +34 -5
  53. data/lib/super_good/solidus_taxjar/discount_calculator.rb +1 -1
  54. data/lib/super_good/solidus_taxjar/overrides/request_override.rb +15 -0
  55. data/lib/super_good/solidus_taxjar/reportable.rb +91 -0
  56. data/lib/super_good/solidus_taxjar/reporting.rb +44 -0
  57. data/lib/super_good/solidus_taxjar/spree/legacy_reporting_subscriber.rb +41 -0
  58. data/lib/super_good/solidus_taxjar/spree/reporting_subscriber.rb +40 -0
  59. data/lib/super_good/solidus_taxjar/tax_calculator.rb +10 -4
  60. data/lib/super_good/solidus_taxjar/testing_support/factories/address_factory.rb +11 -0
  61. data/lib/super_good/solidus_taxjar/testing_support/factories/configuration_factory.rb +11 -0
  62. data/lib/super_good/solidus_taxjar/testing_support/factories/order_transaction_factory.rb +22 -0
  63. data/lib/super_good/solidus_taxjar/testing_support/factories/refund_transaction_factory.rb +7 -0
  64. data/lib/super_good/solidus_taxjar/testing_support/factories/transaction_sync_batch_factory.rb +9 -0
  65. data/lib/super_good/solidus_taxjar/testing_support/factories/transaction_sync_log_factory.rb +18 -0
  66. data/lib/super_good/solidus_taxjar/transaction_id_generator.rb +45 -0
  67. data/lib/super_good/solidus_taxjar/version.rb +1 -1
  68. data/lib/super_good/solidus_taxjar.rb +30 -3
  69. data/spec/features/spree/admin/backfill_transactions_spec.rb +138 -0
  70. data/spec/features/spree/admin/refund_spec.rb +167 -0
  71. data/spec/features/spree/admin/reporting_to_taxjar_spec.rb +156 -0
  72. data/spec/features/spree/admin/taxjar_settings_spec.rb +90 -0
  73. data/spec/features/spree/checkout_spec.rb +58 -0
  74. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/GET_sync_nexus_regions/Taxjar_API_token_is_not_set/doesn_t_make_a_request_for_the_nexus_regions.yml +57 -0
  75. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/GET_sync_tax_categories/Taxjar_API_token_is_not_set/doesn_t_make_a_request_for_the_tax_categories.yml +57 -0
  76. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_API_token_is_set/shows_the_settings_page.yml +2437 -0
  77. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_API_token_isn_t_set/doesn_t_show_any_other_TaxJar_features.yml +57 -0
  78. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_API_token_isn_t_set/shows_a_descriptive_error_message.yml +57 -0
  79. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_reporting_is_enabled/shows_that_reporting_is_enabled.yml +2382 -0
  80. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/order_is_shipped/the_user_backfills_their_transactions.yml +2511 -0
  81. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/the_user_navigates_to_the_TaxJar_Settings.yml +2382 -0
  82. data/spec/fixtures/cassettes/Admin_Transaction_Sync_Batches/user_has_a_shipped_order/starts_a_transaction_backfill.yml +370 -0
  83. data/spec/fixtures/cassettes/Reporting_orders_to_TaxJar/shipping_a_complete_and_paid_order.yml +310 -0
  84. data/spec/fixtures/cassettes/Reporting_orders_to_TaxJar/updating_an_order_which_was_not_reported_due_to_failure/it_reports_the_order_instead_of_trying_to_replace_it.yml +794 -0
  85. data/spec/fixtures/cassettes/Reporting_orders_to_TaxJar/with_an_order_with_invalid_zipcode/retry_of_a_previously_failed_transaction_sync.yml +418 -0
  86. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/creates_a_batch.yml +250 -0
  87. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/creates_a_log_in_the_batch_with_an_order.yml +250 -0
  88. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/user_supplies_a_start_date/creates_a_batch.yml +250 -0
  89. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/user_supplies_a_start_date/user_supplies_start_and_end_date/creates_a_batch.yml +250 -0
  90. data/spec/fixtures/cassettes/SuperGood_SolidusTaxjar_CalculatorHelper/_taxable_address_/when_taxable_address_check_returns_true/with_US_address/when_the_address_is_not_within_a_nexus_region/1_3_2_2_2_1.yml +58 -0
  91. data/spec/fixtures/cassettes/SuperGood_SolidusTaxjar_CalculatorHelper/_taxable_address_/when_taxable_address_check_returns_true/with_US_address/when_the_address_is_within_a_nexus_region/1_3_2_2_1_1.yml +58 -0
  92. data/spec/fixtures/cassettes/SuperGood_SolidusTaxjar_Reporting/_refund_and_create_transaction/when_Taxjar_cannot_create_a_refund_transaction/doesn_t_create_a_new_transaction.yml +393 -0
  93. data/spec/fixtures/cassettes/SuperGood_SolidusTaxjar_Reporting/_refund_and_create_transaction/when_Taxjar_cannot_create_a_refund_transaction/raises_an_error.yml +393 -0
  94. data/spec/fixtures/cassettes/Taxjar_API_Request/logging_is_disabled/doesn_t_call_the_logger.yml +158 -0
  95. data/spec/fixtures/cassettes/Taxjar_API_Request/logging_is_enabled/calls_the_logger.yml +158 -0
  96. data/spec/fixtures/cassettes/features/spree/admin/checkout.yml +238 -0
  97. data/spec/fixtures/cassettes/features/spree/admin/refund.yml +1162 -0
  98. data/spec/jobs/super_good/solidus_taxjar/backfill_transaction_sync_batch_job_spec.rb +117 -0
  99. data/spec/jobs/super_good/solidus_taxjar/replace_transaction_job_spec.rb +95 -0
  100. data/spec/jobs/super_good/solidus_taxjar/report_transaction_job_spec.rb +76 -0
  101. data/spec/models/super_good/solidus_taxjar/configuration_spec.rb +79 -0
  102. data/spec/models/super_good/solidus_taxjar/order_transaction_spec.rb +36 -0
  103. data/spec/models/super_good/solidus_taxjar/transaction_sync_batch_spec.rb +48 -0
  104. data/spec/requests/spree/admin/order_request_spec.rb +121 -0
  105. data/spec/requests/spree/admin/taxjar_settings_request_spec.rb +198 -0
  106. data/spec/requests/spree/admin/taxjar_transactions_request_spec.rb +62 -0
  107. data/spec/requests/spree/admin/transaction_sync_batches_request_spec.rb +82 -0
  108. data/spec/spec_helper.rb +47 -4
  109. data/spec/subscribers/super_good/solidus_taxjar/spree/reporting_subscriber_spec.rb +278 -0
  110. data/spec/super_good/solidus_taxjar/addresses_spec.rb +0 -14
  111. data/spec/super_good/solidus_taxjar/api_params_spec.rb +284 -80
  112. data/spec/super_good/solidus_taxjar/api_spec.rb +168 -28
  113. data/spec/super_good/solidus_taxjar/backfill_transactions_spec.rb +24 -0
  114. data/spec/super_good/solidus_taxjar/cached_api_spec.rb +58 -0
  115. data/spec/super_good/solidus_taxjar/calculator_helper_spec.rb +131 -0
  116. data/spec/super_good/solidus_taxjar/discount_calculator_spec.rb +19 -2
  117. data/spec/super_good/solidus_taxjar/reportable_spec.rb +194 -0
  118. data/spec/super_good/solidus_taxjar/reporting_spec.rb +243 -0
  119. data/spec/super_good/solidus_taxjar/tax_calculator_spec.rb +19 -19
  120. data/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb +8 -3
  121. data/spec/super_good/solidus_taxjar/transaction_id_generator_spec.rb +77 -0
  122. data/spec/super_good/solidus_taxjar_spec.rb +99 -0
  123. data/spec/support/checkoutable_store_shared_context.rb +19 -0
  124. data/spec/support/solidus_events_helper.rb +26 -0
  125. data/spec/taxjar/api/request_spec.rb +52 -0
  126. data/super_good-solidus_taxjar.gemspec +4 -3
  127. metadata +176 -14
data/bin/sandbox CHANGED
@@ -1,58 +1,65 @@
1
1
  #!/usr/bin/env bash
2
-
3
2
  set -e
3
+ test -z "${DEBUG+empty_string}" || set -x
4
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
5
+ test "$DB" = "sqlite" && export DB="sqlite3"
20
6
 
21
- if [ ! -z $SOLIDUS_BRANCH ]
7
+ if [ -z "$PAYMENT_METHOD" ]
22
8
  then
23
- BRANCH=$SOLIDUS_BRANCH
24
- else
25
- BRANCH="master"
9
+ PAYMENT_METHOD="none"
26
10
  fi
27
11
 
12
+ if [ -z "$SOLIDUS_BRANCH" ]
13
+ then
14
+ echo "~~> Use 'export SOLIDUS_BRANCH=[main|v4.0|...]' to control the Solidus branch"
15
+ SOLIDUS_BRANCH="main"
16
+ fi
17
+ echo "~~> Using branch $SOLIDUS_BRANCH of solidus"
18
+
19
+ case $SOLIDUS_BRANCH in
20
+ v3* | v4.0* | v4.1*)
21
+ echo "~~> Using a SOLIDUS_BRANCH older than v4.2. Skipping the --admin-preview flag for compatibility."
22
+ ADMIN_PREVIEW_FLAG=" "
23
+ ;;
24
+ *)
25
+ ADMIN_PREVIEW_FLAG="--admin-preview=false"
26
+ ;;
27
+ esac
28
+
28
29
  extension_name="super_good-solidus_taxjar"
29
30
 
30
31
  # Stay away from the bundler env of the containing extension.
31
32
  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
+ ruby -rbundler -e'
34
+ Bundler.with_unbundled_env {system *ARGV}' -- \
35
+ env BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES=true $@
33
36
  }
34
37
 
38
+ echo "~~~> Removing the old sandbox"
35
39
  rm -rf ./sandbox
36
- unbundled bundle exec rails new sandbox --database="$RAILSDB" \
37
- --skip-bundle \
40
+
41
+ echo "~~~> Creating a pristine Rails app"
42
+ rails new sandbox \
43
+ --database="${DB:-sqlite3}" \
38
44
  --skip-git \
39
45
  --skip-keeps \
40
46
  --skip-rc \
41
- --skip-spring \
42
- --skip-test \
43
- --skip-javascript
47
+ --skip-bootsnap \
48
+ --skip-test
44
49
 
45
50
  if [ ! -d "sandbox" ]; then
46
51
  echo 'sandbox rails application failed'
47
52
  exit 1
48
53
  fi
49
54
 
55
+ echo "~~~> Adding solidus (with i18n) to the Gemfile"
50
56
  cd ./sandbox
51
57
  cat <<RUBY >> Gemfile
52
- gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH'
53
- gem 'solidus_auth_devise', '>= 2.1.0'
58
+ gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH'
54
59
  gem 'rails-i18n'
55
60
  gem 'solidus_i18n'
61
+ gem 'solidus_auth_devise'
62
+ gem "solidus_frontend"
56
63
 
57
64
  gem '$extension_name', path: '..'
58
65
 
@@ -67,18 +74,18 @@ unbundled bundle install --gemfile Gemfile
67
74
 
68
75
  unbundled bundle exec rake db:drop db:create
69
76
 
70
- unbundled bundle exec rails generate spree:install \
77
+ unbundled bundle exec rails generate solidus:install \
71
78
  --auto-accept \
72
- --user_class=Spree::User \
73
- --enforce_available_locales=true \
74
- --with-authentication=false \
79
+ --payment-method=none \
80
+ --frontend=none \
81
+ ${ADMIN_PREVIEW_FLAG} \
75
82
  $@
76
83
 
77
- unbundled bundle exec rails generate solidus:auth:install
84
+ SKIP_SOLIDUS_BOLT=true unbundled bundle exec rails generate solidus_frontend:install --auto-accept
85
+ unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations
86
+ unbundled bundle exec rails generate super_good:solidus_taxjar:install --auto-run-migrations
78
87
 
79
88
  echo
80
89
  echo "🚀 Sandbox app successfully created for $extension_name!"
81
90
  echo "🚀 Using $RAILSDB and Solidus $BRANCH"
82
- echo "🚀 Use 'export DB=[postgres|mysql|sqlite]' to control the DB adapter"
83
- echo "🚀 Use 'export SOLIDUS_BRANCH=<BRANCH-NAME>' to control the Solidus version"
84
- echo "🚀 This app is intended for test purposes."
91
+ echo "🧪 This app is intended for test purposes."
data/bin/setup CHANGED
@@ -3,6 +3,6 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
6
+ gem install bundler --conservative
7
+ bundle update
8
+ bin/rake clobber
data/config/routes.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Core::Engine.routes.draw do
4
+ namespace :admin do
5
+ resource :taxjar_settings, only: [:edit, :update]
6
+ resources :transaction_sync_batches, only: [:index, :show, :create]
7
+ get 'taxjar_settings/sync_nexus_regions', to: 'taxjar_settings#sync_nexus_regions'
8
+ get 'taxjar_settings/sync_tax_categories', to: 'taxjar_settings#sync_tax_categories'
9
+ post 'taxjar_settings/backfill_transactions', to: 'taxjar_settings#backfill_transactions'
10
+
11
+ resources :orders do
12
+ member do
13
+ get :taxjar_transactions
14
+ end
15
+
16
+ post 'taxjar_transaction/retry', to: "taxjar_transactions#retry"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ class CreateTaxjarOrderTransactions < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :solidus_taxjar_order_transactions do |t|
4
+ t.references :order, null: false
5
+ t.string :transaction_id, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_foreign_key :solidus_taxjar_order_transactions,
11
+ :spree_orders,
12
+ column: :order_id
13
+
14
+ add_index :solidus_taxjar_order_transactions, :transaction_id, unique: true
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTaxjarRefundTransaction < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :solidus_taxjar_refund_transactions do |t|
4
+ t.references :order_transaction, index: { unique: true, name: :refund_transactions_orders_idx }
5
+ t.string :transaction_id, null: false, index: { unique: true }
6
+ t.datetime :transaction_date, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_foreign_key :solidus_taxjar_refund_transactions,
12
+ :solidus_taxjar_order_transactions,
13
+ column: :order_transaction_id
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ class AddTransactionDateToOrderTransaction < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :solidus_taxjar_order_transactions, :transaction_date, :datetime, null: false
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class CreateConfiguration < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :solidus_taxjar_configuration do |t|
4
+ t.text :preferences
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class CreateTransactionSyncBatches < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :solidus_taxjar_transaction_sync_batches do |t|
4
+ t.timestamps
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ class CreateTransactionSyncLogs < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :solidus_taxjar_transaction_sync_logs do |t|
4
+ t.references :transaction_sync_batch, foreign_key: {to_table: :solidus_taxjar_transaction_sync_batches}, index: {name: "index_transaction_sync_logs_on_transaction_sync_batch_id"}, null: false
5
+ t.references :order, foreign_key: {to_table: :spree_orders}, null: false
6
+
7
+ t.references :order_transaction, foreign_key: {to_table: :solidus_taxjar_order_transactions}, index: {name: "index_transaction_sync_logs_on_order_transaction_id"}
8
+ t.integer :status, null: false, default: 0
9
+ t.string :error_message
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ class AddDatesToTransactionSyncBatch < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :solidus_taxjar_transaction_sync_batches, :start_date, :date
4
+ add_column :solidus_taxjar_transaction_sync_batches, :end_date, :date
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AllowNullTransactionSyncBatchesOnLogs < ActiveRecord::Migration[6.1]
2
+ def change
3
+ change_column :solidus_taxjar_transaction_sync_logs, :transaction_sync_batch_id, :integer, null: true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddRefundTransactionToSyncLogs < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_reference :solidus_taxjar_transaction_sync_logs, :refund_transaction, foreign_key: {to_table: :solidus_taxjar_refund_transactions}, index: {name: "index_transaction_sync_logs_on_refund_transaction_id"}
4
+ end
5
+ end
@@ -0,0 +1,68 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+ class_option :auto_run_migrations, type: :boolean, default: false
6
+
7
+ def create_initializer_file
8
+ solidus_initializer_path = "config/initializers/solidus.rb"
9
+
10
+ create_file(solidus_initializer_path) unless File.exist?(solidus_initializer_path)
11
+ append_to_file(solidus_initializer_path, <<~INIT)
12
+ Spree.config do |config|
13
+ config.tax_calculator_class = SuperGood::SolidusTaxjar::TaxCalculator
14
+ end
15
+ INIT
16
+ end
17
+
18
+ def create_omes_initializer_file
19
+ return unless Gem::Requirement.new('>= 3.2.0.alpha')
20
+ .satisfied_by?(::Spree.solidus_gem_version)
21
+
22
+ omnes_initializer_path = "config/initializers/omnes.rb"
23
+
24
+ create_file(omnes_initializer_path) unless File.exist?(omnes_initializer_path)
25
+ append_to_file(omnes_initializer_path, <<~INIT)
26
+ Rails.application.config.to_prepare do
27
+ if SolidusSupport::LegacyEventCompat.using_legacy?
28
+ require 'super_good/solidus_taxjar/spree/legacy_reporting_subscriber.rb'
29
+ SuperGood::SolidusTaxjar::Spree::LegacyReportingSubscriber.omnes_subscriber.subscribe_to(Spree::Bus)
30
+ else
31
+ require 'super_good/solidus_taxjar/spree/reporting_subscriber.rb'
32
+ SuperGood::SolidusTaxjar::Spree::ReportingSubscriber.new.subscribe_to(Spree::Bus)
33
+ end
34
+ end
35
+ INIT
36
+ end
37
+
38
+ def create_legacy_events_initializer_file
39
+ return unless Gem::Requirement.new('< 3.2')
40
+ .satisfied_by?(::Spree.solidus_gem_version)
41
+
42
+ initializer_path = "config/initializers/solidus_taxjar_legacy_events.rb"
43
+
44
+ create_file(initializer_path) unless File.exist?(initializer_path)
45
+ append_to_file(initializer_path, <<~INIT)
46
+ Rails.application.config.to_prepare do
47
+ require 'super_good/solidus_taxjar/spree/legacy_reporting_subscriber.rb'
48
+ SuperGood::SolidusTaxjar::Spree::LegacyReportingSubscriber.activate
49
+ end
50
+ INIT
51
+ end
52
+
53
+ def add_migrations
54
+ run 'bin/rails railties:install:migrations FROM=super_good_solidus_taxjar'
55
+ end
56
+
57
+ def run_migrations
58
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
59
+ if run_migrations
60
+ run 'bin/rails db:migrate'
61
+ else
62
+ puts 'Skipping bin/rails db:migrate, don\'t forget to run it!'
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -4,5 +4,7 @@ module SuperGoodSolidusTaxjar
4
4
  class Engine < Rails::Engine
5
5
  isolate_namespace Spree
6
6
  engine_name 'super_good_solidus_taxjar'
7
+
8
+ include SolidusSupport::EngineExtensions
7
9
  end
8
10
  end
@@ -20,7 +20,7 @@ module SuperGood
20
20
 
21
21
  return if taxjar_address.nil?
22
22
 
23
- Spree::Address.immutable_merge(spree_address, {
23
+ ::Spree::Address.immutable_merge(spree_address, {
24
24
  country: us, # TaxJar only supports the US currently.
25
25
  state: state(taxjar_address.state),
26
26
  zipcode: taxjar_address.zip,
@@ -31,7 +31,7 @@ module SuperGood
31
31
 
32
32
  def possibilities(spree_address)
33
33
  taxjar_addresses(spree_address).map { |taxjar_address|
34
- Spree::Address.immutable_merge(spree_address, {
34
+ ::Spree::Address.immutable_merge(spree_address, {
35
35
  country: us, # TaxJar only supports the US currently.
36
36
  state: state(taxjar_address.state),
37
37
  zipcode: taxjar_address.zip,
@@ -52,7 +52,7 @@ module SuperGood
52
52
  end
53
53
 
54
54
  def us
55
- Spree::Country.find_by iso: "US"
55
+ ::Spree::Country.find_by iso: "US"
56
56
  end
57
57
 
58
58
  def state(abbr)
@@ -3,7 +3,7 @@ module SuperGood
3
3
  class Api
4
4
  def self.default_taxjar_client
5
5
  client = ::Taxjar::Client.new(
6
- api_key: ENV.fetch("TAXJAR_API_KEY"),
6
+ api_key: ENV["TAXJAR_API_KEY"],
7
7
  api_url: ENV.fetch("TAXJAR_API_URL") { "https://api.taxjar.com" } # Sandbox URL: https://api.sandbox.taxjar.com
8
8
  )
9
9
  client.set_api_config('headers', {
@@ -17,14 +17,12 @@ module SuperGood
17
17
  @taxjar_client = taxjar_client
18
18
  end
19
19
 
20
- def tax_for(order)
21
- taxjar_client.tax_for_order(ApiParams.order_params(order)).tap do |taxes|
22
- next unless SuperGood::SolidusTaxjar.logging_enabled
20
+ def tax_categories
21
+ taxjar_client.categories
22
+ end
23
23
 
24
- Rails.logger.info(
25
- "TaxJar response for #{order.number}: #{taxes.to_h.inspect}"
26
- )
27
- end
24
+ def tax_for(order)
25
+ taxjar_client.tax_for_order(ApiParams.order_params(order))
28
26
  end
29
27
 
30
28
  def tax_rate_for(address)
@@ -36,7 +34,17 @@ module SuperGood
36
34
  end
37
35
 
38
36
  def create_transaction_for(order)
39
- taxjar_client.create_order ApiParams.transaction_params(order)
37
+ latest_transaction_id =
38
+ OrderTransaction.latest_for(order)&.transaction_id
39
+
40
+ transaction_id = TransactionIdGenerator.next_transaction_id(
41
+ order: order,
42
+ current_transaction_id: latest_transaction_id
43
+ )
44
+
45
+ taxjar_client.create_order(
46
+ ApiParams.transaction_params(order, transaction_id)
47
+ )
40
48
  end
41
49
 
42
50
  def update_transaction_for(order)
@@ -47,6 +55,30 @@ module SuperGood
47
55
  taxjar_client.delete_order order.number
48
56
  end
49
57
 
58
+ def show_latest_transaction_for(order)
59
+ latest_transaction_id =
60
+ OrderTransaction.latest_for(order)&.transaction_id
61
+
62
+ return unless latest_transaction_id
63
+
64
+ taxjar_client.show_order(latest_transaction_id)
65
+ rescue Taxjar::Error::NotFound
66
+ nil
67
+ end
68
+
69
+ def create_refund_transaction_for(order)
70
+ unless OrderTransaction.latest_for(order)
71
+ raise NotImplementedError,
72
+ "No latest TaxJar order transaction for #{order.number}. " \
73
+ "Backfilling TaxJar transaction orders from Solidus is not yet " \
74
+ "implemented."
75
+ end
76
+
77
+ taxjar_order = show_latest_transaction_for(order)
78
+
79
+ taxjar_client.create_refund ApiParams.refund_transaction_params(order, taxjar_order)
80
+ end
81
+
50
82
  def create_refund_for(reimbursement)
51
83
  taxjar_client.create_refund ApiParams.refund_params(reimbursement)
52
84
  end
@@ -55,6 +87,10 @@ module SuperGood
55
87
  taxjar_client.validate_address ApiParams.validate_address_params(spree_address)
56
88
  end
57
89
 
90
+ def nexus_regions
91
+ taxjar_client.nexus_regions
92
+ end
93
+
58
94
  private
59
95
 
60
96
  attr_reader :taxjar_client
@@ -1,6 +1,8 @@
1
1
  module SuperGood
2
2
  module SolidusTaxjar
3
3
  module ApiParams
4
+ UNTAXABLE_INVENTORY_UNIT_STATES = ["returned", "canceled"]
5
+
4
6
  class << self
5
7
  def order_params(order)
6
8
  {}
@@ -9,13 +11,6 @@ module SuperGood
9
11
  .merge(line_items_params(order.line_items))
10
12
  .merge(shipping: shipping(order))
11
13
  .merge(SuperGood::SolidusTaxjar.custom_order_params.call(order))
12
- .tap do |params|
13
- next unless SuperGood::SolidusTaxjar.logging_enabled
14
-
15
- Rails.logger.info(
16
- "TaxJar params for #{order.number}: #{params.inspect}"
17
- )
18
- end
19
14
  end
20
15
 
21
16
  def address_params(address)
@@ -37,20 +32,44 @@ module SuperGood
37
32
  }.merge(order_address_params(address))
38
33
  end
39
34
 
40
- def transaction_params(order)
35
+ def transaction_params(order, transaction_id = order.number)
41
36
  {}
42
37
  .merge(customer_params(order))
43
38
  .merge(order_address_params(order.tax_address))
44
39
  .merge(transaction_line_items_params(order.line_items))
45
40
  .merge(
46
- transaction_id: order.number,
41
+ transaction_id: transaction_id,
47
42
  transaction_date: order.completed_at.to_formatted_s(:iso8601),
48
- amount: [order.total - order.additional_tax_total, 0].max,
43
+ # We use `payment_total` to reflect the total liablity
44
+ # transferred.
45
+ amount: [order.payments.completed.sum(&:amount) - refund_total_without_tax(order) - order.additional_tax_total, 0].max,
49
46
  shipping: shipping(order),
50
47
  sales_tax: sales_tax(order)
51
48
  )
52
49
  end
53
50
 
51
+ def refund_transaction_params(spree_order, taxjar_order)
52
+ {}
53
+ .merge(order_address_params(spree_order.tax_address))
54
+ .merge(
55
+ {
56
+ transaction_id: TransactionIdGenerator.refund_transaction_id(taxjar_order.transaction_id),
57
+ transaction_reference_id: taxjar_order.transaction_id,
58
+ transaction_date: spree_order.completed_at.to_formatted_s(:iso8601),
59
+ amount: -1 * taxjar_order.amount,
60
+ sales_tax: -1 * taxjar_order.sales_tax,
61
+ shipping: -1 * taxjar_order.shipping,
62
+ line_items: taxjar_order.line_items.map { |line_item|
63
+ line_item.to_h.merge({
64
+ unit_price: line_item.unit_price * -1,
65
+ discount: line_item.discount * -1,
66
+ sales_tax: line_item.sales_tax * -1
67
+ })
68
+ }
69
+ }
70
+ )
71
+ end
72
+
54
73
  def refund_params(reimbursement)
55
74
  additional_taxes = reimbursement.return_items.sum(&:additional_tax_total)
56
75
 
@@ -72,7 +91,7 @@ module SuperGood
72
91
  state: spree_address.state&.abbr || spree_address.state_name,
73
92
  zip: spree_address.zipcode,
74
93
  city: spree_address.city,
75
- street: spree_address.address1
94
+ street: [spree_address.address1, spree_address.address2].compact.join(' ')
76
95
  }
77
96
  end
78
97
 
@@ -94,9 +113,18 @@ module SuperGood
94
113
  }
95
114
  end
96
115
 
116
+ # @private
117
+ # This method builds line item parameters as expected by the TaxJar
118
+ # Tax API.
119
+ #
120
+ # @param line_items [Spree::LineItem::ActiveRecord_Relation] All of the
121
+ # order's line items.
122
+ # @return [Hash] A TaxJar API-friendly line item collection.
97
123
  def line_items_params(line_items)
98
124
  {
99
- line_items: valid_line_items(line_items).map do |line_item|
125
+ line_items: line_items.filter_map { |line_item|
126
+ next unless line_item.quantity.positive?
127
+
100
128
  {
101
129
  id: line_item.id,
102
130
  quantity: line_item.quantity,
@@ -104,34 +132,39 @@ module SuperGood
104
132
  discount: discount(line_item),
105
133
  product_tax_code: line_item.tax_category&.tax_code
106
134
  }
107
- end
135
+ }
108
136
  }
109
137
  end
110
138
 
139
+ # @private
140
+ # This method builds line item parameters as expected by the TaxJar
141
+ # Transactions API. Note that this logic different from
142
+ # `.line_item_params` as it excludes inventory units we consider to be
143
+ # untaxable (i.e. returned or cancelled inventory units).
144
+ #
145
+ # @param line_items [Spree::LineItem::ActiveRecord_Relation] All of the
146
+ # order's line items.
147
+ # @return [Hash] A TaxJar API-friendly line item collection.
111
148
  def transaction_line_items_params(line_items)
112
149
  {
113
- line_items: valid_line_items(line_items).map do |line_item|
150
+ line_items: line_items.filter_map { |line_item|
151
+ quantity = taxable_quantity line_item
152
+ next unless quantity.positive?
153
+
114
154
  {
115
155
  id: line_item.id,
116
- quantity: line_item.quantity,
156
+ quantity: quantity,
117
157
  product_identifier: line_item.sku,
158
+ description: line_item.variant.descriptive_name,
118
159
  product_tax_code: line_item.tax_category&.tax_code,
119
160
  unit_price: line_item.price,
120
161
  discount: discount(line_item),
121
162
  sales_tax: line_item_sales_tax(line_item)
122
163
  }
123
- end
164
+ }
124
165
  }
125
166
  end
126
167
 
127
- def valid_line_items(line_items)
128
- # The API appears to error when sent line items with no quantity...
129
- # but why would you do that anyway.
130
- line_items.reject do |line_item|
131
- line_item.quantity.zero?
132
- end
133
- end
134
-
135
168
  def discount(line_item)
136
169
  ::SuperGood::SolidusTaxjar.discount_calculator.new(line_item).discount
137
170
  end
@@ -143,13 +176,47 @@ module SuperGood
143
176
  def sales_tax(order)
144
177
  return 0 if order.total.zero?
145
178
 
146
- order.additional_tax_total
179
+ order.additional_tax_total - order_reimbursement_tax_total(order)
147
180
  end
148
181
 
149
182
  def line_item_sales_tax(line_item)
150
183
  return 0 if line_item.order.total.zero?
151
184
 
152
- line_item.additional_tax_total
185
+ line_item.additional_tax_total - line_item_reimbursement_tax_total(line_item)
186
+ end
187
+
188
+ def taxable_quantity(line_item)
189
+ line_item.inventory_units
190
+ .where.not(state: UNTAXABLE_INVENTORY_UNIT_STATES)
191
+ .count
192
+ end
193
+
194
+ def line_item_reimbursement_tax_total(line_item)
195
+ line_item
196
+ .inventory_units
197
+ .flat_map(&:return_items)
198
+ .filter { |return_item| return_item.reimbursement.present? }
199
+ .sum(&:additional_tax_total)
200
+ end
201
+
202
+ def order_reimbursement_tax_total(order)
203
+ order.reimbursements.sum { |reimbursement| reimbursement_tax_total(reimbursement) }
204
+ end
205
+
206
+ def reimbursement_tax_total(reimbursement)
207
+ reimbursement.return_items.sum(&:additional_tax_total)
208
+ end
209
+
210
+ def refund_total_without_tax(order)
211
+ order.refunds.sum do |refund|
212
+ if refund.reimbursement.present?
213
+ refund.reimbursement.total - reimbursement_tax_total(refund.reimbursement)
214
+ else
215
+ # This use case represents making a line item level adjustment, and then refunding
216
+ # that amount.
217
+ refund.amount
218
+ end
219
+ end
153
220
  end
154
221
  end
155
222
  end
@@ -0,0 +1,11 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ class BackfillTransactions
4
+ def call(start_date:, end_date:)
5
+ transaction_sync_batch = SuperGood::SolidusTaxjar::TransactionSyncBatch.create!(start_date: start_date, end_date: end_date)
6
+ SuperGood::SolidusTaxjar::BackfillTransactionSyncBatchJob.perform_later(transaction_sync_batch)
7
+ transaction_sync_batch
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ class CachedApi
4
+ # @param api [SuperGood::SolidusTaxjar::Api] instance of API wrapper
5
+ def initialize(api: SuperGood::SolidusTaxjar.api)
6
+ @api = api
7
+ end
8
+
9
+ # @param refresh [Boolean] forces a refresh of the cached regions
10
+ def nexus_regions(refresh: false)
11
+ Rails.cache.fetch(
12
+ :nexus_regions,
13
+ expires_in: SuperGood::SolidusTaxjar.cache_duration,
14
+ force: refresh
15
+ ) { api.nexus_regions }
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :api
21
+ end
22
+ end
23
+ end