Featured image

About the integration

The Medusa TaxJar Integration connects Medusa with TaxJar, a popular tool for automating sales tax calculations, making tax management simple and efficient.

This integration helps developers save time by automating tax calculations for products and shipping, so there’s no need for manual work or extra tools. With TaxJar’s powerful API, you can manage taxes for different regions right from your Medusa backend.

In this guide, we’ll show you how to set it up and use its main features to handle taxes easily in your e-commerce project.

Key features of TaxJar

  • Automated tax calculations: Use TaxJar’s powerful API to automate tax calculations for transactions, without manual intervention.

  • Product tax codes: Assign and manage tax codes specific to your products, allowing for accurate tax calculation tailored to diverse product categories.

  • Real-time tax updates: Stay compliant effortlessly by syncing your tax data with TaxJar’s regularly updated database of tax rules and rates.

Getting started with TaxJar integration

1. Set up your TaxJar account

The integration begins with creating an account on the TaxJar.com platform. From there, there are two important steps

First, you should obtain your API key linked to the account. Save the live token to use in the Medusa application.

taxjar1.png

Second, you have to enter the business and nexus details in the account configuration. That information will be used in tax calculations.

taxjar2.png
taxjar3.png

2. Extend the ProductCategory model

TaxJar uses a tax code to perform calculations. Each product type have different code. That means, the provider needs to know them before sending a calculation request.

Let’s assume that we want to keep all products of the same tax code in one category. That means that we have to extend the ProductCategory model by a new property called tax_code. Let’s add as well a code name and description to keep things easy. Our extension model should look like this:

import { model } from "@medusajs/utils";

const TaxCode = model.define("tax_code", {
 id: model.id().primaryKey(),
 name: model.text().default(""),
 description: model.text().default(""),
 code: model.text().unique(),
});

export default TaxCode;

Next, let’s link the ProductCategory to newly created model. We do that by creating a file in the links folder of the Medusa application. We define the link like this:

import { defineLink } from "@medusajs/framework/utils";
import ProductModule from "@medusajs/medusa/product";
import Taxcode from "../modules/taxcode";

export default defineLink(
 {
   linkable: ProductModule.linkable.productCategory,
   isList: true,
 },
 Taxcode.linkable.taxCode
);

Register the Taxcode module in the medusa-config.ts file, generate migration and run it.

The tax_code table will be created, but to actually create a relation between tax_code and ProductCategory a sync links command needs to be executed.

npx medusa db:sync-links

After we have created necessary tables in the database, we should seed tax_code with actual values.

We can pull them from the TaxJar API by performing a GET request to https://api.taxjar.com/v2/categories, and attaching our API token as an Authorization header.

Then, insert them into the database, for example like in this script.

export default async function seedTaxCodes({ container }: ExecArgs) {
 const taxjarClient = new TaxJarClient(
   process.env.TAXJAR_API_KEY
 );


 const taxCodeService = container.resolve("Taxcode") as TaxCodeService;
 const entries = await taxjarClient.getTaxCategories();


 await taxCodeService.createTaxCodes(
   entries.map((e) => {
     return {
       name: e.name,
       description: e.description,
       code: e.product_tax_code,
     };
   })
 );
 logger.log(`Created ${entries.length} entries!`);
}

4. Create a custom TaxProvider

Having a link between product-product_category-tax_code we can go on and implement a custom TaxProvider class.

By default, Medusa uses the built-in system tax provider, which simply proceeds with the tax rates entered by the application administrator. In order to use TaxJar calculations, we have to create a custom one.

To do this, we are going to create a separate module, and a class that implements interface ITaxProvider from the @medusajs/framework package.

It specifies two methods that have to be implemented:

  • getIdentifier(): Return an unique string that indicates the provider in the system.

  • getTaxLines(): Match the appropriate tax rate to each of the items in the cart.

It takes following arguments:

  • itemLines: TaxTypes.ItemTaxCalculationLine[] - all items stored in the cart,

  • shippingLines: TaxTypes.ShippingTaxCalculationLine[] - shipping information,

  • context: TaxTypes.TaxCalculationContext - buyer information.

So, having a product_id of each itemLine we can query the database in order to retrieve tax_codes used in the calculations.

Let’s perform ask TaxJar API for calculations. The request format is:

POST <https://api.taxjar.com/v2/taxes>

Authorization: Bearer <api token>

{ 
to_country: string; 
to_zip: string; 
to_state: string; 
to_city: string; 
to_street: string; 
shipping: number; 
line_items: { 
id: string; 
quantity: number; 
unit_price: number; 
discount: number; 
product_tax_code: string; 
}
}

In return we got total tax amount to charge, as well as tax rates for each item. We can use them to feed the TaxProvider.

Please note, that Medusa uses tax rates as percentage values, while TaxJar returns them in fraction format, so each value has to be multiplied by a hundred.

5. Register the Tax Provider

The newly created TaxJarProvider has to be registered in the medusa-config.ts file in order to be recognized by Medusa.js:

{ 
resolve: "@medusajs/medusa/tax", 
options: { 
providers: [ { 
resolve: "./src/modules/taxjar", 
id: "tax-jar-provider", 
options: { apiKey: process.env.TAXJAR_API_KEY }, 
} ],
}, 
}

After finishing with the code, we need to save the provider information in the database.

In table tax-provider we need to create a record.

taxjar4.png

And link the US tax region to the tax-jar-provider in table tax-region:

taxjar5.png

The provider is now ready to go.

6. Admin configuration

We should also create an admin interface to assign tax code to product category.

Let’s create a widget in admin/widgets directory and attach it to zone product_category.details.after. That means the widget will be displayed in the category section, right after details widget.

export const config = defineWidgetConfig({
 zone: "product_category.details.after",
});

Let’s also create an GET endpoint in api/admin/category/[categoryId]/taxcode catalog, that returns tax code details assigned to categoryId.

In order to fetch tax code details we use useEffect hook in the widget:

 useEffect(() => {
   axios
     .get(`/admin/category/${props.data.id}/taxcode`, {
       withCredentials: true,
     })
     .then((res) => res.data)
     .then((res) => {
       if (res.tax_code) {
         setTaxCode(res.tax_code);
       } else {
         setTaxCode({});
       }
     });
 }, []);

Having tax code fetched, we can display it, for example, like this.

taxjar6.png

Also, we should create a way to assign the code to the category.

Let’s create another endpoint, this time POST, that will accept both categoryId and taxCode as parameters, and create a link between them.

Wrapping up

Congratulations! Your configuration is now complete, and you can start automate tax calculations and simplify tax management.

By leveraging Medusa.js's modular architecture and TaxJar's powerful API, you can focus more on building unique commerce experiences and less on navigating tax complexities.

You can check out the GitHub repository here.

Transform your online store with our expertise in Medusa

Let's talk about your project

Other blog posts

Mercur Marketplace Leap to Medusa 2.0 and More

Mercur migrated to Medusa 2.0, introducing cleaner architecture, advanced seller management, and essential features like order splitting...

How to Get Started with Mercur Marketplace?

We'll show you how to get started with Mercur - Medusa MultiVendor Marketplace Accelerator. By the end of this video, you'll have a fully functional marketplace with both admin and vendor panels along with their features...

Tell us about your project

Got a project in mind? Let's make it happen!

By clicking “Send Message” you grant us, i.e., Rigby, consent for email marketing of our services as part of the communication regarding your project. You may withdraw your consent, for example via hello@rigbyjs.com.
More information
placeholder

Grzegorz Tomaka

Co-CEO & Co-founder

LinkedIn icon
placeholder

Jakub Zbaski

Co-CEO & Co-founder

LinkedIn icon