super_good-solidus_taxjar 0.15.2 → 0.18.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +16 -0
  3. data/.gitignore +19 -13
  4. data/.travis.yml +12 -5
  5. data/CHANGELOG.md +71 -0
  6. data/Gemfile +23 -5
  7. data/LICENSE +26 -0
  8. data/PULL_REQUEST_TEMPLATE.md +20 -0
  9. data/README.md +34 -9
  10. data/Rakefile +4 -17
  11. data/app/controllers/spree/admin/taxjar_settings_controller.rb +8 -0
  12. data/app/decorators/super_good/solidus_taxjar/spree/order_updater/fire_recalculated_event.rb +18 -0
  13. data/app/overrides/spree/admin/shared/_configuration_menu.rb +11 -0
  14. data/app/views/spree/admin/taxjar_settings/show.html.erb +13 -0
  15. data/bin/rails +7 -0
  16. data/bin/rails-engine +13 -0
  17. data/bin/rails-sandbox +16 -0
  18. data/bin/rake +7 -0
  19. data/bin/sandbox +84 -0
  20. data/config/routes.rb +7 -0
  21. data/lib/super_good-solidus_taxjar.rb +4 -0
  22. data/lib/super_good/engine.rb +10 -0
  23. data/lib/super_good/solidus_taxjar.rb +19 -7
  24. data/lib/super_good/solidus_taxjar/addresses.rb +63 -0
  25. data/lib/super_good/solidus_taxjar/api.rb +27 -10
  26. data/lib/super_good/solidus_taxjar/api_params.rb +26 -8
  27. data/lib/super_good/solidus_taxjar/calculator_helper.rb +38 -0
  28. data/lib/super_good/solidus_taxjar/discount_calculator.rb +1 -1
  29. data/lib/super_good/solidus_taxjar/tax_calculator.rb +17 -50
  30. data/lib/super_good/solidus_taxjar/tax_rate_calculator.rb +35 -0
  31. data/lib/super_good/solidus_taxjar/version.rb +2 -2
  32. data/spec/features/spree/admin/taxjar_settings_spec.rb +48 -0
  33. data/spec/models/spree/order_updater_spec.rb +12 -0
  34. data/spec/spec_helper.rb +20 -0
  35. data/spec/super_good/solidus_taxjar/addresses_spec.rb +288 -0
  36. data/spec/super_good/solidus_taxjar/api_params_spec.rb +434 -0
  37. data/spec/super_good/solidus_taxjar/api_spec.rb +222 -0
  38. data/spec/super_good/solidus_taxjar/discount_calculator_spec.rb +13 -0
  39. data/spec/super_good/solidus_taxjar/tax_calculator_spec.rb +332 -0
  40. data/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb +116 -0
  41. data/spec/super_good/solidus_taxjar_spec.rb +92 -0
  42. data/super_good-solidus_taxjar.gemspec +17 -14
  43. metadata +46 -9
  44. data/LICENSE.txt +0 -21
@@ -0,0 +1,222 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe SuperGood::SolidusTaxjar::Api do
4
+ describe ".new" do
5
+ subject { described_class.new }
6
+
7
+ before do
8
+ allow(ENV).to receive(:fetch).and_call_original
9
+ allow(ENV).to receive(:fetch).with("TAXJAR_API_KEY").and_return("taxjar_api_token")
10
+ end
11
+
12
+ it "sets the correct headers" do
13
+ expect_any_instance_of(::Taxjar::Client).to receive(:set_api_config).with('headers', {
14
+ 'x-api-version' => '2020-08-07',
15
+ 'plugin' => 'supergoodsolidustaxjar'
16
+ })
17
+ subject
18
+ end
19
+ end
20
+
21
+ describe ".default_taxjar_client" do
22
+ subject { described_class.default_taxjar_client }
23
+
24
+ before do
25
+ allow(ENV).to receive(:fetch).and_call_original
26
+ allow(ENV).to receive(:fetch).with("TAXJAR_API_KEY").and_return("taxjar_api_token")
27
+ end
28
+
29
+ it "returns an instance of the TaxJar client" do
30
+ expect(subject).to be_an_instance_of(::Taxjar::Client)
31
+ end
32
+ end
33
+
34
+
35
+ describe "#tax_for" do
36
+ subject { api.tax_for order }
37
+
38
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
39
+ let(:dummy_client) { instance_double ::Taxjar::Client }
40
+ let(:order) { Spree::Order.new }
41
+
42
+ before do
43
+ allow(SuperGood::SolidusTaxjar::ApiParams)
44
+ .to receive(:order_params)
45
+ .with(order)
46
+ .and_return({order: "params"})
47
+
48
+ allow(dummy_client)
49
+ .to receive(:tax_for_order)
50
+ .with({order: "params"})
51
+ .and_return({some_kind_of: "response"})
52
+ end
53
+
54
+ it { is_expected.to eq({some_kind_of: "response"}) }
55
+ end
56
+
57
+ describe "tax_rate_for" do
58
+ subject { api.tax_rate_for address }
59
+
60
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
61
+ let(:dummy_client) { instance_double ::Taxjar::Client }
62
+ let(:address) { Spree::Address.new }
63
+ let(:tax_rate) { 0.04 }
64
+ let(:response) { double(rate: tax_rate) }
65
+
66
+ before do
67
+ allow(SuperGood::SolidusTaxjar::ApiParams)
68
+ .to receive(:tax_rate_address_params)
69
+ .with(address)
70
+ .and_return({address: "params"})
71
+
72
+ allow(dummy_client)
73
+ .to receive(:tax_for_order)
74
+ .with({address: "params"})
75
+ .and_return(response)
76
+ end
77
+
78
+ it { is_expected.to eq(tax_rate) }
79
+ end
80
+
81
+ describe "#tax_rates_for" do
82
+ subject { api.tax_rates_for address }
83
+
84
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
85
+ let(:dummy_client) { instance_double ::Taxjar::Client }
86
+ let(:address) { Spree::Address.new }
87
+
88
+ before do
89
+ allow(SuperGood::SolidusTaxjar::ApiParams)
90
+ .to receive(:address_params)
91
+ .with(address)
92
+ .and_return(["zipcode", {address: "params"}])
93
+
94
+ allow(dummy_client)
95
+ .to receive(:rates_for_location)
96
+ .with("zipcode", {address: "params"})
97
+ .and_return({some_kind_of: "response"})
98
+ end
99
+
100
+ it { is_expected.to eq({some_kind_of: "response"}) }
101
+ end
102
+
103
+ describe "#create_transaction_for" do
104
+ subject { api.create_transaction_for order }
105
+
106
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
107
+ let(:dummy_client) { instance_double ::Taxjar::Client }
108
+ let(:order) { Spree::Order.new }
109
+
110
+ before do
111
+ allow(SuperGood::SolidusTaxjar::ApiParams)
112
+ .to receive(:transaction_params)
113
+ .with(order)
114
+ .and_return({transaction: "params"})
115
+
116
+ allow(dummy_client)
117
+ .to receive(:create_order)
118
+ .with({transaction: "params"})
119
+ .and_return({some_kind_of: "response"})
120
+ end
121
+
122
+ it { is_expected.to eq({some_kind_of: "response"}) }
123
+ end
124
+
125
+ describe "#update_transaction_for" do
126
+ subject { api.update_transaction_for order }
127
+
128
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
129
+ let(:dummy_client) { instance_double ::Taxjar::Client }
130
+ let(:order) { Spree::Order.new }
131
+
132
+ before do
133
+ allow(SuperGood::SolidusTaxjar::ApiParams)
134
+ .to receive(:transaction_params)
135
+ .with(order)
136
+ .and_return({transaction: "params"})
137
+
138
+ allow(dummy_client)
139
+ .to receive(:update_order)
140
+ .with({transaction: "params"})
141
+ .and_return({some_kind_of: "response"})
142
+ end
143
+
144
+ it { is_expected.to eq({some_kind_of: "response"}) }
145
+ end
146
+
147
+ describe "#update_transaction_for" do
148
+ subject { api.delete_transaction_for order }
149
+
150
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
151
+ let(:dummy_client) { instance_double ::Taxjar::Client }
152
+ let(:order) { Spree::Order.new(number: "R111222333") }
153
+
154
+ before do
155
+ allow(dummy_client)
156
+ .to receive(:delete_order)
157
+ .with("R111222333")
158
+ .and_return({some_kind_of: "response"})
159
+ end
160
+
161
+ it { is_expected.to eq({some_kind_of: "response"}) }
162
+ end
163
+
164
+ describe "#create_refund_for" do
165
+ subject { api.create_refund_for reimbursement }
166
+
167
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
168
+ let(:dummy_client) { instance_double ::Taxjar::Client }
169
+ let(:reimbursement) { Spree::Reimbursement.new }
170
+
171
+ before do
172
+ allow(SuperGood::SolidusTaxjar::ApiParams)
173
+ .to receive(:refund_params)
174
+ .with(reimbursement)
175
+ .and_return({refund: "params"})
176
+
177
+ allow(dummy_client)
178
+ .to receive(:create_refund)
179
+ .with({refund: "params"})
180
+ .and_return({some_kind_of: "response"})
181
+ end
182
+
183
+ it { is_expected.to eq({some_kind_of: "response"}) }
184
+ end
185
+
186
+ describe "#validate_spree_address" do
187
+ subject { api.validate_spree_address spree_address }
188
+
189
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
190
+ let(:dummy_client) { instance_double ::Taxjar::Client }
191
+ let(:spree_address) { build :address }
192
+
193
+ before do
194
+ allow(SuperGood::SolidusTaxjar::ApiParams)
195
+ .to receive(:validate_address_params)
196
+ .with(spree_address)
197
+ .and_return({address: "params"})
198
+
199
+ allow(dummy_client)
200
+ .to receive(:validate_address)
201
+ .with({address: "params"})
202
+ .and_return({some_kind_of: "response"})
203
+ end
204
+
205
+ it { is_expected.to eq({some_kind_of: "response"}) }
206
+ end
207
+
208
+ describe "#nexus_regions" do
209
+ subject { api.nexus_regions }
210
+
211
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
212
+ let(:dummy_client) { instance_double ::Taxjar::Client }
213
+
214
+ before do
215
+ allow(dummy_client)
216
+ .to receive(:nexus_regions)
217
+ .and_return({some_kind_of: "response"})
218
+ end
219
+
220
+ it { is_expected.to eq({some_kind_of: "response"}) }
221
+ end
222
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe SuperGood::SolidusTaxjar::DiscountCalculator do
4
+ describe "#discount" do
5
+ subject { calculator.discount }
6
+
7
+ let(:calculator) { described_class.new line_item }
8
+
9
+ let(:line_item) { ::Spree::LineItem.new(promo_total: 12.34) }
10
+
11
+ it { is_expected.to eq(-12.34) }
12
+ end
13
+ end
@@ -0,0 +1,332 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe ::SuperGood::SolidusTaxjar::TaxCalculator do
4
+ describe "#calculate" do
5
+ subject { calculator.calculate }
6
+
7
+ let(:calculator) { described_class.new(order, api: dummy_api) }
8
+
9
+ let(:dummy_api) do
10
+ instance_double ::SuperGood::SolidusTaxjar::Api
11
+ end
12
+
13
+ let(:order) do
14
+ ::Spree::Order.new(
15
+ id: 10,
16
+ store: store,
17
+ ship_address: address,
18
+ line_items: line_items,
19
+ shipments: [
20
+ boring_shipment,
21
+ shipment_with_adjustment,
22
+ shipment_with_existing_tax
23
+ ]
24
+ )
25
+ end
26
+
27
+ let(:line_items) { [::Spree::LineItem.new(id: 33)] }
28
+
29
+ let(:boring_shipment) do
30
+ ::Spree::Shipment.new(id: 1, cost: 7)
31
+ end
32
+
33
+ let(:shipment_with_adjustment) do
34
+ ::Spree::Shipment.new(
35
+ id: 2,
36
+ cost: 20,
37
+ adjustments: [
38
+ ::Spree::Adjustment.new(amount: -7)
39
+ ]
40
+ )
41
+ end
42
+
43
+ let(:shipment_with_existing_tax) do
44
+ ::Spree::Shipment.new(
45
+ id: 3,
46
+ cost: 10,
47
+ additional_tax_total: 3,
48
+ adjustments: [
49
+ ::Spree::Adjustment.new(
50
+ amount: 3,
51
+ source_type: "Spree::TaxRate"
52
+ )
53
+ ]
54
+ )
55
+ end
56
+
57
+ let(:store) do
58
+ ::Spree::Store.new(
59
+ name: "Default Store",
60
+ url: "https://store.example.com",
61
+ code: "store",
62
+ mail_from_address: "contact@example.com",
63
+ cart_tax_country_iso: "US"
64
+ )
65
+ end
66
+
67
+ context "when the order has an empty tax address" do
68
+ let(:address) { nil }
69
+
70
+ it "returns no taxes" do
71
+ expect(subject.order_id).to eq order.id
72
+ expect(subject.shipment_taxes).to be_empty
73
+ expect(subject.line_item_taxes).to be_empty
74
+ end
75
+ end
76
+
77
+ context "when the order has an incomplete tax address" do
78
+ let(:address) do
79
+ ::Spree::Address.new(
80
+ first_name: "Ronnie James",
81
+ zipcode: nil,
82
+ address1: nil,
83
+ city: "Beverly Hills",
84
+ state_name: "California",
85
+ country: ::Spree::Country.new(iso: "US")
86
+ )
87
+ end
88
+
89
+ it "returns no taxes" do
90
+ expect(subject.order_id).to eq order.id
91
+ expect(subject.shipment_taxes).to be_empty
92
+ expect(subject.line_item_taxes).to be_empty
93
+ end
94
+ end
95
+
96
+ context "when the order has no tax address" do
97
+ let(:address) { nil }
98
+
99
+ it "returns no taxes" do
100
+ expect(subject.order_id).to eq order.id
101
+ expect(subject.shipment_taxes).to be_empty
102
+ expect(subject.line_item_taxes).to be_empty
103
+ end
104
+ end
105
+
106
+ context "when the order has no line items" do
107
+ let(:address) do
108
+ ::Spree::Address.new(
109
+ first_name: "Ronnie James",
110
+ zipcode: "90210",
111
+ address1: "9900 Wilshire Blvd",
112
+ city: "Beverly Hills",
113
+ state_name: "California",
114
+ country: ::Spree::Country.new(iso: "US")
115
+ )
116
+ end
117
+
118
+ let(:line_items) { [] }
119
+
120
+ it "returns no taxes" do
121
+ expect(subject.order_id).to eq order.id
122
+ expect(subject.shipment_taxes).to be_empty
123
+ expect(subject.line_item_taxes).to be_empty
124
+ end
125
+ end
126
+
127
+ context "when the API encounters an error" do
128
+ let(:address) do
129
+ ::Spree::Address.new(
130
+ first_name: "Ronnie James",
131
+ zipcode: "90210",
132
+ address1: "9900 Wilshire Blvd",
133
+ city: "Beverly Hills",
134
+ state_name: "California",
135
+ country: ::Spree::Country.new(iso: "US")
136
+ )
137
+ end
138
+
139
+ before do
140
+ allow(dummy_api).to receive(:tax_for).with(order).and_raise("A bad thing happened.")
141
+ end
142
+
143
+ it "calls the configured error handler" do
144
+ expect(SuperGood::SolidusTaxjar.exception_handler).to receive(:call) do |e|
145
+ expect(e).to be_a StandardError
146
+ expect(e.message).to eq "A bad thing happened."
147
+ end
148
+
149
+ subject
150
+ end
151
+
152
+ it "returns no taxes" do
153
+ expect(subject.order_id).to eq order.id
154
+ expect(subject.shipment_taxes).to be_empty
155
+ expect(subject.line_item_taxes).to be_empty
156
+ end
157
+ end
158
+
159
+ context "when the order has a non-empty tax address" do
160
+ let(:address) do
161
+ ::Spree::Address.new(
162
+ first_name: "Ronnie James",
163
+ zipcode: "90210",
164
+ address1: "9900 Wilshire Blvd",
165
+ city: "Beverly Hills",
166
+ state_name: "California",
167
+ country: ::Spree::Country.new(iso: "US")
168
+ )
169
+ end
170
+
171
+ before do
172
+ allow(dummy_api).to receive(:tax_for).with(order).and_return(
173
+ instance_double(
174
+ ::Taxjar::Tax,
175
+ breakdown: breakdown
176
+ )
177
+ )
178
+ end
179
+
180
+ context "and there is tax" do
181
+ let!(:tax_rate) do
182
+ ::Spree::TaxRate.create!(
183
+ name: "Sales Tax",
184
+ amount: 0.5,
185
+ calculator: ::Spree::Calculator.new
186
+ )
187
+ end
188
+
189
+ let(:breakdown) do
190
+ instance_double ::Taxjar::Breakdown,
191
+ line_items: [taxjar_line_item],
192
+ shipping?: !!shipping_tax_breakdown,
193
+ shipping: shipping_tax_breakdown
194
+ end
195
+
196
+ let(:taxjar_line_item) do
197
+ instance_double ::Taxjar::BreakdownLineItem, id: "33", tax_collectable: 6.66
198
+ end
199
+
200
+ let(:shipping_tax_breakdown) { nil }
201
+
202
+ it "returns the taxes" do
203
+ expect(subject.order_id).to eq order.id
204
+ expect(subject.shipment_taxes).to be_empty
205
+ expect(subject.line_item_taxes.length).to eq 1
206
+
207
+ item_tax = subject.line_item_taxes.first
208
+
209
+ aggregate_failures do
210
+ expect(item_tax.item_id).to eq 33
211
+ expect(item_tax.label).to eq "Sales Tax"
212
+ expect(item_tax.tax_rate).to eq tax_rate
213
+ expect(item_tax.amount).to eq 6.66
214
+ expect(item_tax.included_in_price).to eq false
215
+ end
216
+ end
217
+
218
+ context "with custom line item tax labels" do
219
+ before do
220
+ allow(SuperGood::SolidusTaxjar.line_item_tax_label_maker)
221
+ .to receive(:call)
222
+ .with(taxjar_line_item, line_items.first)
223
+ .and_return("Space Tax")
224
+ end
225
+
226
+ it "applies those labels" do
227
+ expect(subject.line_item_taxes.length).to eq 1
228
+ expect(subject.line_item_taxes.first.label).to eq "Space Tax"
229
+ end
230
+ end
231
+
232
+ context "but the taxable address check returns false" do
233
+ before do
234
+ allow(SuperGood::SolidusTaxjar.taxable_address_check)
235
+ .to receive(:call).with(address)
236
+ .and_return(false)
237
+ end
238
+
239
+ it "returns no taxes" do
240
+ expect(subject.order_id).to eq order.id
241
+ expect(subject.shipment_taxes).to be_empty
242
+ expect(subject.line_item_taxes).to be_empty
243
+ end
244
+ end
245
+
246
+ context "but the taxable order check returns false" do
247
+ before do
248
+ allow(SuperGood::SolidusTaxjar.taxable_order_check)
249
+ .to receive(:call).with(order)
250
+ .and_return(false)
251
+ end
252
+
253
+ it "returns no taxes" do
254
+ expect(subject.order_id).to eq order.id
255
+ expect(subject.shipment_taxes).to be_empty
256
+ expect(subject.line_item_taxes).to be_empty
257
+ end
258
+ end
259
+
260
+ context "when there are shipping taxes" do
261
+ let(:shipping_tax_breakdown) do
262
+ instance_double ::Taxjar::Shipping, tax_collectable: 10.00
263
+ end
264
+
265
+ it "returns the shipping taxes" do
266
+ shipment_taxes = subject.shipment_taxes
267
+ expect(shipment_taxes.length).to eq 3
268
+
269
+ aggregate_failures do
270
+ expect(shipment_taxes[0].item_id).to eq 1
271
+ expect(shipment_taxes[0].label).to eq "Sales Tax"
272
+ expect(shipment_taxes[0].tax_rate).to eq tax_rate
273
+ expect(shipment_taxes[0].amount).to eq 2.33
274
+ expect(shipment_taxes[0].included_in_price).to eq false
275
+
276
+ expect(shipment_taxes[1].item_id).to eq 2
277
+ expect(shipment_taxes[1].label).to eq "Sales Tax"
278
+ expect(shipment_taxes[1].tax_rate).to eq tax_rate
279
+ expect(shipment_taxes[1].amount).to eq 4.33
280
+ expect(shipment_taxes[1].included_in_price).to eq false
281
+
282
+ expect(shipment_taxes[2].item_id).to eq 3
283
+ expect(shipment_taxes[2].label).to eq "Sales Tax"
284
+ expect(shipment_taxes[2].tax_rate).to eq tax_rate
285
+ expect(shipment_taxes[2].amount).to eq 3.34
286
+ expect(shipment_taxes[2].included_in_price).to eq false
287
+ end
288
+ end
289
+
290
+ context "with custom shipping tax labels" do
291
+ before do
292
+ allow(SuperGood::SolidusTaxjar.shipping_tax_label_maker).to receive(:call)
293
+ .and_return("Magic Tax", "Spicy Tax", "Vegetable Tax")
294
+ end
295
+
296
+ it "applies those labels" do
297
+ shipment_taxes = subject.shipment_taxes
298
+ expect(shipment_taxes.length).to eq 3
299
+
300
+ aggregate_failures do
301
+ expect(shipment_taxes[0].label).to eq "Magic Tax"
302
+ expect(shipment_taxes[1].label).to eq "Spicy Tax"
303
+ expect(shipment_taxes[2].label).to eq "Vegetable Tax"
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ context "when test_mode is set" do
310
+ before { SuperGood::SolidusTaxjar.test_mode = true }
311
+ after { SuperGood::SolidusTaxjar.test_mode = false }
312
+
313
+ it "returns no taxes" do
314
+ expect(subject.order_id).to eq order.id
315
+ expect(subject.shipment_taxes).to be_empty
316
+ expect(subject.line_item_taxes).to be_empty
317
+ end
318
+ end
319
+ end
320
+
321
+ context "and there is not a breakdown" do
322
+ let(:breakdown) { nil }
323
+
324
+ it "returns no taxes" do
325
+ expect(subject.order_id).to eq order.id
326
+ expect(subject.shipment_taxes).to be_empty
327
+ expect(subject.line_item_taxes).to be_empty
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end