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
@@ -4,19 +4,25 @@ module SuperGood
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def incomplete_address?(address)
7
- return true if address.is_a?(Spree::Tax::TaxLocation)
7
+ return true if address.is_a?(::Spree::Tax::TaxLocation)
8
8
 
9
- [
9
+ fields = [
10
10
  address.address1,
11
11
  address.city,
12
- address.state&.abbr || address.state_name,
13
12
  address.zipcode,
14
13
  address.country&.iso
15
- ].any?(&:blank?)
14
+ ]
15
+
16
+ if state_required?(address.country)
17
+ fields << (address.state&.abbr || address.state_name)
18
+ end
19
+
20
+ fields.any?(&:blank?)
16
21
  end
17
22
 
18
23
  def taxable_address?(address)
19
- SuperGood::SolidusTaxjar.taxable_address_check.call(address)
24
+ SuperGood::SolidusTaxjar.taxable_address_check.call(address) &&
25
+ address_in_nexus_region?(address)
20
26
  end
21
27
 
22
28
  def cache
@@ -33,6 +39,29 @@ module SuperGood
33
39
  def exception_handler
34
40
  SuperGood::SolidusTaxjar.exception_handler
35
41
  end
42
+
43
+ # Only require a "state" value if this is an address for Canada or the
44
+ # USA. This aligns with TaxJar's API requirement for `to_state`.
45
+ #
46
+ # @param country [Spree::Country] The country to check.
47
+ # @return [Boolean] True if the "state" field is required for the country
48
+ def state_required?(country)
49
+ ["CA", "US"].include?(country&.iso)
50
+ end
51
+
52
+ private
53
+
54
+ # Nexus regions are only a concept for US states. For non-US addresses
55
+ # we always want to defer to the configuration in TaxJar.
56
+ def address_in_nexus_region?(address)
57
+ return true unless address&.country&.iso == "US"
58
+
59
+ ::SuperGood::SolidusTaxjar::CachedApi
60
+ .new
61
+ .nexus_regions
62
+ .map(&:region_code)
63
+ .include?(address.state.abbr)
64
+ end
36
65
  end
37
66
  end
38
67
  end
@@ -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
@@ -17,7 +17,7 @@ module SuperGood
17
17
  cache do
18
18
  next no_tax unless taxjar_breakdown
19
19
 
20
- Spree::Tax::OrderTax.new(
20
+ ::Spree::Tax::OrderTax.new(
21
21
  order_id: order.id,
22
22
  line_item_taxes: line_item_taxes,
23
23
  shipment_taxes: shipment_taxes
@@ -41,7 +41,7 @@ module SuperGood
41
41
  # orders aren't going to have a huge number of line items.
42
42
  spree_line_item = order.line_items.find { |li| li.id == spree_line_item_id }
43
43
 
44
- Spree::Tax::ItemTax.new(
44
+ ::Spree::Tax::ItemTax.new(
45
45
  item_id: spree_line_item_id,
46
46
  label: line_item_tax_label(taxjar_line_item, spree_line_item),
47
47
  tax_rate: tax_rate,
@@ -102,15 +102,21 @@ module SuperGood
102
102
  end
103
103
 
104
104
  def no_tax
105
- Spree::Tax::OrderTax.new(
105
+ ::Spree::Tax::OrderTax.new(
106
106
  order_id: order.id,
107
107
  line_item_taxes: [],
108
108
  shipment_taxes: []
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.1"
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,11 +65,13 @@ 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
47
- self.shipping_calculator = ->(order) { order.shipment_total }
72
+ self.logging_enabled = true
73
+
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 }
50
77
  self.taxable_order_check = ->(order) { true }