The day you’ve been dreading is finally here. You’ve put it off as long as you could, but alas! It is now time…to integrate sales tax into your payment processing flow.
Thanks to the landmark South Dakota v. Wayfair Supreme Court case in 2018, online businesses may now be liable for collecting sales tax. Luckily, there are several vendors dedicated to helping online merchants deal with this new requirement.
At The Lifetime Value Company, we partnered with TaxJar to assist with fulfilling our sales tax obligations, which has been a big help. In this post, we’ll walk through how we implemented TaxJar’s integration to get the job done. Even when working with a vendor there’s still much to do.
Where do we start? The first step was to break this project down into two high-level objectives: first to understand the scope, and then to map the scope to an implementation plan.
Table of Content
Part 1: Understanding the Scope
A key concept when dealing with sales tax is “nexus.”
“Sales tax nexus occurs when your business has some kind of connection to a state. All states have a slightly different definition of nexus, but most of the time states consider that a “physical presence” or “economic connection” creates nexus.”
~ Mark Faggiano from TaxJar
Online businesses get more complicated than brick and mortar stores very quickly since you’re now introducing the concept of “doing business” with consumers across state lines. When scoping out how to integrate sales tax into our application, we first wanted to identify our nexus obligations before moving onto finer details.
Part 1.1: Determining if we have nexus for a given transaction
First, we engaged our accounting/finance department to determine our nexus obligations. Once we had a list of states where we had nexus, we used TaxJar to enable them for sales tax collection in their account management settings.
Part 1.2: Obtaining the correct sales tax rate for a given transaction
Once we configured the nexus states in TaxJar, we simply used their Taxes API to request a sales tax rate for a given location:
Docs: https://developers.taxjar.com/api/reference/#taxes
Please note, not all payment transactions require billing location data. If you are not already doing so, you may need to modify your application to collect it. This requirement is application and product-specific and beyond the scope of this article.
We also used their Validations API to ensure that the billing location data we’re receiving is valid. Good billing location data hygiene will become important in the next step: logging transactions.
Part 1.3: Logging the transaction for bookkeeping purposes
Logging transactions where sales tax is collected lets us square up the transaction data we send to TaxJar with our own backend figures, which helps with financial reporting. Another benefit is since some states will only start requiring sales tax collection once you surpass certain revenue thresholds, TaxJar can adjust the rates you’ll be required to collect depending on if you’ve met the threshold per state. To take advantage of this, you can send all transactions to TaxJar, and not just ones where sales tax is collected.
We used their Transactions API to log transaction data with TaxJar.
We made sure to also log refunds since we count them as part of our total revenue per state when considering the revenue thresholds.
Part 2: Mapping Scope to Implementation
With a plan in place for the sales tax side of things, it was time to put it into action. But before we did that, we needed to evaluate our current application to prep it for integration.
Part 2.1: Identify Points of Entry
By points of entry, I’m referring to any point in the payments flow where we needed to add support for sales tax. At The Lifetime Value Company, our main offering is an online subscription product, with support for individual purchases.
For our integration, we identified three points of entry where we’d need to integrate sales tax support:
- Signup page
- User purchases
- Subscription renewals
Then, we broke out each point of entry into their specific requirements.
Signup page: The page where a prospective user creates an account and makes their first payment. When checking out, we want to be able to show them the estimated amount of sales tax they can expect to pay based on their payment information.
- Requirement: Collect billing location information and display an estimated sales tax rate, if applicable.
- Infrastructure: Frontend.
Signup processing: This is the part of our backend infrastructure that takes in a signup request and puts everything together to process the initial payment and create all the necessary records for a new user.
- Requirement: Process the first subscription payment with sales tax, if applicable.
- Infrastructure: Backend.
User purchases: Purchases made by users who have already signed up for an account and who are using the site to make an additional purchase.
- Requirement: Use previously-stored billing location information and display an estimated sales tax rate, if applicable.
- Infrastructure: Frontend and backend.
Payment processing: This is the part of our backend infrastructure that takes a purchase request from an existing user, processes the payment, and enables the feature or upgrade that the user has just purchased.
- Requirement: Use previously-stored billing location information and process the subscription payment with sales tax, if applicable.
- Infrastructure: Backend.
Subscription renewals: Renewals happen regularly for individual customers based on plan duration. We use a process to collect all subscriptions due each day.
- Requirement: Process the subscription payment with sales tax, if applicable.
- Infrastructure: Backend.
In our case, we have a few different responsibilities split across front and backend applications. The more we fleshed out the requirements, the more obvious it became that the heart of our sales tax processing would be best served by living in a separate microservice.
Part 2.2: Optimize sales tax processing for efficiency
Centralizing our sales tax operations in a microservice had some added benefits:
- The frontend clients could request tax rates.
- The backend application would be able to process signups and one-off purchases.
- Our daily subscription renewal process could go directly to the microservice to fetch updated rate data as needed.
- We could run this microservice in a very efficient and independent manner.
- Since this was the sole responsibility of the microservice and the single interface to sales tax for our entire system, we could switch up the sales tax vendors and we’d only need to update integrations in this one microservice without affecting the rest of our system.
We used our microservice to expose two API endpoints:
-
/rates
, which returns the most accurate rate we can obtain based on the billing location data -
/transactions
, which we use to log transactions with TaxJar
Since the frontend clients were only concerned with displaying estimated rates before a purchase, the /rates
API is the only endpoint they need to integrate.
Part 2.3: Integrate with the existing billing infrastructure
At this point, we had a vendor, a microservice and a plan of action. The last piece of the puzzle was to ensure that our existing backend infrastructure could adapt to the sales tax requirements.
While this largely depends on specific business needs, these were the tricky situations facing us:
- Supporting existing non-sales taxed users
- Developing custom rules for certain states/locations
- Determining how to save a rate for a user
- Determining when to update an existing rate
Supporting existing non-sales taxed users was an early problem. Once we flipped the switch to begin collecting sales tax, we didn’t want to also start charging users who had signed up prior to our sales tax implementation. We wanted to test it first, both to make sure it was working properly, and also to not suddenly introduce new charges to existing users when renewing their subscriptions.
We decided to create a simple table in the database to track if a user had sales tax enabled or not. It looks something like this:
sales_tax_provisions | |
---|---|
user_id | Integer |
sales_tax_provisioned | Boolean |
When a user signs up and is eligible for sales tax, we create one of these records and set sales_tax_provisioned
to true
, effectively forcing them into sales tax processing. Any preexisting users won’t have any associated sales_tax_provision
records, and our logic will skip them when processing sales tax.
As a side benefit, this also gave us greater control over individual users’ sales tax behavior since we can now flip the sales_tax_provisioned
boolean to false
, should we want to disable it for a user.
Developing custom rules for certain states/locations was something we faced after our sales tax integration had been live for a few months. Because laws concerning a sales tax for online businesses are so new, each state has different requirements for collecting sales tax. However, due to how your business may be classified in some states, you may only be required to collect a lower statewide rate instead of locality-specific rates.
To handle this, we developed additional logic in the microservice to bypass the API call to TaxJar when processing transactions from specific states, and return the override rate instead. This was a nice benefit from centralizing all of our sales tax processing logic in one place.
Determining how to save a rate for a user opened up another line of discussion on how to define the location for a specific user. We identified two options to determine the tax rate for a user viz., a) tie a user’s sales tax setting to location data associated with the user themselves, or b) tie the rate to location data for a specific payment method they use. However your company defines this, the key thing is to identify early on the approach that works for you.
With this decided, we devised our solution to store the rate information in a new table with a schema that looked like this:
sales_tax_configurations | |
---|---|
user_id | Integer |
tax_rate | Decimal |
tax_eligible | Boolean |
The tax_rate
field is self-explanatory, and we store this in a decimal format for greater accuracy over floats. The tax_eligible
boolean is another way to short-circuit the sales tax processing logic if a user is coming from a state where we don’t need to collect tax, but we have created a sales_tax_configuration
for them anyway.
Determining when to update the existing rate ties into the above point. Continuing on that thread, if a user’s sales_tax_configuration
is recent enough and they are eligible for sales tax, then we can use the existing rate we have on file without going out to TaxJar. To determine if a sales tax rate is recent enough, we look at the date in the table’s updated_at
timestamp.
It’s wise to get fresh updates each month since that’s when rate changes go into effect, but rate information from the current month is generally safe to use and saves us an API call in the process.
Conclusion: Make It Your Own
It’s highly likely that your business has specific needs that’ll require custom solutions to properly bring the wonder and glory that is sales tax into your payments flow. Hopefully, these high-level ideas of how to approach things (and some hidden complexity to think about) will help make your journey to sales tax a little bit easier.
As you can imagine, we work with substantial amounts of data and solve difficult large-scale problems. If this seems like an environment that aligns with your interests, check out our careers page and reach out. We would love to chat!
Top comments (0)