super_good-solidus_taxjar 0.18.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +48 -6
  3. data/CHANGELOG.md +103 -1
  4. data/Gemfile +26 -10
  5. data/PULL_REQUEST_TEMPLATE.md +0 -1
  6. data/README.md +299 -39
  7. data/Rakefile +3 -0
  8. data/app/controllers/spree/admin/taxjar_settings_controller.rb +86 -1
  9. data/app/controllers/spree/admin/taxjar_transactions_controller.rb +37 -0
  10. data/app/controllers/spree/admin/transaction_sync_batches_controller.rb +18 -0
  11. data/app/jobs/super_good/solidus_taxjar/backfill_transaction_sync_batch_job.rb +23 -0
  12. data/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb +26 -0
  13. data/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb +22 -0
  14. data/app/models/super_good/solidus_taxjar/configuration.rb +27 -0
  15. data/app/models/super_good/solidus_taxjar/order_transaction.rb +16 -0
  16. data/app/models/super_good/solidus_taxjar/refund_transaction.rb +13 -0
  17. data/app/models/super_good/solidus_taxjar/transaction_sync_batch.rb +11 -0
  18. data/app/models/super_good/solidus_taxjar/transaction_sync_log.rb +8 -0
  19. data/app/overrides/spree/admin/orders_controller_override.rb +12 -0
  20. data/app/overrides/spree/admin/shared/_order_submenu/add_taxjar_sync_history_tab.html.erb.deface +6 -0
  21. data/app/overrides/spree/admin/shared/_order_summary/add_taxjar_reported_at.html.erb.deface +30 -0
  22. data/app/overrides/spree/admin/shared/_taxes_tabs/add_configuration_menu_items.html.erb.deface +5 -0
  23. data/app/overrides/super_good/solidus_taxjar/spree/order_override.rb +21 -0
  24. data/app/views/spree/admin/orders/taxjar_transactions.html.erb +4 -0
  25. data/app/views/spree/admin/shared/_transaction_sync_log_table.html.erb +35 -0
  26. data/app/views/spree/admin/taxjar_settings/_nexus_regions.html.erb +23 -0
  27. data/app/views/spree/admin/taxjar_settings/_tax_categories.html.erb +41 -0
  28. data/app/views/spree/admin/taxjar_settings/edit.html.erb +17 -0
  29. data/app/views/spree/admin/taxjar_settings/edit_no_api_key.html.erb +21 -0
  30. data/app/views/spree/admin/transaction_sync_batches/index.html.erb +50 -0
  31. data/app/views/spree/admin/transaction_sync_batches/show.html.erb +7 -0
  32. data/bin/console +2 -0
  33. data/bin/rails-engine +1 -1
  34. data/bin/sandbox +43 -36
  35. data/bin/setup +3 -3
  36. data/config/routes.rb +13 -1
  37. data/db/migrate/20210908205201_create_taxjar_order_transactions.rb +16 -0
  38. data/db/migrate/20211008175113_create_taxjar_refund_transaction.rb +15 -0
  39. data/db/migrate/20211008183858_add_transaction_date_to_order_transaction.rb +5 -0
  40. data/db/migrate/20211119143354_create_configuration.rb +8 -0
  41. data/db/migrate/20220405213958_create_transaction_sync_batches.rb +7 -0
  42. data/db/migrate/20220405215225_create_transaction_sync_logs.rb +14 -0
  43. data/db/migrate/20220908181655_add_dates_to_transaction_sync_batch.rb +6 -0
  44. data/db/migrate/20220912182210_allow_null_transaction_sync_batches_on_logs.rb +5 -0
  45. data/db/migrate/20230320211309_add_refund_transaction_to_sync_logs.rb +5 -0
  46. data/lib/generators/super_good/solidus_taxjar/install/install_generator.rb +68 -0
  47. data/lib/super_good/solidus_taxjar/api.rb +41 -9
  48. data/lib/super_good/solidus_taxjar/api_params.rb +92 -25
  49. data/lib/super_good/solidus_taxjar/backfill_transactions.rb +11 -0
  50. data/lib/super_good/solidus_taxjar/cached_api.rb +23 -0
  51. data/lib/super_good/solidus_taxjar/calculator_helper.rb +33 -4
  52. data/lib/super_good/solidus_taxjar/discount_calculator.rb +1 -1
  53. data/lib/super_good/solidus_taxjar/overrides/request_override.rb +15 -0
  54. data/lib/super_good/solidus_taxjar/reportable.rb +91 -0
  55. data/lib/super_good/solidus_taxjar/reporting.rb +44 -0
  56. data/lib/super_good/solidus_taxjar/spree/legacy_reporting_subscriber.rb +41 -0
  57. data/lib/super_good/solidus_taxjar/spree/reporting_subscriber.rb +40 -0
  58. data/lib/super_good/solidus_taxjar/tax_calculator.rb +7 -1
  59. data/lib/super_good/solidus_taxjar/testing_support/factories/address_factory.rb +11 -0
  60. data/lib/super_good/solidus_taxjar/testing_support/factories/configuration_factory.rb +11 -0
  61. data/lib/super_good/solidus_taxjar/testing_support/factories/order_transaction_factory.rb +22 -0
  62. data/lib/super_good/solidus_taxjar/testing_support/factories/refund_transaction_factory.rb +7 -0
  63. data/lib/super_good/solidus_taxjar/testing_support/factories/transaction_sync_batch_factory.rb +9 -0
  64. data/lib/super_good/solidus_taxjar/testing_support/factories/transaction_sync_log_factory.rb +18 -0
  65. data/lib/super_good/solidus_taxjar/transaction_id_generator.rb +45 -0
  66. data/lib/super_good/solidus_taxjar/version.rb +1 -1
  67. data/lib/super_good/solidus_taxjar.rb +29 -2
  68. data/spec/features/spree/admin/backfill_transactions_spec.rb +138 -0
  69. data/spec/features/spree/admin/refund_spec.rb +167 -0
  70. data/spec/features/spree/admin/reporting_to_taxjar_spec.rb +156 -0
  71. data/spec/features/spree/admin/taxjar_settings_spec.rb +58 -16
  72. data/spec/features/spree/checkout_spec.rb +58 -0
  73. 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
  74. 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
  75. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_API_token_is_set/shows_the_settings_page.yml +2437 -0
  76. 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
  77. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_API_token_isn_t_set/shows_a_descriptive_error_message.yml +57 -0
  78. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/Taxjar_reporting_is_enabled/shows_that_reporting_is_enabled.yml +2382 -0
  79. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/order_is_shipped/the_user_backfills_their_transactions.yml +2511 -0
  80. data/spec/fixtures/cassettes/Admin_TaxJar_Settings/Taxjar_settings_tab/the_user_navigates_to_the_TaxJar_Settings.yml +2382 -0
  81. data/spec/fixtures/cassettes/Admin_Transaction_Sync_Batches/user_has_a_shipped_order/starts_a_transaction_backfill.yml +370 -0
  82. data/spec/fixtures/cassettes/Reporting_orders_to_TaxJar/shipping_a_complete_and_paid_order.yml +310 -0
  83. 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
  84. data/spec/fixtures/cassettes/Reporting_orders_to_TaxJar/with_an_order_with_invalid_zipcode/retry_of_a_previously_failed_transaction_sync.yml +418 -0
  85. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/creates_a_batch.yml +250 -0
  86. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/creates_a_log_in_the_batch_with_an_order.yml +250 -0
  87. data/spec/fixtures/cassettes/Spree_Admin_TransactionSyncBatchesController/_create/user_supplies_a_start_date/creates_a_batch.yml +250 -0
  88. 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
  89. 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
  90. 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
  91. 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
  92. data/spec/fixtures/cassettes/SuperGood_SolidusTaxjar_Reporting/_refund_and_create_transaction/when_Taxjar_cannot_create_a_refund_transaction/raises_an_error.yml +393 -0
  93. data/spec/fixtures/cassettes/Taxjar_API_Request/logging_is_disabled/doesn_t_call_the_logger.yml +158 -0
  94. data/spec/fixtures/cassettes/Taxjar_API_Request/logging_is_enabled/calls_the_logger.yml +158 -0
  95. data/spec/fixtures/cassettes/features/spree/admin/checkout.yml +238 -0
  96. data/spec/fixtures/cassettes/features/spree/admin/refund.yml +1162 -0
  97. data/spec/jobs/super_good/solidus_taxjar/backfill_transaction_sync_batch_job_spec.rb +117 -0
  98. data/spec/jobs/super_good/solidus_taxjar/replace_transaction_job_spec.rb +95 -0
  99. data/spec/jobs/super_good/solidus_taxjar/report_transaction_job_spec.rb +76 -0
  100. data/spec/models/super_good/solidus_taxjar/configuration_spec.rb +79 -0
  101. data/spec/models/super_good/solidus_taxjar/order_transaction_spec.rb +36 -0
  102. data/spec/models/super_good/solidus_taxjar/transaction_sync_batch_spec.rb +48 -0
  103. data/spec/requests/spree/admin/order_request_spec.rb +121 -0
  104. data/spec/requests/spree/admin/taxjar_settings_request_spec.rb +198 -0
  105. data/spec/requests/spree/admin/taxjar_transactions_request_spec.rb +62 -0
  106. data/spec/requests/spree/admin/transaction_sync_batches_request_spec.rb +82 -0
  107. data/spec/spec_helper.rb +46 -3
  108. data/spec/subscribers/super_good/solidus_taxjar/spree/reporting_subscriber_spec.rb +278 -0
  109. data/spec/super_good/solidus_taxjar/addresses_spec.rb +0 -14
  110. data/spec/super_good/solidus_taxjar/api_params_spec.rb +261 -89
  111. data/spec/super_good/solidus_taxjar/api_spec.rb +152 -29
  112. data/spec/super_good/solidus_taxjar/backfill_transactions_spec.rb +24 -0
  113. data/spec/super_good/solidus_taxjar/cached_api_spec.rb +58 -0
  114. data/spec/super_good/solidus_taxjar/calculator_helper_spec.rb +131 -0
  115. data/spec/super_good/solidus_taxjar/discount_calculator_spec.rb +19 -2
  116. data/spec/super_good/solidus_taxjar/reportable_spec.rb +194 -0
  117. data/spec/super_good/solidus_taxjar/reporting_spec.rb +243 -0
  118. data/spec/super_good/solidus_taxjar/tax_calculator_spec.rb +19 -19
  119. data/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb +8 -3
  120. data/spec/super_good/solidus_taxjar/transaction_id_generator_spec.rb +77 -0
  121. data/spec/super_good/solidus_taxjar_spec.rb +84 -0
  122. data/spec/support/checkoutable_store_shared_context.rb +19 -0
  123. data/spec/support/solidus_events_helper.rb +26 -0
  124. data/spec/taxjar/api/request_spec.rb +52 -0
  125. data/super_good-solidus_taxjar.gemspec +3 -2
  126. metadata +169 -17
  127. data/app/decorators/super_good/solidus_taxjar/spree/order_updater/fire_recalculated_event.rb +0 -18
  128. data/app/overrides/spree/admin/shared/_configuration_menu.rb +0 -11
  129. data/app/views/spree/admin/taxjar_settings/show.html.erb +0 -13
  130. data/spec/models/spree/order_updater_spec.rb +0 -12
@@ -6,7 +6,7 @@ module SuperGood
6
6
  end
7
7
 
8
8
  def discount
9
- -line_item.promo_total
9
+ -1 * line_item.adjustments.select { |value| !value.tax? && value.eligible? }.sum(&:amount)
10
10
  end
11
11
 
12
12
  private
@@ -0,0 +1,15 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ module RequestOverride
4
+ def build_http_client
5
+ if SuperGood::SolidusTaxjar.logging_enabled
6
+ super.use(logging: {logger: SuperGood::SolidusTaxjar.logger})
7
+ else
8
+ super
9
+ end
10
+ end
11
+
12
+ Taxjar::API::Request.prepend(self)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,91 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ module Reportable
4
+ ORDER_TOO_OLD_MESSAGE = "Order cannot be synced because it was completed before TaxJar reporting was enabled"
5
+
6
+ def self.included(base)
7
+ # Omnes subscribers are classes, whereas the legacy event system uses
8
+ # modules for subscribers. The type check ensures this is forwards
9
+ # compatible and can be removed when we drop support for the legacy
10
+ # event system.
11
+ base.extend(base) unless base.is_a?(Class)
12
+ end
13
+
14
+ def with_reportable(order, &block)
15
+ raise "Please provide a block!" unless block_given?
16
+
17
+ return unless order_reportable?(order)
18
+ return if completed_before_reporting_enabled?(order)
19
+ return unless order.taxjar_order_transactions.none?
20
+
21
+ yield
22
+ end
23
+
24
+ def with_replaceable(order, &block)
25
+ return unless order_reportable?(order)
26
+ return if completed_before_reporting_enabled?(order)
27
+ return unless transaction_replaceable?(order)
28
+
29
+ yield
30
+ end
31
+
32
+ # @return [Boolean] true if the TaxJar reporting is currently enabled
33
+ # and the order meets all the other requirements for reporting.
34
+ def order_reportable?(order)
35
+ return SuperGood::SolidusTaxjar.configuration.preferred_reporting_enabled &&
36
+ order.completed? &&
37
+ order.shipped? &&
38
+ order.payment_state == "paid"
39
+ end
40
+
41
+ # @return [Boolean] true if the transaction has been previously reported
42
+ # to TaxJar, the order is currently in `paid` state and there is a
43
+ # difference between the total (before tax) on the order in Solidus
44
+ # and the transaction amount on TaxJar.
45
+ def transaction_replaceable?(order)
46
+ order.taxjar_order_transactions.present? &&
47
+ amount_changed?(order)
48
+ end
49
+
50
+ private
51
+
52
+ def completed_before_reporting_enabled?(order)
53
+ configuration = SuperGood::SolidusTaxjar.configuration
54
+
55
+ completed_before_reporting_enabled = configuration.preferred_reporting_enabled &&
56
+ configuration.preferred_reporting_enabled_at > order.completed_at
57
+
58
+ if completed_before_reporting_enabled
59
+ log_order_too_old_to_sync(order)
60
+ end
61
+
62
+ completed_before_reporting_enabled
63
+ end
64
+
65
+ # Logs and notifies the custom exception handler for orders which are
66
+ # too old to be reported. If a log for this already exists a new one
67
+ # won't be created and the exception handler won't be called.
68
+ def log_order_too_old_to_sync(order)
69
+ sync_log = order.taxjar_transaction_sync_logs.find_or_initialize_by(
70
+ status: :error,
71
+ error_message: ORDER_TOO_OLD_MESSAGE
72
+ )
73
+
74
+ unless sync_log.persisted?
75
+ sync_log.save!
76
+
77
+ SuperGood::SolidusTaxjar.exception_handler.call(
78
+ RuntimeError.new(ORDER_TOO_OLD_MESSAGE)
79
+ )
80
+ end
81
+ end
82
+
83
+ def amount_changed?(order)
84
+ # We use `order.payment_total` to ensure we capture any total changes
85
+ # from refunds.
86
+ SuperGood::SolidusTaxjar.api.show_latest_transaction_for(order).amount !=
87
+ (order.payment_total - order.additional_tax_total)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,44 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ class Reporting
4
+ def initialize(api: SuperGood::SolidusTaxjar.api)
5
+ @api = api
6
+ end
7
+
8
+ def refund_and_create_new_transaction(order)
9
+ latest_order_transaction = OrderTransaction.latest_for(order)
10
+
11
+ unless latest_order_transaction.refund_transaction
12
+ transaction_response = @api.create_refund_transaction_for(order)
13
+ latest_order_transaction.create_refund_transaction!(
14
+ transaction_id: transaction_response.transaction_id,
15
+ transaction_date: transaction_response.transaction_date
16
+ )
17
+ end
18
+
19
+ return if order.total.zero?
20
+
21
+ if transaction_response = @api.create_transaction_for(order)
22
+ order.taxjar_order_transactions.create!(
23
+ transaction_id: transaction_response.transaction_id,
24
+ transaction_date: transaction_response.transaction_date
25
+ )
26
+ end
27
+ end
28
+
29
+ def show_or_create_transaction(order)
30
+ if transaction_response = @api.show_latest_transaction_for(order)
31
+ SuperGood::SolidusTaxjar::OrderTransaction.find_by!(
32
+ transaction_id: transaction_response.transaction_id
33
+ )
34
+ else
35
+ transaction_response = @api.create_transaction_for(order)
36
+ order.taxjar_order_transactions.create!(
37
+ transaction_id: transaction_response.transaction_id,
38
+ transaction_date: transaction_response.transaction_date
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ module Spree
4
+ module LegacyReportingSubscriber
5
+ include ::Spree::Event::Subscriber
6
+ include SolidusSupport::LegacyEventCompat::Subscriber
7
+ include SuperGood::SolidusTaxjar::Reportable
8
+
9
+ # FIXME:
10
+ # This is a workaround until we add a new Solidus event we can subscribe
11
+ # to. "order_recalculated" occurs too early.
12
+ #
13
+ # This delay helps us be sure that `Spree::OrderUpdater#persist_totals`
14
+ # has been called, and the `order` transaction has been completed,
15
+ # before we report this transaction to TaxJar.
16
+ #
17
+ DELAY = 2
18
+
19
+ event_action :report_or_replace_transaction, event_name: :order_recalculated
20
+
21
+ def report_or_replace_transaction(event)
22
+ order = event.payload[:order]
23
+
24
+ with_reportable(order) do
25
+ SuperGood::SolidusTaxjar::ReportTransactionJob
26
+ .set(wait: DELAY.seconds)
27
+ .perform_later(order)
28
+
29
+ return
30
+ end
31
+
32
+ with_replaceable(order) do
33
+ SuperGood::SolidusTaxjar::ReplaceTransactionJob
34
+ .set(wait: DELAY.seconds)
35
+ .perform_later(order)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ module Spree
4
+ class ReportingSubscriber
5
+ include Omnes::Subscriber
6
+ include SuperGood::SolidusTaxjar::Reportable
7
+
8
+ # FIXME:
9
+ # This is a workaround until we add a new Solidus event we can subscribe
10
+ # to. "order_recalculated" occurs too early.
11
+ #
12
+ # This delay helps us be sure that `Spree::OrderUpdater#persist_totals`
13
+ # has been called, and the `order` transaction has been completed,
14
+ # before we report this transaction to TaxJar.
15
+ #
16
+ DELAY = 2
17
+
18
+ handle :order_recalculated, with: :report_or_replace_transaction
19
+
20
+ def report_or_replace_transaction(event)
21
+ order = event.payload[:order]
22
+
23
+ with_reportable(order) do
24
+ SuperGood::SolidusTaxjar::ReportTransactionJob
25
+ .set(wait: DELAY.seconds)
26
+ .perform_later(order)
27
+
28
+ return
29
+ end
30
+
31
+ with_replaceable(order) do
32
+ SuperGood::SolidusTaxjar::ReplaceTransactionJob
33
+ .set(wait: DELAY.seconds)
34
+ .perform_later(order)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -109,8 +109,14 @@ module SuperGood
109
109
  )
110
110
  end
111
111
 
112
+ # Tax adjustments require an associated Tax Rate. This tax rate is not
113
+ # used in the extension's calculations as the rates from the TaxJar API
114
+ # are used, so a placeholder rate and calculator is created.
112
115
  def tax_rate
113
- ::Spree::TaxRate.find_by(name: "Sales Tax")
116
+ ::Spree::TaxRate.find_or_create_by!(name: "Solidus TaxJar Rate") do |tax_rate|
117
+ tax_rate.calculator = ::Spree::Calculator.new
118
+ tax_rate.amount = 0
119
+ end
114
120
  end
115
121
 
116
122
  def cache_key
@@ -0,0 +1,11 @@
1
+ FactoryBot.modify do
2
+ # Solidus's default address factories provide an invalid state and zipcode
3
+ # combination.
4
+ factory :address do
5
+ zipcode { "11430" }
6
+
7
+ transient do
8
+ state_code { "NY" }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ FactoryBot.define do
2
+ factory :taxjar_configuration, class: "SuperGood::SolidusTaxjar::Configuration" do
3
+ trait :reporting_enabled do
4
+ preferred_reporting_enabled_at_integer { DateTime.now.to_i }
5
+ end
6
+
7
+ trait :reporting_disabled do
8
+ preferred_reporting_enabled_at_integer { nil }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ FactoryBot.define do
2
+ factory :taxjar_order_transaction, class: "SuperGood::SolidusTaxjar::OrderTransaction" do
3
+ order
4
+ transaction_date { Date.current }
5
+
6
+ transient do
7
+ last_transaction_id {
8
+ SuperGood::SolidusTaxjar::OrderTransaction
9
+ .latest_for(order)
10
+ &.transaction_id
11
+ }
12
+ end
13
+
14
+ transaction_id {
15
+ SuperGood::SolidusTaxjar::TransactionIdGenerator
16
+ .next_transaction_id(
17
+ order: order,
18
+ current_transaction_id: last_transaction_id
19
+ )
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ FactoryBot.define do
2
+ factory :taxjar_refund_transaction, class: "SuperGood::SolidusTaxjar::RefundTransaction" do
3
+ order_transaction { create :taxjar_order_transaction }
4
+ sequence(:transaction_id) { |n| "RefundTransaction-#{n}" }
5
+ transaction_date { Date.current }
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ FactoryBot.define do
2
+ factory :transaction_sync_batch, class: "SuperGood::SolidusTaxjar::TransactionSyncBatch" do
3
+ trait :with_logs do
4
+ after :build do |batch|
5
+ batch.transaction_sync_logs << build(:transaction_sync_log)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ FactoryBot.define do
2
+ factory :transaction_sync_log, class: "SuperGood::SolidusTaxjar::TransactionSyncLog" do
3
+ association :order, strategy: :create
4
+
5
+ trait :success do
6
+ status { "success" }
7
+
8
+ after :build do |log|
9
+ log.order_transaction = build(:taxjar_order_transaction, order: log.order)
10
+ end
11
+ end
12
+
13
+ trait :error do
14
+ status { "error" }
15
+ error_message { "Sync failed" }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ module SuperGood
2
+ module SolidusTaxjar
3
+ # Responsible for generating `transaction_id` references for transactions
4
+ # we create on TaxJar for Solidus orders. This class handles creating
5
+ # associated ID's for transactions which need to be cancelled and recreated
6
+ # when the order is updated in Solidus after it has been sent to the TaxJar
7
+ # reporting API.
8
+ class TransactionIdGenerator
9
+ class << self
10
+ # Generates the next sequential `transaction_id` given an order and
11
+ # optionally the current transaction ID on TaxJar. This handles the
12
+ # case where a transaction already has been created on TaxJar and later
13
+ # needs to be cancelled and we need to create an updated transaction
14
+ # with an associated identifier.
15
+ #
16
+ # @param order [Spree::Order] the order for which we want to generate a
17
+ # transaction ID.
18
+ # @param current_transaction_id [String] the current transaction ID for
19
+ # the order if it exists on TaxJar.
20
+ # @return [String] the next sequential `transaction_id`
21
+ def next_transaction_id(order:, current_transaction_id: nil)
22
+ if current_transaction_id.nil?
23
+ "#{order.number}"
24
+ elsif order.number == current_transaction_id
25
+ "#{current_transaction_id}-1"
26
+ else
27
+ parts = current_transaction_id.rpartition("-")
28
+ parts.last.next!
29
+ parts.join
30
+ end
31
+ end
32
+
33
+ # Generates a `transaction_id` for a refund transaction based on the
34
+ # ID of the transaction we're refunding.
35
+ #
36
+ # @param transaction_id [String] the ID of the transaction we are
37
+ # refunding.
38
+ # @return [String] the expected refund transaction ID.
39
+ def refund_transaction_id(transaction_id)
40
+ "#{transaction_id}-REFUND"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  module SuperGood
2
2
  module SolidusTaxjar
3
- VERSION = "0.18.2"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -1,15 +1,22 @@
1
1
  require "solidus_core"
2
2
  require "solidus_support"
3
+ require "deface"
3
4
  require "taxjar"
5
+ require "super_good/solidus_taxjar/overrides/request_override"
4
6
 
5
7
  require "super_good/solidus_taxjar/version"
8
+ require "super_good/solidus_taxjar/transaction_id_generator"
6
9
  require "super_good/solidus_taxjar/api_params"
7
10
  require "super_good/solidus_taxjar/api"
11
+ require "super_good/solidus_taxjar/cached_api"
8
12
  require "super_good/solidus_taxjar/calculator_helper"
9
13
  require "super_good/solidus_taxjar/tax_calculator"
10
14
  require "super_good/solidus_taxjar/tax_rate_calculator"
11
15
  require "super_good/solidus_taxjar/discount_calculator"
12
16
  require "super_good/solidus_taxjar/addresses"
17
+ require "super_good/solidus_taxjar/reporting"
18
+ require "super_good/solidus_taxjar/reportable"
19
+ require "super_good/solidus_taxjar/backfill_transactions"
13
20
 
14
21
  module SuperGood
15
22
  module SolidusTaxjar
@@ -19,6 +26,7 @@ module SuperGood
19
26
  attr_accessor :custom_order_params
20
27
  attr_accessor :discount_calculator
21
28
  attr_accessor :exception_handler
29
+ attr_accessor :job_queue
22
30
  attr_accessor :line_item_tax_label_maker
23
31
  attr_accessor :logging_enabled
24
32
  attr_accessor :shipping_calculator
@@ -26,10 +34,27 @@ module SuperGood
26
34
  attr_accessor :taxable_address_check
27
35
  attr_accessor :taxable_order_check
28
36
  attr_accessor :test_mode
37
+ attr_writer :logger
38
+
39
+ def configuration
40
+ ::SuperGood::SolidusTaxjar::Configuration.default
41
+ end
29
42
 
30
43
  def api
31
44
  ::SuperGood::SolidusTaxjar::Api.new
32
45
  end
46
+
47
+ def table_name_prefix
48
+ "solidus_taxjar_"
49
+ end
50
+
51
+ def reporting
52
+ ::SuperGood::SolidusTaxjar::Reporting.new
53
+ end
54
+
55
+ def logger
56
+ @logger || Rails.logger
57
+ end
33
58
  end
34
59
 
35
60
  self.cache_duration = 3.hours
@@ -40,10 +65,12 @@ module SuperGood
40
65
  self.custom_order_params = ->(order) { {} }
41
66
  self.discount_calculator = ::SuperGood::SolidusTaxjar::DiscountCalculator
42
67
  self.exception_handler = ->(e) {
43
- Rails.logger.error "An error occurred while fetching TaxJar tax rates - #{e}: #{e.message}"
68
+ self.logger.error "An error occurred while fetching TaxJar tax rates - #{e}: #{e.message}"
44
69
  }
70
+ self.job_queue = :default
45
71
  self.line_item_tax_label_maker = ->(taxjar_line_item, spree_line_item) { "Sales Tax" }
46
- self.logging_enabled = false
72
+ self.logging_enabled = true
73
+
47
74
  self.shipping_calculator = ->(order) { order.shipments.sum(&:total_before_tax) }
48
75
  self.shipping_tax_label_maker = ->(shipment, shipping_tax) { "Sales Tax" }
49
76
  self.taxable_address_check = ->(address) { true }
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.feature 'Admin Transaction Sync Batches', js: true, vcr: true do
4
+ stub_authorization!
5
+
6
+ background do
7
+ create :store, default: true
8
+ end
9
+
10
+ feature "user has a shipped order" do
11
+ let!(:order) { create :shipped_order }
12
+ let!(:excluded_order) { create :shipped_order }
13
+
14
+ before do
15
+ order.update_column(:completed_at, 1.day.ago)
16
+ excluded_order.update_column(:completed_at, 4.days.ago)
17
+ end
18
+
19
+ scenario "starts a transaction backfill" do
20
+ visit '/admin'
21
+ click_on "Settings"
22
+ expect(page).to have_content("Taxes")
23
+ click_on "Taxes"
24
+ expect(page).to have_content("TaxJar Backfill")
25
+ click_on "TaxJar Backfill"
26
+ fill_in "Start date", with: 2.days.ago.to_date
27
+ fill_in "End date", with: Date.today
28
+ perform_enqueued_jobs do
29
+ click_on "Backfill Transactions"
30
+ end
31
+ expect(page).to have_content /Transaction Sync Batch \d/
32
+ within ".content-wrapper table" do
33
+ expect(page).to_not have_content excluded_order.number
34
+ expect(page).to_not have_content excluded_order.number
35
+ expect(page).to have_content order.number
36
+ within "tbody td:nth-child(5)" do
37
+ expect(page).to have_content("Success")
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ feature "user has started a transaction backfill" do
44
+ let!(:transaction_sync_batch) { create :transaction_sync_batch }
45
+ let!(:second_transaction_sync_batch) { create :transaction_sync_batch, :with_logs }
46
+ let!(:processing_transaction_sync_log) { create :transaction_sync_log, transaction_sync_batch: transaction_sync_batch }
47
+ let!(:success_transaction_sync_log) { create :transaction_sync_log, :success, transaction_sync_batch: transaction_sync_batch }
48
+
49
+ scenario "transaction backfill index" do
50
+
51
+ visit spree.admin_transaction_sync_batches_path(per_page: 1)
52
+
53
+ within "#transaction_sync_batches" do
54
+ expect(page).to have_content(transaction_sync_batch.id)
55
+ expect(page).to have_content(transaction_sync_batch.created_at)
56
+ expect(page).to have_content(transaction_sync_batch.updated_at)
57
+
58
+ within "tbody td:nth-child(4)" do
59
+ expect(page).to have_content("1/2")
60
+ end
61
+
62
+ within "tbody td:nth-child(5)" do
63
+ expect(page).to have_content("Processing")
64
+ end
65
+ end
66
+
67
+ within ".pagination" do
68
+ click_on "Next"
69
+ end
70
+
71
+ within "#transaction_sync_batches" do
72
+ expect(page).to have_content(second_transaction_sync_batch.id)
73
+ expect(page).to have_content(second_transaction_sync_batch.created_at)
74
+ expect(page).to have_content(second_transaction_sync_batch.updated_at)
75
+
76
+ within "tbody td:nth-child(4)" do
77
+ expect(page).to have_content("0/1")
78
+ end
79
+
80
+ within "tbody td:nth-child(5)" do
81
+ expect(page).to have_content("Processing")
82
+ end
83
+ end
84
+
85
+ within ".pagination" do
86
+ click_on "Prev"
87
+ end
88
+
89
+ within ".actions" do
90
+ find(".fa-edit").click
91
+ end
92
+
93
+ within ".content-wrapper table" do
94
+ within "tbody tr:first-child" do
95
+ within "td:first-child" do
96
+ expect(page).to have_content(processing_transaction_sync_log.id)
97
+ end
98
+
99
+ within "td:nth-child(2)" do
100
+ expect(page).to have_content(processing_transaction_sync_log.order.number)
101
+ end
102
+
103
+ within "td:nth-child(3)" do
104
+ expect(page).to have_content("-")
105
+ end
106
+
107
+ within "td:nth-child(5)" do
108
+ expect(page).to have_content("Processing")
109
+ end
110
+
111
+ expect(page).to have_content(processing_transaction_sync_log.created_at)
112
+ expect(page).to have_content(processing_transaction_sync_log.updated_at)
113
+ end
114
+
115
+ within "tbody tr:nth-child(2)" do
116
+ within "td:first-child" do
117
+ expect(page).to have_content(success_transaction_sync_log.id)
118
+ end
119
+
120
+ within "td:nth-child(2)" do
121
+ expect(page).to have_content(success_transaction_sync_log.order.number)
122
+ end
123
+
124
+ within "td:nth-child(3)" do
125
+ expect(page).to have_content(success_transaction_sync_log.order_transaction.transaction_id)
126
+ end
127
+
128
+ within "td:nth-child(5)" do
129
+ expect(page).to have_content("Success")
130
+ end
131
+
132
+ expect(page).to have_content(success_transaction_sync_log.created_at)
133
+ expect(page).to have_content(success_transaction_sync_log.updated_at)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end