spree_avatax_official 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/.env-sample +4 -0
  3. data/.gitignore +22 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +77 -0
  6. data/.travis.yml +78 -0
  7. data/Appraisals +49 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE +26 -0
  10. data/README.md +173 -0
  11. data/Rakefile +21 -0
  12. data/app/controllers/spree/admin/avalara_entity_use_codes_controller.rb +61 -0
  13. data/app/controllers/spree/admin/avatax_pings_controller.rb +19 -0
  14. data/app/controllers/spree/admin/avatax_settings_controller.rb +26 -0
  15. data/app/controllers/spree/admin/users_controller_decorator.rb +27 -0
  16. data/app/helpers/spree_avatax_official/http_helper.rb +17 -0
  17. data/app/helpers/spree_avatax_official/tax_adjustment_label_helper.rb +30 -0
  18. data/app/models/avatax/request_decorator.rb +34 -0
  19. data/app/models/spree_avatax_official/avatax_log.rb +57 -0
  20. data/app/models/spree_avatax_official/calculator/avatax_transaction_calculator.rb +28 -0
  21. data/app/models/spree_avatax_official/configuration.rb +20 -0
  22. data/app/models/spree_avatax_official/entity_use_code.rb +7 -0
  23. data/app/models/spree_avatax_official/has_uuid.rb +22 -0
  24. data/app/models/spree_avatax_official/spree/address_decorator.rb +31 -0
  25. data/app/models/spree_avatax_official/spree/inventory_unit_decorator.rb +11 -0
  26. data/app/models/spree_avatax_official/spree/line_item_decorator.rb +27 -0
  27. data/app/models/spree_avatax_official/spree/order_decorator.rb +91 -0
  28. data/app/models/spree_avatax_official/spree/refund_decorator.rb +22 -0
  29. data/app/models/spree_avatax_official/spree/shipment_decorator.rb +25 -0
  30. data/app/models/spree_avatax_official/spree/tax_category_decorator.rb +14 -0
  31. data/app/models/spree_avatax_official/spree/user_decorator.rb +11 -0
  32. data/app/models/spree_avatax_official/transaction.rb +27 -0
  33. data/app/overrides/spree/admin/shared/sub_menu/_configuration.rb +10 -0
  34. data/app/overrides/spree/admin/sidebar.rb +14 -0
  35. data/app/presenters/spree_avatax_official/address_presenter.rb +40 -0
  36. data/app/presenters/spree_avatax_official/item_presenter.rb +92 -0
  37. data/app/presenters/spree_avatax_official/ship_to_address_presenter.rb +22 -0
  38. data/app/presenters/spree_avatax_official/transactions/adjust_presenter.rb +31 -0
  39. data/app/presenters/spree_avatax_official/transactions/create_presenter.rb +80 -0
  40. data/app/presenters/spree_avatax_official/transactions/full_refund_presenter.rb +32 -0
  41. data/app/presenters/spree_avatax_official/transactions/partial_refund_presenter.rb +46 -0
  42. data/app/services/spree_avatax_official/address/validate.rb +27 -0
  43. data/app/services/spree_avatax_official/base.rb +71 -0
  44. data/app/services/spree_avatax_official/create_tax_adjustments_service.rb +133 -0
  45. data/app/services/spree_avatax_official/generate_transaction_cache_key_service.rb +94 -0
  46. data/app/services/spree_avatax_official/get_tax_service.rb +20 -0
  47. data/app/services/spree_avatax_official/settings/update_service.rb +36 -0
  48. data/app/services/spree_avatax_official/transactions/adjust_service.rb +36 -0
  49. data/app/services/spree_avatax_official/transactions/create_service.rb +47 -0
  50. data/app/services/spree_avatax_official/transactions/find_order_transaction_service.rb +23 -0
  51. data/app/services/spree_avatax_official/transactions/full_refund_service.rb +36 -0
  52. data/app/services/spree_avatax_official/transactions/get_by_code_service.rb +32 -0
  53. data/app/services/spree_avatax_official/transactions/partial_refund_service.rb +33 -0
  54. data/app/services/spree_avatax_official/transactions/refund_service.rb +87 -0
  55. data/app/services/spree_avatax_official/transactions/void_service.rb +28 -0
  56. data/app/services/spree_avatax_official/utilities/ping_service.rb +9 -0
  57. data/app/views/spree/admin/avalara_entity_use_codes/_form.html.erb +20 -0
  58. data/app/views/spree/admin/avalara_entity_use_codes/edit.html.erb +12 -0
  59. data/app/views/spree/admin/avalara_entity_use_codes/index.html.erb +35 -0
  60. data/app/views/spree/admin/avalara_entity_use_codes/new.html.erb +12 -0
  61. data/app/views/spree/admin/avatax_settings/edit.html.erb +99 -0
  62. data/app/views/spree/admin/users/avalara_information.html.erb +39 -0
  63. data/app/views/spree/admin/zones/_form.html.erb +58 -0
  64. data/bin/rails +8 -0
  65. data/config/initializers/spree_permitted_attributes.rb +5 -0
  66. data/config/locales/en.yml +40 -0
  67. data/config/routes.rb +15 -0
  68. data/db/migrate/20190118155953_add_exemption_number_to_spree_users.rb +7 -0
  69. data/db/migrate/20190118160653_create_spree_avalara_entity_use_codes.rb +11 -0
  70. data/db/migrate/20190118161318_add_avalara_entity_use_code_reference_to_users.rb +7 -0
  71. data/db/migrate/20190118162345_add_vat_id_to_spree_users.rb +7 -0
  72. data/db/migrate/20190122154119_create_spree_avatax_official_transactions.rb +10 -0
  73. data/db/migrate/20190201123706_add_tax_code_to_spree_tax_categories.rb +7 -0
  74. data/db/migrate/20190208130136_add_avatax_company_code_to_spree_stores.rb +8 -0
  75. data/db/migrate/20190225145808_add_uuid_to_spree_line_items_and_spree_shipments.rb +13 -0
  76. data/db/migrate/20190329071439_add_included_in_price_to_spree_zones.rb +5 -0
  77. data/db/migrate/20191001085458_remove_spree_avalara_entity_use_codes_table.rb +12 -0
  78. data/db/migrate/20191001092353_create_spree_avatax_official_entity_use_codes_table.rb +18 -0
  79. data/db/seeds.rb +1 -0
  80. data/gemfiles/spree_3_1.gemfile +16 -0
  81. data/gemfiles/spree_3_7.gemfile +13 -0
  82. data/gemfiles/spree_4_0.gemfile +14 -0
  83. data/gemfiles/spree_4_1.gemfile +14 -0
  84. data/gemfiles/spree_4_2.gemfile +14 -0
  85. data/gemfiles/spree_master.gemfile +14 -0
  86. data/lib/generators/spree_avatax_official/install/install_generator.rb +38 -0
  87. data/lib/generators/spree_avatax_official/install/templates/config/initializers/spree_avatax_official.rb +9 -0
  88. data/lib/spree_avatax_official.rb +5 -0
  89. data/lib/spree_avatax_official/engine.rb +32 -0
  90. data/lib/spree_avatax_official/factories.rb +9 -0
  91. data/lib/spree_avatax_official/seeder.rb +51 -0
  92. data/lib/spree_avatax_official/version.rb +11 -0
  93. data/lib/tasks/load_seeds.rake +6 -0
  94. data/spec/.rubocop.yml +36 -0
  95. data/spec/factories/canada_address_factory.rb +23 -0
  96. data/spec/factories/europe_address_factory.rb +22 -0
  97. data/spec/factories/spree/avatax_global_zone_factory.rb +16 -0
  98. data/spec/factories/spree/avatax_order_factory.rb +69 -0
  99. data/spec/factories/spree/avatax_shipment_factory.rb +31 -0
  100. data/spec/factories/spree/avatax_shipping_method_factory.rb +35 -0
  101. data/spec/factories/spree/avatax_tax_category_factory.rb +17 -0
  102. data/spec/factories/spree/avatax_tax_rate_factory.rb +23 -0
  103. data/spec/factories/spree/line_item_factory.rb +13 -0
  104. data/spec/factories/spree/promotion_factory.rb +35 -0
  105. data/spec/factories/spree/shipping_calculator_factory.rb +10 -0
  106. data/spec/factories/spree_avatax_official/avalara_entity_use_code_factory.rb +7 -0
  107. data/spec/factories/spree_avatax_official/calculator/avatax_transaction_calculator_factory.rb +3 -0
  108. data/spec/factories/spree_avatax_official/transaction_factory.rb +12 -0
  109. data/spec/factories/usa_address_factory.rb +34 -0
  110. data/spec/features/admin/edit_avatax_settings_spec.rb +11 -0
  111. data/spec/features/admin/update_avatax_settings_spec.rb +200 -0
  112. data/spec/features/spree/admin/avalara_entity_use_codes_controller_spec.rb +71 -0
  113. data/spec/features/spree/admin/users_controller_spec.rb +34 -0
  114. data/spec/helpers/spree_avatax_official/tax_adjustment_label_helper_spec.rb +81 -0
  115. data/spec/lib/spree_avatax_official/seeder_spec.rb +23 -0
  116. data/spec/models/spree/line_item_spec.rb +76 -0
  117. data/spec/models/spree/order_spec.rb +210 -0
  118. data/spec/models/spree/shipment_spec.rb +11 -0
  119. data/spec/models/spree_avatax_official/avatax_log_spec.rb +87 -0
  120. data/spec/models/spree_avatax_official/entity_use_code_spec.rb +9 -0
  121. data/spec/models/spree_avatax_official/has_uuid_spec.rb +21 -0
  122. data/spec/models/spree_avatax_official/spree/refund_decorator_spec.rb +31 -0
  123. data/spec/models/spree_avatax_official/transaction_spec.rb +9 -0
  124. data/spec/presenters/spree_avatax_official/address_presenter_spec.rb +53 -0
  125. data/spec/presenters/spree_avatax_official/item_presenter_spec.rb +159 -0
  126. data/spec/presenters/spree_avatax_official/ship_to_address_presenter_spec.rb +34 -0
  127. data/spec/presenters/spree_avatax_official/transactions/adjust_presenter_spec.rb +39 -0
  128. data/spec/presenters/spree_avatax_official/transactions/create_presenter_spec.rb +110 -0
  129. data/spec/presenters/spree_avatax_official/transactions/full_refund_presenter_spec.rb +30 -0
  130. data/spec/presenters/spree_avatax_official/transactions/partial_refund_presenter_spec.rb +48 -0
  131. data/spec/services/spree_avatax_official/address/validate_spec.rb +47 -0
  132. data/spec/services/spree_avatax_official/create_tax_adjustments_service_spec.rb +444 -0
  133. data/spec/services/spree_avatax_official/generate_transaction_cache_key_service_spec.rb +29 -0
  134. data/spec/services/spree_avatax_official/get_tax_service_spec.rb +33 -0
  135. data/spec/services/spree_avatax_official/settings/update_service_spec.rb +131 -0
  136. data/spec/services/spree_avatax_official/transactions/adjust_service_spec.rb +55 -0
  137. data/spec/services/spree_avatax_official/transactions/create_service_spec.rb +262 -0
  138. data/spec/services/spree_avatax_official/transactions/find_order_transaction_service_spec.rb +38 -0
  139. data/spec/services/spree_avatax_official/transactions/full_refund_service_spec.rb +24 -0
  140. data/spec/services/spree_avatax_official/transactions/get_by_code_service_spec.rb +74 -0
  141. data/spec/services/spree_avatax_official/transactions/partial_refund_service_spec.rb +33 -0
  142. data/spec/services/spree_avatax_official/transactions/refund_service_spec.rb +65 -0
  143. data/spec/services/spree_avatax_official/transactions/void_service_spec.rb +33 -0
  144. data/spec/services/spree_avatax_official/utilities/ping_service_spec.rb +35 -0
  145. data/spec/spec_helper.rb +104 -0
  146. data/spec/support/avatax.rb +15 -0
  147. data/spec/support/capybara.rb +21 -0
  148. data/spec/support/database_cleaner.rb +25 -0
  149. data/spec/support/factory_bot.rb +9 -0
  150. data/spec/support/vcr.rb +17 -0
  151. data/spec/vcr/SpreeAvataxOfficial_Transactions_FindOrderTransactionService/_call/with_Spree_Order/when_order_does_NOT_have_a_SalesInvoice_transaction/when_transaction_does_NOT_exist_in_avatax/returns_failure.yml +44 -0
  152. data/spec/vcr/SpreeAvataxOfficial_Transactions_FindOrderTransactionService/_call/with_Spree_Order/when_order_does_NOT_have_a_SalesInvoice_transaction/when_transaction_exists_in_avatax/returns_success_and_creates_a_new_SalesInvoice_transaction.yml +48 -0
  153. data/spec/vcr/spree_avatax_official/address/validate_failure.yml +46 -0
  154. data/spec/vcr/spree_avatax_official/address/validate_success.yml +43 -0
  155. data/spec/vcr/spree_avatax_official/address/zipcode_failure.yml +45 -0
  156. data/spec/vcr/spree_avatax_official/create_tax_adjustments/californian_stock_location.yml +60 -0
  157. data/spec/vcr/spree_avatax_official/create_tax_adjustments/completed_order_line_item_update.yml +62 -0
  158. data/spec/vcr/spree_avatax_official/create_tax_adjustments/order_adjustment_promotion.yml +61 -0
  159. data/spec/vcr/spree_avatax_official/create_tax_adjustments/shipment_adjustment_promotion.yml +60 -0
  160. data/spec/vcr/spree_avatax_official/create_tax_adjustments/simple_completed_order.yml +60 -0
  161. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_excluded/line_item_adjustment_promotion.yml +59 -0
  162. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_excluded/line_item_and_shipment.yml +60 -0
  163. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_excluded/multiple_line_items_multiple_quantity.yml +59 -0
  164. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_excluded/single_line_item.yml +55 -0
  165. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_included/line_item_adjustment_promotion.yml +59 -0
  166. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_included/line_item_and_shipment.yml +60 -0
  167. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_included/multiple_line_items_multiple_quantity.yml +59 -0
  168. data/spec/vcr/spree_avatax_official/create_tax_adjustments/tax_included/single_line_item.yml +55 -0
  169. data/spec/vcr/spree_avatax_official/create_tax_adjustments/with_incorrect_address.yml +97 -0
  170. data/spec/vcr/spree_avatax_official/get_tax/error.yml +49 -0
  171. data/spec/vcr/spree_avatax_official/get_tax/sucsess.yml +55 -0
  172. data/spec/vcr/spree_avatax_official/transactions/adjust/failure.yml +101 -0
  173. data/spec/vcr/spree_avatax_official/transactions/adjust/invoice_order_success.yml +117 -0
  174. data/spec/vcr/spree_avatax_official/transactions/create/complete_order_success.yml +60 -0
  175. data/spec/vcr/spree_avatax_official/transactions/create/gst/canada_to_canada.yml +55 -0
  176. data/spec/vcr/spree_avatax_official/transactions/create/gst/canada_to_us.yml +57 -0
  177. data/spec/vcr/spree_avatax_official/transactions/create/gst/us_to_canada.yml +56 -0
  178. data/spec/vcr/spree_avatax_official/transactions/create/invoice_order_success.yml +60 -0
  179. data/spec/vcr/spree_avatax_official/transactions/create/sales_order_success.yml +60 -0
  180. data/spec/vcr/spree_avatax_official/transactions/create/timeout.yml +54 -0
  181. data/spec/vcr/spree_avatax_official/transactions/create/vat/europe_to_europe.yml +57 -0
  182. data/spec/vcr/spree_avatax_official/transactions/create/vat/europe_to_us.yml +57 -0
  183. data/spec/vcr/spree_avatax_official/transactions/create/vat/us_to_europe.yml +58 -0
  184. data/spec/vcr/spree_avatax_official/transactions/get_by_code/error.yml +44 -0
  185. data/spec/vcr/spree_avatax_official/transactions/get_by_code/order_with_custom_code_success.yml +49 -0
  186. data/spec/vcr/spree_avatax_official/transactions/get_by_code/order_with_number_success.yml +49 -0
  187. data/spec/vcr/spree_avatax_official/transactions/get_by_code/order_with_type_success.yml +48 -0
  188. data/spec/vcr/spree_avatax_official/transactions/get_by_code/order_without_type_success.yml +48 -0
  189. data/spec/vcr/spree_avatax_official/transactions/refund/full_refund_success.yml +114 -0
  190. data/spec/vcr/spree_avatax_official/transactions/refund/partial_refund_success.yml +58 -0
  191. data/spec/vcr/spree_avatax_official/transactions/refund/partial_refund_with_refund_success.yml +58 -0
  192. data/spec/vcr/spree_avatax_official/transactions/void/success.yml +112 -0
  193. data/spec/vcr/spree_avatax_official/utilities/ping.yml +41 -0
  194. data/spec/vcr/spree_avatax_official/utilities/ping_unauthorized.yml +42 -0
  195. data/spec/vcr/spree_line_item/update_tax_charge/decrease_quantity.yml +55 -0
  196. data/spec/vcr/spree_line_item/update_tax_charge/increase_quantity.yml +55 -0
  197. data/spec/vcr/spree_order/california_order.yml +67 -0
  198. data/spec/vcr/spree_order/complete_order.yml +60 -0
  199. data/spec/vcr/spree_order/order_transition_to_completed.yml +112 -0
  200. data/spec/vcr/spree_order/order_with_line_item_adjustment.yml +57 -0
  201. data/spec/vcr/spree_order/order_with_order_adjustment.yml +57 -0
  202. data/spec/vcr/spree_order/simple_order_with_single_line_item.yml +55 -0
  203. data/spec/vcr/spree_order/simple_order_with_single_line_item_and_shipment.yml +57 -0
  204. data/spec/vcr/spree_order/update_tax_charge/create_line_item.yml +55 -0
  205. data/spree_avatax_official.gemspec +43 -0
  206. metadata +486 -0
@@ -0,0 +1,32 @@
1
+ module SpreeAvataxOfficial
2
+ module Transactions
3
+ class FullRefundPresenter
4
+ def initialize(order:, transaction_code:)
5
+ @order = order
6
+ @transaction_code = transaction_code
7
+ end
8
+
9
+ # based on https://developer.avalara.com/api-reference/avatax/rest/v2/models/RefundTransactionModel/
10
+ def to_json
11
+ {
12
+ refundTransactionCode: transaction_code,
13
+ referenceCode: reference_code,
14
+ refundDate: refund_date,
15
+ refundType: 'Full'
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :order, :transaction_code
22
+
23
+ def reference_code
24
+ order.number
25
+ end
26
+
27
+ def refund_date
28
+ order.completed_at.strftime('%Y-%m-%d')
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ module SpreeAvataxOfficial
2
+ module Transactions
3
+ class PartialRefundPresenter < CreatePresenter
4
+ OVERRIDE_TYPE = 'TaxDate'.freeze
5
+ OVERRIDE_REASON = 'Refund'.freeze
6
+
7
+ def initialize(order:, refund_items:, transaction_code:)
8
+ @order = order
9
+ @refund_items = refund_items
10
+ @transaction_code = transaction_code
11
+ @transaction_type = SpreeAvataxOfficial::Transaction::RETURN_INVOICE
12
+ end
13
+
14
+ # based on https://developer.avalara.com/api-reference/avatax/rest/v2/models/CreateTransactionModel/
15
+ # date should be refund date, taxDate should be order's completed_at date
16
+ def to_json
17
+ super.merge(
18
+ date: formatted_date(Time.current),
19
+ taxOverride: tax_override
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :refund_items
26
+
27
+ def tax_override
28
+ {
29
+ type: self.class::OVERRIDE_TYPE,
30
+ reason: self.class::OVERRIDE_REASON,
31
+ taxDate: formatted_date(order.completed_at)
32
+ }
33
+ end
34
+
35
+ def items_payload
36
+ refund_items.map do |item, (quantity, amount)|
37
+ SpreeAvataxOfficial::ItemPresenter.new(
38
+ item: item,
39
+ custom_quantity: quantity,
40
+ custom_amount: amount
41
+ ).to_json
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ module SpreeAvataxOfficial
2
+ module Address
3
+ class Validate < SpreeAvataxOfficial::Base
4
+ def call(address:)
5
+ response = send_request(address)
6
+
7
+ return failure(response) if errors?(response)
8
+
9
+ success(response)
10
+ end
11
+
12
+ private
13
+
14
+ def errors?(response)
15
+ response.body['messages'] || response.body['error']
16
+ end
17
+
18
+ def send_request(address)
19
+ ship_to_address_model = SpreeAvataxOfficial::ShipToAddressPresenter.new(
20
+ address: address
21
+ ).to_json
22
+
23
+ client.resolve_address(ship_to_address_model)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module SpreeAvataxOfficial
2
+ class Base
3
+ prepend ::Spree::ServiceModule::Base
4
+
5
+ APP_NAME = 'a0o0b000005HsXPAA0'.freeze
6
+ APP_VERSION = 'Spree by Spark'.freeze
7
+ SUCCESS_STATUSES = [200, 201].freeze
8
+ CONNECTION_OPTIONS = ::AvaTax::Configuration::DEFAULT_CONNECTION_OPTIONS.merge(
9
+ request: {
10
+ timeout: SpreeAvataxOfficial::Config.read_timeout,
11
+ open_timeout: SpreeAvataxOfficial::Config.open_timeout
12
+ }
13
+ ).freeze
14
+
15
+ private
16
+
17
+ def client
18
+ AvaTax::Client.new(
19
+ app_name: APP_NAME,
20
+ app_version: APP_VERSION,
21
+ connection_options: CONNECTION_OPTIONS,
22
+ logger: true,
23
+ faraday_response: true,
24
+ endpoint: SpreeAvataxOfficial::Config.endpoint,
25
+ username: SpreeAvataxOfficial::Config.account_number,
26
+ password: SpreeAvataxOfficial::Config.license_key
27
+ )
28
+ end
29
+
30
+ def company_code(order)
31
+ order.store&.avatax_company_code || SpreeAvataxOfficial::Config.company_code
32
+ end
33
+
34
+ def request_result(response, object = nil)
35
+ status = response.try(:status)
36
+ response_body = status ? response.body : response
37
+
38
+ if request_error?(status, response_body)
39
+ logger.error(object, response)
40
+
41
+ failure(response_body)
42
+ else
43
+ yield if block_given?
44
+
45
+ logger.info(response, object)
46
+
47
+ success(response_body)
48
+ end
49
+ end
50
+
51
+ def request_error?(status, response_body)
52
+ response_body['error'].present? || !status.in?(SUCCESS_STATUSES)
53
+ end
54
+
55
+ def refund_transaction_code(order_number, refundable_id)
56
+ "#{order_number}-#{refundable_id}"
57
+ end
58
+
59
+ def create_transaction!(order:, code: nil, transaction_type: nil)
60
+ SpreeAvataxOfficial::Transaction.create!(
61
+ code: code || order.number,
62
+ order: order,
63
+ transaction_type: transaction_type || SpreeAvataxOfficial::Transaction::SALES_INVOICE
64
+ )
65
+ end
66
+
67
+ def logger
68
+ @logger ||= SpreeAvataxOfficial::AvataxLog.new
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,133 @@
1
+ module SpreeAvataxOfficial
2
+ class CreateTaxAdjustmentsService < SpreeAvataxOfficial::Base
3
+ include SpreeAvataxOfficial::TaxAdjustmentLabelHelper
4
+
5
+ def call(order:) # rubocop:disable Metrics/AbcSize
6
+ return failure(::Spree.t('spree_avatax_official.create_tax_adjustments.order_canceled')) if order.canceled?
7
+
8
+ order.all_adjustments.tax.destroy_all
9
+
10
+ return failure(::Spree.t('spree_avatax_official.create_tax_adjustments.tax_calculation_unnecessary')) unless order.avatax_tax_calculation_required?
11
+
12
+ transaction_cache_key = SpreeAvataxOfficial::GenerateTransactionCacheKeyService.call(order: order).value
13
+
14
+ avatax_response = Rails.cache.fetch(transaction_cache_key, expires_in: 5.minutes) do
15
+ send_transaction_to_avatax(order)
16
+ end
17
+
18
+ return failure(build_error_message_from_response(avatax_response.value)) if avatax_failed_response?(avatax_response)
19
+
20
+ process_avatax_items(order, avatax_response.value['lines'])
21
+
22
+ success(true)
23
+ end
24
+
25
+ private
26
+
27
+ def send_transaction_to_avatax(order)
28
+ if order.avatax_sales_invoice_transaction.present?
29
+ SpreeAvataxOfficial::Transactions::AdjustService.call(
30
+ order: order,
31
+ adjustment_reason: SpreeAvataxOfficial::Transaction::DEFAULT_ADJUSTMENT_REASON,
32
+ adjustment_description: ::Spree.t('spree_avatax_official.create_tax_adjustments.adjustment_description')
33
+ )
34
+ else
35
+ SpreeAvataxOfficial::Transactions::CreateService.call(order: order)
36
+ end
37
+ end
38
+
39
+ def avatax_failed_response?(avatax_response)
40
+ avatax_response.failure? || avatax_response.value['totalTax'].zero?
41
+ end
42
+
43
+ def process_avatax_items(order, avatax_items)
44
+ avatax_items.each { |avatax_item| process_avatax_item(order, avatax_item) }
45
+ end
46
+
47
+ def process_avatax_item(order, avatax_item)
48
+ tax_amount = avatax_item['taxCalculated']
49
+
50
+ return if tax_amount.zero?
51
+
52
+ item_suffix = avatax_item['lineNumber'].slice(0..2)
53
+ item_id = avatax_item['lineNumber'].slice(3..-1)
54
+ item = find_item(order, item_id, item_suffix)
55
+
56
+ # Spree allows to setup shipping methods without tax category and
57
+ # in that case it doesn't make sense to collect any tax,
58
+ # especially because of validation that requires presence of tax category
59
+ return if item.tax_category.nil?
60
+
61
+ tax_rate = find_or_create_tax_rate(item, avatax_item)
62
+
63
+ store_pre_tax_amount(item, tax_rate, tax_amount)
64
+
65
+ create_tax_adjustment(item, tax_rate, tax_amount)
66
+ end
67
+
68
+ def find_item(order, uuid, suffix)
69
+ case suffix
70
+ when 'LI-'
71
+ order.line_items.find_by(avatax_uuid: uuid)
72
+ when 'FR-'
73
+ order.shipments.find_by(avatax_uuid: uuid)
74
+ end
75
+ end
76
+
77
+ def find_or_create_tax_rate(item, avatax_item)
78
+ ::Spree::TaxRate.find_or_create_by!(
79
+ name: tax_rate_name,
80
+ amount: sum_rates_from_details(avatax_item),
81
+ zone: item.tax_zone&.reload,
82
+ tax_category: item.tax_category,
83
+ show_rate_in_label: false,
84
+ included_in_price: item.included_in_price
85
+ ) do |tax_rate|
86
+ tax_rate.calculator = SpreeAvataxOfficial::Calculator::AvataxTransactionCalculator.new
87
+ end
88
+ end
89
+
90
+ def tax_rate_name
91
+ ENV.fetch('AVATAX_TAX_RATE_NAME', 'AvaTax Official Tax Rate')
92
+ end
93
+
94
+ def create_tax_adjustment(item, source, amount)
95
+ item.adjustments.create!(
96
+ source: source,
97
+ amount: amount,
98
+ included: item.included_in_price,
99
+ label: tax_adjustment_label(item, source.amount),
100
+ order: item.order
101
+ )
102
+ end
103
+
104
+ def store_pre_tax_amount(item, tax_rate, tax_amount)
105
+ pre_tax_amount = case item.class.name.demodulize
106
+ when 'LineItem' then item.discounted_amount
107
+ when 'Shipment' then item.discounted_cost
108
+ end
109
+
110
+ pre_tax_amount -= tax_amount if tax_rate.included_in_price?
111
+
112
+ item.update_column(:pre_tax_amount, pre_tax_amount)
113
+ end
114
+
115
+ def sum_rates_from_details(avatax_item)
116
+ avatax_item['details']
117
+ .sum { |detail_entry| detail_entry['rate'] }
118
+ .round(6)
119
+ end
120
+
121
+ def build_error_message_from_response(avatax_response)
122
+ return ::Spree.t('spree_avatax_official.create_tax_adjustments.tax_calculation_failed') unless error_present?(avatax_response)
123
+
124
+ avatax_response['error']['details'].map do |error_detail_entry|
125
+ "#{error_detail_entry['number']} - #{error_detail_entry['message']} - #{error_detail_entry['description']}."
126
+ end.join(' ')
127
+ end
128
+
129
+ def error_present?(avatax_response)
130
+ avatax_response && avatax_response['error'].present?
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,94 @@
1
+ module SpreeAvataxOfficial
2
+ class GenerateTransactionCacheKeyService < SpreeAvataxOfficial::Base
3
+ def call(order:)
4
+ reloaded_order = order.class.includes(
5
+ :line_items,
6
+ :adjustments,
7
+ :shipments => :stock_location,
8
+ order.tax_address_symbol => %i[state country]
9
+ ).find(order.id)
10
+
11
+ success(transaction_cache_key(reloaded_order))
12
+ end
13
+
14
+ private
15
+
16
+ def transaction_cache_key(order)
17
+ transaction_cache_key = [
18
+ order_cache_key(order),
19
+ tax_address_cache_key(order.tax_address),
20
+ line_items_cache_key(order),
21
+ shipments_cache_key(order.shipments),
22
+ avatax_preferences_cache_key(order)
23
+ ].join('-')
24
+
25
+ # Cache key length has to be compressed as max_length is 250:
26
+ # https://github.com/memcached/memcached/blob/master/memcached.h#L36
27
+ "AvaTax-transaction-#{Digest::MD5.hexdigest(transaction_cache_key)}"
28
+ end
29
+
30
+ def order_cache_key(order)
31
+ [
32
+ order.number,
33
+ order.avatax_discount_amount
34
+ ].join('-')
35
+ end
36
+
37
+ def tax_address_cache_key(tax_address)
38
+ [
39
+ tax_address.address1,
40
+ tax_address.address2,
41
+ tax_address.city,
42
+ tax_address.state.try(:abbr),
43
+ tax_address.country.try(:iso),
44
+ tax_address.zipcode
45
+ ].join('-')
46
+ end
47
+
48
+ def line_items_cache_key(order) # rubocop:disable Metrics/MethodLength
49
+ order.line_items.map do |line_item|
50
+ [
51
+ line_item.avatax_uuid,
52
+ line_item.quantity,
53
+ line_item.variant_id,
54
+ line_item.price,
55
+ line_item.amount,
56
+ line_item.discounted_amount,
57
+ order.line_items_discounted_in_avatax?,
58
+ line_item.avatax_tax_code
59
+ ].join('-')
60
+ end.join('-')
61
+ end
62
+
63
+ def shipments_cache_key(shipments)
64
+ shipments.map do |shipment|
65
+ [
66
+ shipment.avatax_uuid,
67
+ shipment.discounted_cost,
68
+ shipment.avatax_tax_code,
69
+ stock_location_cache_key(shipment.stock_location)
70
+ ].join('-')
71
+ end.join('-')
72
+ end
73
+
74
+ def stock_location_cache_key(stock_location)
75
+ [
76
+ stock_location.address1,
77
+ stock_location.address2,
78
+ stock_location.city,
79
+ stock_location.state.try(:abbr),
80
+ stock_location.country.try(:iso)
81
+ ].join('-')
82
+ end
83
+
84
+ def avatax_preferences_cache_key(order)
85
+ ship_from_address_timestamp = ship_from_address_preference.try(:updated_at).try(:utc).try(:to_s, :number)
86
+
87
+ "#{company_code(order)}-#{ship_from_address_timestamp}"
88
+ end
89
+
90
+ def ship_from_address_preference
91
+ ::Spree::Preference.find_by(key: 'spree_avatax_official/configuration/ship_from_address')
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,20 @@
1
+ module SpreeAvataxOfficial
2
+ class GetTaxService < SpreeAvataxOfficial::Base
3
+ def call(order:, options: {})
4
+ response = send_request(order, options)
5
+
6
+ return failure(response) if response.body['error'].present?
7
+
8
+ success(taxCalculated: response.body['totalTaxCalculated'])
9
+ end
10
+
11
+ def send_request(order, options)
12
+ create_transaction_model = SpreeAvataxOfficial::Transactions::CreatePresenter.new(
13
+ order: order,
14
+ transaction_type: SpreeAvataxOfficial::Transaction::SALES_ORDER
15
+ ).to_json
16
+
17
+ client.create_transaction(create_transaction_model, options)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeAvataxOfficial
2
+ module Settings
3
+ class UpdateService < SpreeAvataxOfficial::Base
4
+ def call(params:)
5
+ update_settings(params)
6
+ end
7
+
8
+ private
9
+
10
+ def update_settings(params)
11
+ update_address_settings(params[:ship_from])
12
+
13
+ SpreeAvataxOfficial::Config.account_number = params[:account_number] if params.key?(:account_number)
14
+ SpreeAvataxOfficial::Config.license_key = params[:license_key] if params.key?(:license_key)
15
+ SpreeAvataxOfficial::Config.company_code = params[:company_code] if params.key?(:company_code)
16
+ SpreeAvataxOfficial::Config.endpoint = params[:endpoint] if params.key?(:endpoint)
17
+ SpreeAvataxOfficial::Config.address_validation_enabled = params[:address_validation_enabled] if params.key?(:address_validation_enabled)
18
+ SpreeAvataxOfficial::Config.commit_transaction_enabled = params[:commit_transaction_enabled] if params.key?(:commit_transaction_enabled)
19
+ SpreeAvataxOfficial::Config.enabled = params[:enabled] if params.key?(:enabled)
20
+ end
21
+
22
+ def update_address_settings(ship_from_params)
23
+ return unless ship_from_params
24
+
25
+ SpreeAvataxOfficial::Config.ship_from_address = {
26
+ line1: ship_from_params[:line1],
27
+ line2: ship_from_params[:line2],
28
+ city: ship_from_params[:city],
29
+ region: ship_from_params[:region],
30
+ country: ship_from_params[:country],
31
+ postalCode: ship_from_params[:postal_code]
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end