summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJared Norman <jared@super.gd>2020-01-15 15:24:22 -0800
committerGitHub <noreply@github.com>2020-01-15 15:24:22 -0800
commit48a68fcb692d83e211794ba064fd4448158a0c92 (patch)
tree01f32803ea75c3c5f9c1920ce523b0eca2bded0d
parent98677696e6f43432c05eb581dcaec292beeafaaa (diff)
parentdc109637def501b965c4c59112eb625188484b6a (diff)
Merge pull request #17 from spaghetticode/spaghetticode/tax-rate-calculator
Add tax rate calculator
-rw-r--r--.travis.yml1
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.md15
-rw-r--r--lib/super_good/solidus_taxjar.rb7
-rw-r--r--lib/super_good/solidus_taxjar/api.rb4
-rw-r--r--lib/super_good/solidus_taxjar/api_params.rb7
-rw-r--r--lib/super_good/solidus_taxjar/calculator_helper.rb44
-rw-r--r--lib/super_good/solidus_taxjar/tax_calculator.rb35
-rw-r--r--lib/super_good/solidus_taxjar/tax_rate_calculator.rb36
-rw-r--r--spec/super_good/solidus_taxjar/api_params_spec.rb18
-rw-r--r--spec/super_good/solidus_taxjar/api_spec.rb24
-rw-r--r--spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb97
12 files changed, 255 insertions, 35 deletions
diff --git a/.travis.yml b/.travis.yml
index c421d58..4598c2e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
---
+dist: trusty
sudo: false
language: ruby
cache: bundler
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a83a480..581f0d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## master
+- Added `SuperGood::SolidusTaxJar::TaxRateCalculator` for retrieving the tax rate for a given `Spree::Address`. The calculator follows `TaxCalculator` conventions by relying on address validators and custom exception handling.
+
## v0.15.2
- Add order number to param logging.
diff --git a/README.md b/README.md
index 732277f..ae71373 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,21 @@ Requirements for TaxJar integrations vary as some stores also need reporting, wh
If you're having trouble integrating this extension with your store and would like some assistance, please reach out to Jared via e-mail at [jared@super.gd](mailto:jared@super.gd) or on the official Solidus as `@jared`.
+## Features
+
+The extension provides currently two high level `calculator` classes that wrap the low-level Ruby taxjar gem API calls:
+
+* tax calculator
+* tax rate calculator
+
+### TaxCalculator
+
+`SuperGood::SolidusTaxJar::TaxCalculator` allows calculating the full tax breakdown for a given `Spree::Order`. The breakdown includes separate line items taxes and shipment taxes.
+
+### TaxRateCalculator
+
+`SuperGood::SolidusTaxJar::TaxRateCalculator` allows calculating the tax rate for a given `Spree::Address`. It relies on the same low-level Ruby TaxJar API endpoint of the tax calculator in order to provide the most coherent and reliable results. TaxJar support recommends using this endpoint for live calculations.
+
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
diff --git a/lib/super_good/solidus_taxjar.rb b/lib/super_good/solidus_taxjar.rb
index 9318181..1b1a4ac 100644
--- a/lib/super_good/solidus_taxjar.rb
+++ b/lib/super_good/solidus_taxjar.rb
@@ -5,7 +5,9 @@ require 'taxjar'
require "super_good/solidus_taxjar/version"
require "super_good/solidus_taxjar/api_params"
require "super_good/solidus_taxjar/api"
+require "super_good/solidus_taxjar/calculator_helper"
require "super_good/solidus_taxjar/tax_calculator"
+require "super_good/solidus_taxjar/tax_rate_calculator"
require "super_good/solidus_taxjar/discount_calculator"
module SuperGood
@@ -25,7 +27,10 @@ module SuperGood
end
self.cache_duration = 3.hours
- self.cache_key = ->(order) { APIParams.order_params(order).to_json }
+ self.cache_key = ->(record) {
+ record_type = record.class.name.demodulize.underscore
+ APIParams.send("#{record_type}_params", record).to_json
+ }
self.discount_calculator = ::SuperGood::SolidusTaxJar::DiscountCalculator
self.exception_handler = ->(e) {
Rails.logger.error "An error occurred while fetching TaxJar tax rates - #{e}: #{e.message}"
diff --git a/lib/super_good/solidus_taxjar/api.rb b/lib/super_good/solidus_taxjar/api.rb
index 0d182ce..601a2ae 100644
--- a/lib/super_good/solidus_taxjar/api.rb
+++ b/lib/super_good/solidus_taxjar/api.rb
@@ -22,6 +22,10 @@ module SuperGood
end
end
+ def tax_rate_for(address)
+ taxjar_client.tax_for_order(APIParams.tax_rate_address_params(address)).rate
+ end
+
def tax_rates_for(address)
taxjar_client.rates_for_location(*APIParams.address_params(address))
end
diff --git a/lib/super_good/solidus_taxjar/api_params.rb b/lib/super_good/solidus_taxjar/api_params.rb
index b8ef896..3e5bcb4 100644
--- a/lib/super_good/solidus_taxjar/api_params.rb
+++ b/lib/super_good/solidus_taxjar/api_params.rb
@@ -29,6 +29,13 @@ module SuperGood
]
end
+ def tax_rate_address_params(address)
+ {
+ amount: 100,
+ shipping: 0,
+ }.merge(order_address_params(address))
+ end
+
def transaction_params(order)
{}
.merge(customer_params(order))
diff --git a/lib/super_good/solidus_taxjar/calculator_helper.rb b/lib/super_good/solidus_taxjar/calculator_helper.rb
new file mode 100644
index 0000000..89dedc0
--- /dev/null
+++ b/lib/super_good/solidus_taxjar/calculator_helper.rb
@@ -0,0 +1,44 @@
+module SuperGood
+ module SolidusTaxJar
+ module CalculatorHelper
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def default_api
+ ::SuperGood::SolidusTaxJar::API.new
+ end
+ end
+
+ def incomplete_address?(address)
+ return true if address.is_a?(Spree::Tax::TaxLocation)
+
+ [
+ address.address1,
+ address.city,
+ address&.state&.abbr || address.state_name,
+ address.zipcode,
+ address.country.iso
+ ].any?(&:blank?)
+ end
+
+ def taxable_address?(address)
+ SuperGood::SolidusTaxJar.taxable_address_check.(address)
+ end
+
+ def cache
+ if !Rails.env.test?
+ Rails.cache.fetch(
+ cache_key,
+ expires_in: SuperGood::SolidusTaxJar.cache_duration
+ ) { yield }
+ else
+ yield
+ end
+ end
+
+ def exception_handler
+ SuperGood::SolidusTaxJar.exception_handler
+ end
+ end
+ end
+end
diff --git a/lib/super_good/solidus_taxjar/tax_calculator.rb b/lib/super_good/solidus_taxjar/tax_calculator.rb
index c7d44a9..7fac1bd 100644
--- a/lib/super_good/solidus_taxjar/tax_calculator.rb
+++ b/lib/super_good/solidus_taxjar/tax_calculator.rb
@@ -1,9 +1,7 @@
module SuperGood
module SolidusTaxJar
class TaxCalculator
- def self.default_api
- ::SuperGood::SolidusTaxJar::API.new
- end
+ include CalculatorHelper
def initialize(order, api: self.class.default_api)
@order = order
@@ -115,33 +113,14 @@ module SuperGood
Spree::TaxRate.find_by(name: "Sales Tax")
end
- def cache
- if !Rails.env.test?
- Rails.cache.fetch(
- cache_key,
- expires_in: SuperGood::SolidusTaxJar.cache_duration
- ) { yield }
- else
- yield
- end
- end
-
def cache_key
SuperGood::SolidusTaxJar.cache_key.(order)
end
- def exception_handler
- SuperGood::SolidusTaxJar.exception_handler
- end
-
def taxable_order?(order)
SuperGood::SolidusTaxJar.taxable_order_check.(order)
end
- def taxable_address?(address)
- SuperGood::SolidusTaxJar.taxable_address_check.(address)
- end
-
def shipping_tax_label(shipment, shipping_tax)
SuperGood::SolidusTaxJar.shipping_tax_label_maker.(
shipment,
@@ -152,18 +131,6 @@ module SuperGood
def line_item_tax_label(taxjar_line_item, spree_line_item)
SuperGood::SolidusTaxJar.line_item_tax_label_maker.(taxjar_line_item, spree_line_item)
end
-
- def incomplete_address?(tax_address)
- return true if tax_address.is_a?(Spree::Tax::TaxLocation)
-
- [
- tax_address.address1,
- tax_address.city,
- tax_address&.state&.abbr || tax_address.state_name,
- tax_address.zipcode,
- tax_address.country.iso
- ].any?(&:blank?)
- end
end
end
end
diff --git a/lib/super_good/solidus_taxjar/tax_rate_calculator.rb b/lib/super_good/solidus_taxjar/tax_rate_calculator.rb
new file mode 100644
index 0000000..dc3d771
--- /dev/null
+++ b/lib/super_good/solidus_taxjar/tax_rate_calculator.rb
@@ -0,0 +1,36 @@
+module SuperGood
+ module SolidusTaxJar
+ class TaxRateCalculator
+ include CalculatorHelper
+ def initialize(address, api: self.class.default_api)
+ @address = address
+ @api = api
+ end
+
+ def calculate
+ return no_rate if SuperGood::SolidusTaxJar.test_mode
+ return no_rate if incomplete_address?(address)
+ return no_rate unless taxable_address?(address)
+ cache do
+ api.tax_rate_for(address).to_d
+ end
+
+ rescue StandardError => e
+ exception_handler.(e)
+ no_rate
+ end
+
+ private
+
+ attr_reader :address, :api
+
+ def no_rate
+ BigDecimal(0)
+ end
+
+ def cache_key
+ SuperGood::SolidusTaxJar.cache_key.(address)
+ end
+ end
+ end
+end
diff --git a/spec/super_good/solidus_taxjar/api_params_spec.rb b/spec/super_good/solidus_taxjar/api_params_spec.rb
index 3a55655..e0a8766 100644
--- a/spec/super_good/solidus_taxjar/api_params_spec.rb
+++ b/spec/super_good/solidus_taxjar/api_params_spec.rb
@@ -183,6 +183,24 @@ RSpec.describe SuperGood::SolidusTaxJar::APIParams do
end
end
+ describe "#tax_rate_address_params" do
+ subject { described_class.tax_rate_address_params(ship_address) }
+
+ it "returns params for fetching the tax rate for that address" do
+ expect(subject).to eq(
+ {
+ amount: 100,
+ shipping: 0,
+ to_city: "Los Angeles",
+ to_country: "US",
+ to_state: "CA",
+ to_street: "475 N Beverly Dr",
+ to_zip: "90210"
+ }
+ )
+ end
+ end
+
describe "#transaction_params" do
subject { described_class.transaction_params(order) }
diff --git a/spec/super_good/solidus_taxjar/api_spec.rb b/spec/super_good/solidus_taxjar/api_spec.rb
index 0f890ca..8b0a5db 100644
--- a/spec/super_good/solidus_taxjar/api_spec.rb
+++ b/spec/super_good/solidus_taxjar/api_spec.rb
@@ -23,6 +23,30 @@ RSpec.describe SuperGood::SolidusTaxJar::API do
it { is_expected.to eq({ some_kind_of: "response" }) }
end
+ describe "tax_rate_for" do
+ subject { api.tax_rate_for address }
+
+ let(:api) { described_class.new(taxjar_client: dummy_client) }
+ let(:dummy_client) { instance_double ::Taxjar::Client }
+ let(:address) { Spree::Address.new }
+ let(:tax_rate) { 0.04 }
+ let(:response) { double(rate: tax_rate) }
+
+ before do
+ allow(SuperGood::SolidusTaxJar::APIParams)
+ .to receive(:tax_rate_address_params)
+ .with(address)
+ .and_return({ address: "params" })
+
+ allow(dummy_client)
+ .to receive(:tax_for_order)
+ .with({ address: "params" })
+ .and_return(response)
+ end
+
+ it { is_expected.to eq(tax_rate) }
+ end
+
describe "#tax_rates_for" do
subject { api.tax_rates_for address }
diff --git a/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb b/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb
new file mode 100644
index 0000000..9a4e2b8
--- /dev/null
+++ b/spec/super_good/solidus_taxjar/tax_rate_calculator_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+RSpec.describe ::SuperGood::SolidusTaxJar::TaxRateCalculator do
+ describe "#calculate" do
+ subject { calculator.calculate }
+
+ let(:calculator) { described_class.new(address, api: dummy_api) }
+
+ let(:dummy_api) do
+ instance_double ::SuperGood::SolidusTaxJar::API
+ end
+
+ let(:dummy_tax_rate) { BigDecimal(0) }
+
+ let(:incomplete_address) do
+ ::Spree::Address.new(
+ first_name: "Ronnie James",
+ zipcode: nil,
+ address1: nil,
+ city: "Beverly Hills",
+ state_name: "California",
+ country: ::Spree::Country.new(iso: "US")
+ )
+ end
+
+ let(:complete_address) do
+ incomplete_address.tap do |address|
+ address.zipcode = "90210"
+ address.address1 = "9900 Wilshire Blvd"
+ end
+ end
+
+ shared_examples "returns the dummy tax rate" do
+ it { expect(subject).to eq dummy_tax_rate }
+ end
+
+ context "when the address is not complete" do
+ let(:address) { incomplete_address }
+
+ it_behaves_like "returns the dummy tax rate"
+ end
+
+ context "when the address is complete" do
+ let(:address) { complete_address }
+
+ context "when the address is not taxable" do
+ before do
+ allow(SuperGood::SolidusTaxJar.taxable_address_check)
+ .to receive(:call).with(address)
+ .and_return(false)
+ end
+
+ it_behaves_like "returns the dummy tax rate"
+ end
+
+ context "when the address is taxable" do
+ let(:tax_rate) { 0.03 }
+
+ before do
+ allow(dummy_api).to receive(:tax_rate_for) { tax_rate }
+ end
+
+ it "returns the expected tax rate" do
+ expect(subject).to eq tax_rate
+ end
+ end
+ end
+
+ context "when the API encounters an error" do
+ let(:address) { complete_address }
+
+ before do
+ allow(dummy_api).to receive(:tax_rate_for).and_raise("A bad thing happened.")
+ end
+
+ it "calls the configured error handler" do
+ expect(SuperGood::SolidusTaxJar.exception_handler).to receive(:call) do |e|
+ expect(e).to be_a StandardError
+ expect(e.message).to eq "A bad thing happened."
+ end
+
+ subject
+ end
+
+ it_behaves_like "returns the dummy tax rate"
+ end
+
+ context "when test_mode is set" do
+ let(:address) { complete_address }
+
+ before { SuperGood::SolidusTaxJar.test_mode = true }
+ after { SuperGood::SolidusTaxJar.test_mode = false }
+
+ it_behaves_like "returns the dummy tax rate"
+ end
+ end
+end