We’re supposed to have our own shop here, but I’ve been putting it off for months. I’ve worked with Salor (Python), MedusaJS (Node), Sylius (PHP), and Vendure (Node). These are all next generation headless eCommerce backends that will let us customize like crazy.
The goal is to have one place to “showcase” all the Bus Pirate stuff and have relevant links to local distributors as well.
Another goal is to use the latest “special lines” that ship fast and dump off with the local post service (think aliexpress) with the VAT prepaid where applicable (EU, UK, MX). DirtyPCBs supports one VAT special line to germany, and we have to bill VAT manually.
I’ve chosen MedusaJS because it seems the most popular so problems I have will probably be documented somewhere.
The first step is a Python script to interact with MedusaJS because the admin backend is super tedious.
Medusa has a concept of regions that don’t really work for us because we ship world wide. It would create a separate region for each VAT rate and shipping method, plus a “world wide” category. It all gets a bit tedious, so let’s customize.
headers = {
"Accept": "application/json; charset=utf-8",
"X-API-KEY": public_key
}
url_vat = url+"?limit=100"
response = requests.get(url_vat, headers=headers)
self.http_error(response)
# loop, print country name, country code, standard rate
rates = response.json().get('rates', [])
for rate in rates:
print(f"{{ country_name: \"{rate['country_name']}\", country_code: \"{rate['country_code']}\", rate: {rate['standard_rate']/100} }},")
First let’s pull a list of EU VAT rates from vatstack api using a bit of python to print a typescript object (yuck I know, but gotta start somewhere).
import {
ITaxCalculationStrategy,
LineItem,
LineItemTaxLine,
ShippingMethodTaxLine,
TaxCalculationContext,
} from "@medusajs/medusa";
class TaxCalculationStrategy implements ITaxCalculationStrategy {
async calculate(
items: LineItem[],
taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
calculationContext: TaxCalculationContext
): Promise<number> {
const { shipping_address } = calculationContext;
const { shipping_methods } = calculationContext;
console.log("tax strategy override");
console.log(shipping_address.country_code);
const prepaid_taxes: { country_name: string; country_code: string; rate: number; }[] = [
{ country_name: "Austria", country_code: "AT", rate: 0.2 },
{ country_name: "Belgium", country_code: "BE", rate: 0.21 },
{ country_name: "Bulgaria", country_code: "BG", rate: 0.2 },
{ country_name: "Croatia", country_code: "HR", rate: 0.25 },
{ country_name: "Cyprus", country_code: "CY", rate: 0.19 },
{ country_name: "Czech Republic", country_code: "CZ", rate: 0.21 },
{ country_name: "Denmark", country_code: "DK", rate: 0.25 },
{ country_name: "Estonia", country_code: "EE", rate: 0.22 },
{ country_name: "Finland", country_code: "FI", rate: 0.255 },
{ country_name: "France", country_code: "FR", rate: 0.2 },
{ country_name: "Germany", country_code: "DE", rate: 0.19 },
{ country_name: "Greece", country_code: "GR", rate: 0.24 },
{ country_name: "Hungary", country_code: "HU", rate: 0.27 },
{ country_name: "Ireland", country_code: "IE", rate: 0.23 },
{ country_name: "Italy", country_code: "IT", rate: 0.22 },
{ country_name: "Latvia", country_code: "LV", rate: 0.21 },
{ country_name: "Lithuania", country_code: "LT", rate: 0.21 },
{ country_name: "Luxembourg", country_code: "LU", rate: 0.17 },
{ country_name: "Malta", country_code: "MT", rate: 0.18 },
{ country_name: "Netherlands", country_code: "NL", rate: 0.21 },
{ country_name: "Poland", country_code: "PL", rate: 0.23 },
{ country_name: "Portugal", country_code: "PT", rate: 0.23 },
{ country_name: "Romania", country_code: "RO", rate: 0.19 },
{ country_name: "Slovakia", country_code: "SK", rate: 0.2 },
{ country_name: "Slovenia", country_code: "SI", rate: 0.22 },
{ country_name: "Spain", country_code: "ES", rate: 0.21 },
{ country_name: "Sweden", country_code: "SE", rate: 0.25 },
];
const rate = prepaid_taxes.find((tax) => tax.country_code.toUpperCase() === shipping_address.country_code.toUpperCase())?.rate || 0;
console.log(rate);
if(rate==0) return 0;
var cart_total = 0;
for (const item of items) {
cart_total += (item.unit_price*item.quantity);
}
for (const shipping_method of shipping_methods) {
cart_total += shipping_method.price;
}
return Math.round(cart_total * rate);
}
}
export default TaxCalculationStrategy;
tax-calculation.ts contains our “tax calculation strategy”. This overrides the default per-region tax setting. The important bit is logistics only allows prepaid VAT for certain countries. If a country is on the list, we get the VAT rate, total up the cost, and return the tax amount. Now we’re handing VAT independent of regionalized shops.
Next up, shipping API integration.