Blog
0 pixels scrolled
  • Home
  • Work
  • Services
  • About
  • Blog
  • Contact
Making Sense of Taxes in Solidus
Adam Mueller on April 04, 2023
Making Sense of Taxes in Solidus
Adam Mueller on April 04, 2023
Posted in Solidus
← Back to the blog

At first glance, the tax system in Solidus can seem complicated. I know I struggled for a long time to understand how all of the pieces fit together. This blog post will help shed some light on the key components of the tax system in Solidus, its shortcomings, and how best to integrate with it to ensure your store is tax compliant.

How does the built in calculator work?

There are several “layers” to tax calculation in Solidus. Fortunately, unlike promotions, tax adjustments are always calculated in the same way. It starts in Spree::OrderUpdater#update. Since taxes are stored as Spree::Adjustment records in Solidus, their calculation happens at the same time as all of adjustments on an order.

The Spree::OrderUpdater delegates recalculating the taxes to our first configurable layer: Spree::Config.tax_adjuster_class. Swapping out the tax adjuster is unlikely to be something most stores will need to do, but it allows you full control over the entire process. The built-in adjuster is quite simple, and introduces the next layer that can be configured:

taxes = Spree::Config.tax_calculator_class.new(order).calculate
Spree::OrderTaxation.new(order).apply(taxes)

What’s up with all of these calculators?

This might be a good point for brief interlude into some of the historical context for all of Solidus’ many different types of tax calculators. Several years ago, while working on updating a store’s integration with Avalara, we realized that the integration point for taxes in Solidus was not sufficient for what we needed to do. At that point in time, the only way to modify how taxes were calculated was to provide your own Spree::Calculator::DefaultTax class and assign it to the Spree::TaxRate record. Unfortunately, those calculators only have knowledge of a single line item or shipment. They’re great if you’re looking to calculate taxes based on the cost of something in isolation but that’s not how most third-party tax services work. They all want to be able to calculate taxes for an entire order. Which meant a lot of our earlier integrations had to make duplicate API calls or use a lot of caching to avoid that problem.

This lead to me introducing Spree::TaxCalculator::Default. It provides an integration point at a higher level. Instead of only calculating tax for a single item, this calculator is responsible for calculating the tax on the entire order. I still regret the unfortunate naming to this day, but I haven’t been able to come up with anything better and it’s too late now. Using this new integration point, it’s now much easier to swap out the entirety of Solidus’ tax calculations and use a third-party service to help manage them.

So to summarize, calculators in Spree::Calculator generally operate on a single item/shipment and those in Spree::TaxCalculator work with the entire order at once.

And now back to our regularly scheduled programming!

As mentioned in the interlude, swapping out the tax calculator class is what you’ll most likely want to do if you’re looking to integrate with a third-party service. For now, let’s dive into the provided default to see where things go from there.

Spree::TaxCalculator::Default delegates the computation of taxes to Spree::TaxRate records. For every line item and shipment in the order, it will search for all applicable rates. There are three conditions a rate needs to meet in order to be applicable for an item:

  • Location: Each tax rate can have multiple zones that it applies to. A zone is either a collection of countries or states/provinces.
  • Category: Tax categories can be used to help distinguish between rates that apply to specific items. As an example, some clothing is exempt from New York state’s sales tax.
  • Active: A simple boolean flag on rates.

Once we know which rates apply to an item, we compute how much tax should be applied to it. This is where that second type of calculator mentioned earlier comes in. Solidus has two built-in calculators: Spree::Calculator::DefaultTax and Spree::Calculator::FlatFee.

That first calculator will compute taxes using a percentage based rate and is likely what most people would think of when they think of calculating taxes. It has two different formulas based on the type of tax being applied: additional or included. The second calculator is meant to help capture tax-like fees for orders, that are often just a flat amount and not based on a percentage of the item’s total. We’ll talk more about it down below.

What’s the difference between additional and included tax?

Value-added tax (VAT) is a flat tax levied on goods. It is very similar to sales tax, with the key distinction being that sales tax is only collected once: at the initial point of sale. VAT on the other hand is collected multiple times by multiple different parties, each responsible for remitting the tax to the government. When working with Solidus, we often use the term VAT instead to refer to taxes that are included in the price of goods because that’s how VAT is commonly applied in Europe.

# To calculate how much tax is included on an item based on a rate:
(cost_of_item * tax_rate) / (1 + tax_rate)

However, included taxes are not unique to Europe or VAT. For example, Canada has a 5% goods and services tax (GST) across the country which is almost always calculated at the point of purchase and considered an additional tax. However, items purchased at arenas will often have the tax included in the price to help make paying with cash easier and in those cases are calculated as an included tax. Essentially, the decision of using an included versus an additional tax comes down to what’s typically expected in the region you’re selling.

What’s a flat fee?

Recently, we’ve come across new types of taxes collected by some states that don’t fall under what we would usually consider a VAT or sales tax. Of note, Colorado introduced a retail delivery fee that must be collected for any delivery made to an address within Colorado that contains at least one tangible good. Because the delivery fee is a flat amount that is applied to an order, and shares many similarities to a tax, it is modeled as such in Solidus.

Flat fees in Solidus could be used to help capture other common fees on items where it’s important to you to track the amount separately from the base cost of the item. As an example, British Columbia’s Environmental Handling Fee (EHF) is a flat amount that is applied on the sale of all new electronic products in the province. Fees do often have some key distinctions from taxes. (Notably, they’re often non-refundable.) As such, it’s possible that fees will be pulled out into their own separate entity in Solidus eventually, but there’s nothing currently on the roadmap to do that work.

Extension points for customizing tax calculations

Now that we’ve got a better understanding for how all of the tax code in Solidus works, let’s take a look at why we would want to modify it. In all of the years I’ve worked on Solidus projects, the only time I’ve noticed the need for customizations to the tax system in Solidus is when working with stores that ship to/from the USA. Granted the large majority of the stores I’ve worked with have been primarily focused on Western markets, so maybe there are other countries out there with tax systems nearing the complexity of the USA that I’m blissfully unaware of. The majority of the complexity in the US stems from sales taxes varying from for each state, or in some cases each county. The taxes also often have differing rules that apply based on the amount being sold, where the warehouse the item was shipped from is located, where the item was manufactured, where the company is headquatered, and so on. Dealing with all of these rules could be a full-time job so most stores opt to use a third-party service to help.

The three most common services are: Avalara, TaxJar, and TaxCloud. And luckily for us there are already extensions available for all three:

Work ServicesAboutBlogCareersContact


Privacy policyTerms and conditions© 2023 Super Good Software Inc. All rights reserved.