GithubHelp home page GithubHelp logo

valeriansaliou / node-sales-tax Goto Github PK

View Code? Open in Web Editor NEW
296.0 11.0 45.0 254 KB

:moneybag: International sales tax calculator for Node (offline, but provides optional online VAT number fraud check). Tax rates are kept up-to-date.

Home Page: https://www.npmjs.com/package/sales-tax

License: MIT License

JavaScript 100.00%
sales tax salestax billing invoice vat gst vatmoss

node-sales-tax's Introduction

node-sales-tax

Test and Build Build and Release NPM Downloads Buy Me A Coffee

International sales tax calculator for Node (offline, but provides optional online VAT number fraud check). Tax rates are kept up-to-date.

You may use it to calculate VAT rates for countries in the European Union (VAT MOSS), GST in Canada, or get VAT for countries such as China, or even Hong Kong (which has no VAT).

International tax is hard (especially VAT). This library ensures rules are enforced in the code. If you see a rule that is missing or not correctly enforced, please open an issue. Also, when you use the library, make sure to specify your origin country; as it will return full international tax rates if you don't specify it (ie. the country you invoice your customers from).

You can find the raw sales tax rates JSON file here: sales_tax_rates.json

πŸ‡ΊπŸ‡Έ Crafted in Portland, Maine, USA.

Who uses it?

Crisp Locize TurnShift Tally

πŸ‘‹ You use sales-tax and you want to be listed there? Contact me.

Last changes

The version history can be found in the CHANGELOG.md file.

As this library is SemVer-compatible, any breaking change would be released as a MAJOR version only. Non-breaking changes and features are released as MINOR. Tax rate updates and bug fixes are released as PATCH (note that tax rate updates may as well be bundled under a MINOR release, if it comes with new features or minor changes).

How to install?

Include sales-tax in your package.json dependencies:

npm install --save sales-tax

If you are using TypeScript, type definitions are automatically imported.

How to use?

This module may be used to acquire the billable VAT percentage for a given customer. You may also use it directly to process the total amount including VAT you should bill; and even to validate a customer's VAT number.

πŸ”΄ Important: in order to fetch the sales tax for a customer, you need to know their country (and sometimes state). The country (sometimes state) must be passed to all module methods, formatted as ISO ALPHA-2 (eg. France is FR, United States is US).

➑️ Import the module

Import the module in your code:

var SalesTax = require("sales-tax");

Ensure that you specify your origin country before you use the library. This will affect how worldwide, regional and national area taxes are handled from your point of view (regional stands for the economic community, eg. the European Union).

Also, ensure that you consume correctly the charge values that get returned. It tells you if the VAT charge should be directly invoiced to the customer via the direct tag (you charge the VAT on your end), or if the customer should pay the VAT on their end via the reverse tag (see VAT reverse charge). If the charge is not direct, then the VAT rate will be 0.00 (it is up to the customer to apply their own VAT rate).

βœ… Specify the country you charge from

Prototype: SalesTax.setTaxOriginCountry(countryCode<string>, useRegionalTax<boolean?>)<undefined>

πŸ‡«πŸ‡· Charge customers from France if liable to VAT MOSS (thus worldwide, regional and national VAT gets calculated from a French point of view):

SalesTax.setTaxOriginCountry("FR")

πŸ‡«πŸ‡· Charge customers from France if not liable to VAT MOSS (thus worldwide, regional and national VAT gets calculated from a French point of view):

// Set the 'useRegionalTax' argument to false if not liable to VAT MOSS (eg. not enough turnover in another regional country)
SalesTax.setTaxOriginCountry("FR", false)

🚩 Unset your origin country (use default origin, full VAT rates will be applied for all countries worldwide β€” this is obviously not usable for your invoices):

SalesTax.setTaxOriginCountry(null)

βœ… Check if a country has sales tax

Prototype: SalesTax.hasSalesTax(countryCode<string>)<boolean>

Notice: this method is origin-neutral. It means it return values regardless of your configured tax origin country.

Check some countries for sales tax (returns true or false):

var franceHasSalesTax = SalesTax.hasSalesTax("FR")  // franceHasSalesTax === true
var brazilHasSalesTax = SalesTax.hasSalesTax("BR")  // brazilHasSalesTax === true
var hongKongHasSalesTax = SalesTax.hasSalesTax("HK")  // hongKongHasSalesTax === false

βœ… Check if a state has sales tax (in a country)

Prototype: SalesTax.hasStateSalesTax(countryCode<string>, stateCode<string>)<boolean>

Notice: this method is origin-neutral. It means it return values regardless of your configured tax origin country.

πŸ‡¨πŸ‡¦ Check some Canada states for sales tax (returns true or false):

var canadaQuebecHasSalesTax = SalesTax.hasStateSalesTax("CA", "QC")  // canadaQuebecHasSalesTax === true
var canadaYukonHasSalesTax = SalesTax.hasStateSalesTax("CA", "YT")  // canadaYukonHasSalesTax === false

πŸ‡ΊπŸ‡Έ Check some US states for sales tax (returns true or false):

var unitedStatesCaliforniaHasSalesTax = SalesTax.hasStateSalesTax("US", "CA")  // unitedStatesCaliforniaHasSalesTax === true
var unitedStatesDelawareHasSalesTax = SalesTax.hasStateSalesTax("US", "DE")  // unitedStatesDelawareHasSalesTax === false

βœ… Get the sales tax for a customer

Prototype: SalesTax.getSalesTax(countryCode<string>, stateCode<string?>, taxNumber<string?>)<Promise<object>>

Notice: this method is origin-aware. It means it return values relative to your configured tax origin country.

πŸ‡«πŸ‡· Given a French customer VAT number (eg. here SARL CRISP IM with VAT number FR 50833085806):

SalesTax.getSalesTax("FR", null, "FR50833085806")
  .then((tax) => {
    // This customer is VAT-exempt (as it is a business)
    /* tax ===
      {
        type     : "vat",
        rate     : 0.00,
        currency : "EUR",
        area     : "worldwide",
        exchange : "business",

        charge   : {
          direct  : false,
          reverse : true
        },

        details  : []
      }
     */
  });

Note: Crisp is a real living business from France, check their website there.

πŸ‡«πŸ‡· Given a French customer VAT number from a πŸ‡«πŸ‡· French tax origin (eg. here SARL CRISP IM with VAT number FR 50833085806):

// Set this once when initializing the library (to France)
SalesTax.setTaxOriginCountry("FR")

SalesTax.getSalesTax("FR", null, "FR50833085806")
  .then((tax) => {
    // This customer owes VAT in France (as it is a business, and billing is FR-to-FR)
    // The `direct` tag is set to `true`, thus VAT should be charged
    // The `area` tag is set to `national` as the exchange is done in France
    /* tax ===
      {
        type     : "vat",
        rate     : 0.20,
        currency : "EUR",
        area     : "national",
        exchange : "business",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "vat",
            rate : 0.20
          }
        ]
      }
     */
  });

πŸ‡«πŸ‡· Given a French customer VAT number from a πŸ‡±πŸ‡» Latvian tax origin (eg. here SARL CRISP IM with VAT number FR 50833085806):

// Set this once when initializing the library (to Latvia)
SalesTax.setTaxOriginCountry("LV")

SalesTax.getSalesTax("FR", null, "FR50833085806")
  .then((tax) => {
    // This customer owes a VAT reverse charge in their country (France), no VAT is due in Latvia
    // The `reverse` tag is set to `true`, thus the customer should apply a reverse VAT charge in their country
    // The `area` tag is set to `regional` as the exchange is done in the European Union
    /* tax ===
      {
        type     : "vat",
        rate     : 0.00,
        currency : "EUR",
        area     : "regional",
        exchange : "business",

        charge   : {
          direct  : false,
          reverse : true
        },

        details  : []
      }
     */
  });

πŸ‡ΊπŸ‡Έ Given an United States > California customer without any VAT number (eg. a consumer):

SalesTax.getSalesTax("US", "CA")
  .then((tax) => {
    // This customer has to pay 8.25% VAT (as it is a consumer)
    /* tax ===
      {
        type     : "vat",
        rate     : 0.0825,
        currency : "USD",
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "vat",
            rate : 0.0825
          }
        ]
      }
     */
  });

πŸ‡¨πŸ‡¦ Given a Canada > Ontario customer without any VAT number (eg. a consumer):

SalesTax.getSalesTax("CA", "ON")
  .then((tax) => {
    // This customer has to pay 5% GST + 8% HST (as it is a consumer)
    /* tax ===
      {
        type     : "gst+hst",
        rate     : 0.13,
        currency : "CAD",
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "gst",
            rate : 0.05
          },

          {
            type : "hst",
            rate : 0.08
          }
        ]
      }
     */
  });

πŸ‡±πŸ‡» Given a Latvian customer without any VAT number (eg. a consumer):

SalesTax.getSalesTax("LV")
  .then((tax) => {
    // This customer has to pay 21% VAT (as it is a consumer)
    /* tax ===
      {
        type     : "vat",
        rate     : 0.21,
        currency : "EUR",
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "vat",
            rate : 0.21
          }
        ]
      }
     */
  });

πŸ‡±πŸ‡» Given a Latvian customer without any VAT number from a πŸ‡«πŸ‡· French tax origin (eg. a consumer):

// Set this once when initializing the library (to France)
SalesTax.setTaxOriginCountry("FR")

SalesTax.getSalesTax("LV")
  .then((tax) => {
    // This customer owes VAT in Latvia (as it is a consumer, and billing is FR-to-LV)
    // The `direct` tag is set to `true`, thus VAT should be charged
    // The `area` tag is set to `regional` as the exchange is done in the European Union
    /* tax ===
      {
        type     : "vat",
        rate     : 0.21,
        currency : "EUR",
        area     : "regional",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "vat",
            rate : 0.21
          }
        ]
      }
     */
  });

πŸ‡­πŸ‡° Given an Hong Kong-based customer (eg. a consumer):

SalesTax.getSalesTax("HK")
  .then((tax) => {
    // Hong Kong has no VAT
    /* tax ===
      {
        type     : "none",
        rate     : 0.00,
        currency : null,
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : false,
          reverse : false
        },

        details  : []
      }
     */
  });

πŸ‡ͺπŸ‡Έ Given a Spanish customer who provided an invalid VAT number (eg. a rogue business):

SalesTax.getSalesTax("ES", null, "ESX12345523")
  .then((tax) => {
    // This customer has to pay 21% VAT (VAT number could not be authenticated against the VIES VAT API)
    /* tax ===
      {
        type     : "vat",
        rate     : 0.21,
        currency : "EUR",
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type : "vat",
            rate : 0.21
          }
        ]
      }
     */
  });

βœ… Process the price including sales tax for a customer

Prototype: SalesTax.getAmountWithSalesTax(countryCode<string>, stateCode<string?>, amount<number?>, taxNumber<string?>)<Promise<object>>

Notice: this method is origin-aware. It means it return values relative to your configured tax origin country.

πŸ‡ͺπŸ‡ͺ Given an Estonian customer without any VAT number, buying 100.00€ of goods (eg. a consumer):

SalesTax.getAmountWithSalesTax("EE", null, 100.00)
  .then((amountWithTax) => {
    // This customer has to pay 20% VAT
    /* amountWithTax ===
      {
        type     : "vat",
        rate     : 0.20,
        currency : "EUR",
        price    : 100.00,
        total    : 120.00,
        area     : "worldwide",
        exchange : "consumer",

        charge   : {
          direct  : true,
          reverse : false
        },

        details  : [
          {
            type   : "vat",
            rate   : 0.20,
            amount : 20.00
          }
        ]
      }
     */
  });

βœ… Validate tax number for a customer

Prototype: SalesTax.validateTaxNumber(countryCode<string>, taxNumber<string?>)<Promise<boolean>>

πŸ‡«πŸ‡· Given a French customer VAT number (eg. here SARL CRISP IM with VAT number FR 50833085806):

SalesTax.validateTaxNumber("FR", "FR50833085806")
  .then((isValid) => {
    // isValid === true
  });

πŸ‡ΊπŸ‡Έ Given an United States customer without any VAT number (eg. a consumer):

SalesTax.validateTaxNumber("US")
  .then((isValid) => {
    // isValid === false
  });

πŸ‡±πŸ‡» Given a Latvian customer without any VAT number (eg. a consumer):

SalesTax.validateTaxNumber("LV")
  .then((isValid) => {
    // isValid === false
  });

πŸ‡ͺπŸ‡Έ Given a Spanish customer who provided an invalid VAT number (eg. a rogue business):

SalesTax.validateTaxNumber("ES", "ESX12345523")
  .then((isValid) => {
    // isValid === false
  });

βœ… Get tax exchange status for a customer (exempt + area + exchange)

Prototype: SalesTax.getTaxExchangeStatus(countryCode<string>, stateCode<string?>, taxNumber<string?>)<Promise<object>>

Notice: this method is origin-aware. It means it return values relative to your configured tax origin country.

πŸ‡«πŸ‡· Given a French customer VAT number (eg. here SARL CRISP IM with VAT number FR 50833085806):

SalesTax.getTaxExchangeStatus("FR", null, "FR50833085806")
  .then((exchangeStatus) => {
    /* exchangeStatus ===
      {
        exchange : "business",
        area     : "worldwide",
        exempt   : true
      }
     */
  });

πŸ‡²πŸ‡¦ Given a Morocco-based customer:

SalesTax.getTaxExchangeStatus("MA")
  .then((exchangeStatus) => {
    /* exchangeStatus ===
      {
        exchange : "consumer",
        area     : "worldwide",
        exempt   : false
      }
     */
  });

πŸ‡ΊπŸ‡Έ Given an United States > Delaware-based customer:

SalesTax.getTaxExchangeStatus("US", "DE")
  .then((exchangeStatus) => {
    /* exchangeStatus ===
      {
        exchange : "consumer",
        area     : "worldwide",
        exempt   : true
      }
     */
  });

πŸ‡­πŸ‡° Given an Hong Kong-based customer:

SalesTax.getTaxExchangeStatus("HK")
  .then((exchangeStatus) => {
    /* exchangeStatus ===
      {
        exchange : "consumer",
        area     : "worldwide",
        exempt   : true
      }
     */
  });

βœ… Disable / enable tax number validation

Prototype: SalesTax.toggleEnabledTaxNumberValidation(enabled<boolean>)<undefined>

πŸ‘ Enable tax number validation (enabled by default β€” use only if you disabled it previously):

SalesTax.toggleEnabledTaxNumberValidation(true)

πŸ‘Ž Disable tax number validation (do not check tax number syntax):

SalesTax.toggleEnabledTaxNumberValidation(false)

βœ… Disable / enable tax number fraud check

Prototype: SalesTax.toggleEnabledTaxNumberFraudCheck(enabled<boolean>)<undefined>

Notice: fraud check requires tax number validation to be enabled.

πŸ‘ Enable tax number fraud check (enable hitting against external APIs to verify tax numbers against fraud):

SalesTax.toggleEnabledTaxNumberFraudCheck(true)

πŸ‘Ž Disable tax number fraud check (disabled by default β€” use only if you enabled it previously):

SalesTax.toggleEnabledTaxNumberFraudCheck(false)

Where is the offline tax data pulled from?

The offline data contained in the sales-tax library comes from different sources:

It is kept up-to-date year-by-year with tax changes worldwide.

Some countries have multiple sales tax, eg. Brazil. In those cases, the returned sales tax is the one on services. Indeed, I consider most users of this module use it for their SaaS business β€” in other words, service businesses.

What happens if a country or state schedules a tax rate change?

As tax rate changes happen to some countries in the word on a yearly basis, sales-tax automatically uses the current tax rate relative to current date and time, ie. whenever you call the library functions.

At a technical level, tax rate changes for a country can be easily scheduled from the tax rates JSON file by moving the current tax rate in a before object, which then stores the country tax rate before enforcement date, and then the future tax rate is stored in the main object. Note that as a library user, you do not have to schedule tax rate changes, sales-tax handles it for you automatically.

Please make sure you always keep sales-tax up-to-date with the latest NPM version, as those tax rate changes are stored in an offline JSON file, which requires a manual library update.

For instance, Germany changed their VAT rate from 19% down to 16% as of 1st July 2020:

"DE": {
  "type": "vat",
  "rate": 0.16,
  "currency": "EUR",

  "before": {
    "2020-06-30T22:00:00.000Z": {
      "type": "vat",
      "rate": 0.19,
      "currency": "EUR"
    }
  }
}

I bill from the EU, but sales tax is still being returned for non-EU countries!

As international tax rules can be very complex depending on your business legal structure (eg. if you run a nexus in an US state, you may owe sales tax to this US state, even if you charge from the UK); sales-tax does not void returned tax rate for worldwide countries.

Thus, when the country is worldwide relative to your billing origin country, you need to handle things your own way.

To make things easier for you, sales-tax returns an area parameter in the SalesTax.getSalesTax, that is either worldwide, regional or national (this depends on your configured origin country). For regional and national areas, you can trust the returned rate. However, you may need to override all worldwide area rates and void them all to zero; for instance if you charge from France to the United States, and you know that you do not owe sales tax in the US as you do not run a nexus company in the US.

⚠️ Note that this would also apply to non-EU businesses charging customers outside of their jurisdiction. For instance, an Australian business would not charge VAT for customers outside of the country, yet it would charge VAT for all Australian customers. Yet, VAT might be returned by sales-tax for such international charges (ie. worldwide area). Therefore, such a worldwide area VAT should be voided if you consider that this does not apply to you, while national or regional area VAT should be used as normal. Always check with your accountant when in doubt.

How are tax numbers validated?

πŸ‡ͺπŸ‡Ί Europe

European VAT numbers can be fraud-checked against the official ec.europa.eu VIES VAT API, which return whether a given VAT number exists or not. This helps you ensure a customer-provided VAT number really exists. This feature, as it may incur significant delays (while querying the VIES VAT API) is disabled by default. There's a switch to enable it.

In all cases, the syntax of the European VAT numbers get validated from offline rules. Although, it only checks number syntaxical correctness; thus it is not sufficient to tell if the number exists or not.

You can manually check a VAT number on VIES VAT number validation.

πŸ‡ΊπŸ‡Έ United States

United States EIN (U.S. Employer Identification Number) are validated against EIN format rules.

πŸ‡¨πŸ‡¦ Canada

Canada BN (Business Number) are validated against BN format rules.

🏴 Rest of the world

If a country or economic community is not listed here, provided tax identification numbers are ignored for those countries (considered as invalid β€” so do not rely on validation methods as a source of truth).

If you need tax number validation for a missing country, feel free to submit a Pull Request.

node-sales-tax's People

Contributors

adrai avatar danielraouf avatar francoispala avatar gierschv avatar intarsz avatar jvmonjo avatar lfalck avatar mindflowgo avatar philosophicalpsycho avatar pszxzsd avatar revington avatar stephankaag avatar valeriansaliou avatar vvo avatar wootra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-sales-tax's Issues

City sales tax?

Hey - This is more of a question than an issue. I'm sorry if this is an inappropriate forum. I know that in the US, there are city/locale sales taxes and other sales taxes required for specific postal addresses in addition to state sales tax. Are those support in some way by this library?

Thanks!
Jason

Specify origin country per-method call

Hi there,

Thanks for building this wonderfully useful library. We're planning on using this in our e-commerce platform which allows users in several different countries to sell to their customers. This means we'll need to change the origin country on a per-estimate/invoice basis. Using a global origin introduces a race condition under our circumstances, i.e. when two customers from two different user's origins request an estimate/invoice at the same time.

Are there any plans to allow the origin to be set at the time the sales tax is requested? Thanks!

Australia

My business is from Australia, So broadly speaking, we have GST, which is 10%.

But export sales (ie sales from Australia to some other domicile) is GST free.

Unless I have misunderstood the usage of your software, this is not reflected in the output from your code.

I am expecting to set my business tax origin to Australia (AU).

Then:

  • sales tax (GST) for a customer from Australia should yield 10% (CASE 1 Below)
  • sales tax for a customer OUTSIDE of Australia should yield 0% (CASE 2 Below)

My expectations come about after reading the README file, specifically, with respect to the getSalestax(...) method, where you have stated:

this method is origin-aware. It means it return values relative to your configured tax origin country

So below is a typical query that I would expect to make:

// SET MY ORIGIN TO AUSTRALIA
SalesTax.setTaxOriginCountry('AU')

// [CASE 1] Domestic Customer -- THIS RESULT IS CORRECT
SalesTax.getSalesTax("AU").then(console.log);

{
  type: 'gst',
  rate: 0.1,
  area: 'national',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'gst', rate: 0.1 } ]
}

// [CASE 2] International Customer (eg USA) -- THIS RESULT IS NOT CORRECT (According to my Understanding)
SalesTax.getSalesTax("US","CA").then(console.log);

{
  type: 'vat',
  rate: 0.0825, // <------- WRONG
  area: 'worldwide',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'vat', rate: 0.0825 } ]
}

TN sales taxes

for getting value of the sales tax TN, the value is wrong,

this is the response i get

result is: {"type":"vat","rate":0.07,"currency":"USD","area":"national","exchange":"consumer","charge":{"direct":true,"reverse":false},"details":[{"type":"vat","rate":0.07}]}

this is the function getting the result

const result = await SalesTax.getSalesTax('US', 'TN');
the corrected value for tn is supposed to be 0.0925 not 0.07

[Canada Sales Tax] rate property value seems higher than it should for ON, NB and NS

Hi,
I would like to use this library in my application for sales tax in Canada, but after executing the following code to check the values currently offered, I stumbled upon something I think might be a bug.

const salesTax = require("sales-tax")
const provinces = ["QC", "ON", "NB", "NS", "NT", "NU", "YK", "BC", "AB", "MB"];
(async ()=> {
    for (const province of provinces) {
        console.log(province + " has sales tax: " + salesTax.hasStateSalesTax("CA", province))
        await salesTax.getSalesTax("CA", province).then((v)=>{
            console.log(v)
        })
    }  
})()

Results in:

...
ON has sales tax: true
{
  type: 'gst+hst',
  rate: 0.18,
  area: 'worldwide',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'gst', rate: 0.05 }, { type: 'hst', rate: 0.13 } ]
}
NB has sales tax: true
{
  type: 'gst+hst',
  rate: 0.2,
  area: 'worldwide',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'gst', rate: 0.05 }, { type: 'hst', rate: 0.15 } ]
}
NS has sales tax: true
{
  type: 'gst+hst',
  rate: 0.2,
  area: 'worldwide',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'gst', rate: 0.05 }, { type: 'hst', rate: 0.15 } ]
}
...

In all of those, that rate would actually be the hst - gst, because the hst includes the gst already. Ontarians, for example, don't pay 18% taxes, they pay 13%.

If you agree, I could produce a PR to fix this for you, but wanted first to open up a discussion just in case.

My solution would be to remove the gst from the details when there's an hst and calculate the rate by sum of detailed rate. It's something I'm thinking about on the fly.

Tax from EU to non-EU

I have a business in the UK that would like to sell a digital subscription globally. Assuming my business is registered for MOSS, to pay all EU VAT to UK. What should I tax non-EU customers?
Following our conversation on Twitter, I should basically NOT charge Taxes (like VAT) to customers outside the EU, regardless if business or consumer (btw this is in contrast with some things I read online, where countries like Japan, New Zeland etc.. want to tax these goods).
If in my case no tax is due to non-EU customer, how do I use the library to get that result?

Either value I pass to setTaxOriginCountry as useRegionalTax for origin UK for a customer in California, I always get 8.25% VAT:

    const SalesTax = require('sales-tax');
    SalesTax.setTaxOriginCountry("UK", false);
    SalesTax.getSalesTax("US", "CA")
      .then((tax) => {
        console.log(tax);
        /* tax ===
          {
            type: 'vat',
            rate: 0.0825,
            area: 'worldwide',
            exchange: 'consumer',
            charge: { direct: true, reverse: false }
          }
         */
      });

I would assume I should pass true btw, since hopefully there will be enough turnover and hopefully MOSS will be needed.

Tax exemptions in some regions of Spain

When Spain is selected as tax origin country, some regions should not have tax applied.

These are the regions with tax rate 0:

SalesTax.getSalesTax('ES', 'CE') // Ceuta
SalesTax.getSalesTax('ES', 'ML') // Melilla
SalesTax.getSalesTax('ES', 'TF') // Tenerife
SalesTax.getSalesTax('ES', 'GC') // Las Palmas

Wrong tax calculation for Spain

Since the tax is calculated with the sum of Country Tax + State Tax, the previous values were wrong.

Now it only includes states wich are exempt of tax (with negative rate) and any other state gets the default tax.

This way the calculation is correct and the code is easier to mantain.

Here is the PR. I hope you are ok with this. And sorry for the previous wrong PR:
#3

Introduce tax categories

I appreciate that you reduce the complexity of international tax by assuming services as the standard GST/VAT category, but it would be great to include the additional rates from the EY overview in the JSON and allow for querying those too for a more flexible lookup.

Is this something you've considered?

Implement GST for India

Great library! Thank you for simplifying tax rates.

I must point out that GST was implemented in India in 2017 replacing VAT, excise duty and service tax while node-sales-tax still returns VAT for India.

File: https://github.com/valeriansaliou/node-sales-tax/blob/master/res/sales_tax_rates.json#L266-L268
More Info: https://cleartax.in/s/differences-between-gst-and-vat

I must point out that India has multiple tax rate for GST: 0%, 5%, 12%, 18%, 28%, 28% (+ Cess) depending upon the item.
Sample: https://cleartax.in/s/gst-rates

It's pretty clear that changing tax from VAT to GST won't do the trick and therefore, an additional method is required which may accept Item name (or Tax rate) and maintain uniform logic.

Implement way to get country code and province code

I was looking to use node-sales-tax in a small expense tracking project and I wanted to automatically set the sales tax base on the user location. While reading the documentation, I found that the country code and province code are needed to get the sales tax, but I did not see any helpers for converting a location into these codes. After searching for a bit, I found the answer from the U.S. Customs and Border Patrol of all places. They provide an excel sheet that has the country and province code for 1637 provinces.

I wrote some code to download the xlsx and convert it to a json file ( I am a novice, so I apologize for all issues with the code):

const XLSX = require('xlsx')
const fs = require('fs')
const axios = require('axios')
const path = require('path')

let currentDir = process.cwd()
let xlsxFileName = path.join(currentDir,'./international-province-codes.xls')
let jsonFileName = path.join(currentDir,'./codes.json')
// excel sheet has 3 columns
const columns = {
	countryCode:'A',
	provinceCode:'B',
	provinceName:'C'
}
async function getFile(){
	// customs and border patrol provides a xlsx file with all international state/province codes
	let xlsxUrl = 'https://www.cbp.gov/sites/default/files/documents/codes_7.xls'
	const response = await axios.get(xlsxUrl,{
		responseType:'stream',
	})
	await response.data.pipe(fs.createWriteStream(xlsxFileName))
	return xlsxFileName
}
async function parseXlsx(){
	let sheet
	try{
		sheet = XLSX.readFile(await getFile())
	}catch(err){
		if(err.errno === -4058){
			console.error('Excel sheet not found')
			process.exit()
		}
	}
	sheet = sheet.Sheets.Sheet1
	// remove unnecessary keys
	let validKeys = Object.keys(sheet).filter(key=>
		// keys are a letter and a number e.g 'A1'
		key && sheet[key]?.w && (
			key[0] == columns.countryCode || 
			key[0] == columns.provinceCode ||
			key[0] == columns.provinceName
		)
	)
	// convert validKeys into json object
	let provinceCodes = {}
	validKeys.forEach(key=>{
		let rowNumber = parseInt(key.substring(1))
		let countryCell = columns.countryCode + rowNumber
		let provinceCell = columns.provinceCode + rowNumber
		let nameCell = columns.provinceName+rowNumber
		
		// even after filtering to make sure the cells exist
		// the cell values are sometimes null ???
		let countryCode = sheet[countryCell]?.w
		let provinceCode = sheet[provinceCell]?.w
		let provinceName = sheet[nameCell]?.w
		
		// remove diacritical/accent characters found solution here: 
	  // https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
		provinceName = provinceName?.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase()
		provinceCodes[provinceName] = { countryCode,provinceCode}
	})
	// write to file 
	fs.writeFileSync(jsonFileName,JSON.stringify(provinceCodes,null,2))
}
parseXlsx()

The resulting json should allow users to to use states/provinces to get both country and province codes

Function to use custom sales tax rates

Can you add a function that replaces and overrides the 'sales_tax_rates.json' ?

So instead of using the ...

var tax_rates = require("../res/sales_tax_rates.json");

... in your 'sales_tax.js', someone can define and use a function at the beginning that replaces the 'sales_tax_rates.json' and use a custom file?! E.g:

SalesTax.setSalesTaxRates("./helpers/sales_tax_rates_custom.json")

And as a plus, just/only overrides/adds indiviual objects of the 'sales_tax_rates.json' if country/state exist/does not exist?! So e.g. :

SalesTax.setSalesTaxRates("./helpers/sales_tax_rates_custom.json", "override")

I've notice that the Value-added tax (VAT) rates from PwC are in some cases a bit different ... e.g.

China (CN):
You: 16
PWC: 13, 9, or 6

Kenya (KE):
You: 14
PWC: 16

Nigeria (NG)
You: 5
PWC: 7.5

...

Or is this already possible?

It would be great to have this so we don't have to wait till you update it! And given the complexity adding their individual rates.

I also think that would help with other existing and probably incoming request.

Validate-vat Error: Failed to parseField countryCode - update to 0.8.0 required

I'm getting this error:

Error: Failed to parseField countryCode
at parseField (/project/node_modules/validate-vat/lib/index.js:53:15)
at parseSoapResponse (/project/node_modules/validate-vat/lib/index.js:67:22)
at IncomingMessage. (/project/node_modules/validate-vat/lib/index.js:108:18)
at IncomingMessage.emit (events.js:388:22)
at endReadableNT (internal/streams/readable.js:1336:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
soapMessage: '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">soap:Bodysoap:Faultsoap:ServerThe given SOAPAction urn:ec.europa.eu:taxud:vies:services:checkVat/checkVat does not match an operation.</soap:Fault></soap:Body></soap:Envelope>'
}

... when I try to validate a tax number:

salesTax.validateTaxNumber("FR", "FR50833085806")
.then((isValid) => {
// ...
})
.catch(function(error){
console.log('error:');
console.log(error);
});
})

It used to work fine. Seems like to be the same issue as this: viruschidai/validate-vat#46

Can you update validate-vat to the latest?! 0.8.0

I get incorrect responses back, any idea why?

Hi there,
I just installed this package and am sure it will help me a lot in handling VAT.

The problem I'm encountering is that the responses I get back are incorrect. I do exactly as instructed in the docs. Any idea why I'm getting invalid responses back?

Example from the documentation:

SalesTax.setTaxOriginCountry("FR")
SalesTax.getSalesTax("FR", null, "FR50833085806")
  .then((tax) => {
       console.log("tax", tax)
  });`

Expected response:

   {
        type     : "vat",
        rate     : 20.00,
        area     : "national",
        exchange : "business",

        charge   : {
          direct  : true,
          reverse : false
        }
   }

Actual response:
Screenshot 2020-01-07 at 10 42 36

how to handle portugal's VAT in autonomous region ?

Hi there,

I would like to know how can I use this library with autonomous regions from portugal (from here) :

  • 22% in the Autonomous Region of Madeira
  • 18% in the Autonomous Region of the Azores

I've tried to add PT-20 (Azores ISO-Code from here) as a PT state but it seems that their VAT ared added to each other

  "PT": {
    "type": "vat",
    "rate": 0.23,
    "states": {
      "20": {
        "rate": 0.18,
        "type": "vat"
      }
    }
  },

results in a

// response
{
  type: 'vat+vat',
  rate: 0.41000000000000003,
  area: 'worldwide',
  exchange: 'consumer',
  charge: { direct: true, reverse: false },
  details: [ { type: 'vat', rate: 0.23 }, { type: 'vat', rate: 0.18 } ]
}

I cannot go with rate: -0.05 for the state because the details still contain the main PT VAT

Is there an existing solution/workaround ? Maybe we need PT-20 and PT-30 to be added as standalone countries within EU but it does not sound like the best solution

Thanks

international vs. worldwide?

Hi, I was wondering about this part of the readme:

To make things easier for you, sales-tax returns an area parameter in the SalesTax.getSalesTax, that is either international, regional or national (this depends on your configured origin country). For regional and national areas, you can trust the returned rate. However, you may need to override all international area rates and void them all to zero; for instance if you charge from France to the United States, and you know that you do not owe sales tax in the US as you do not run a nexus company in the US.

As far as I can tell, the area parameter returned for VAT calculation from EU to other countries is worldwide and not international as described above, is this an error in the readme or am I doing something wrong?

VAT calculation from EU to other countries?

Just set up a new issue in case it's better for you (sorry about the inconvenience!).

I need to calculate whether my SaaS should or not add VAT to the invoice for my customers (who are individual, natural persons without VAT number from inside and outside the EU). I'm in Estonia, EU. That means that I essentially need to add VAT to customers in the EU and don't add VAT to customers outside the EU.

However, the calculations from this library seem to not realize that I'm in Estonia, or I'm missing something when calling the library.

This is my code:

var SalesTax = require("sales-tax")

SalesTax.setTaxOriginCountry("EE", false)

var countries = [
    {"name": "Afghanistan", "code": "AF"},
    {"name": "land Islands", "code": "AX"},
    {"name": "Albania", "code": "AL"},
    ...
    {"name": "Zambia", "code": "ZM"},
    {"name": "Zimbabwe", "code": "ZW"}
]

countries.forEach(c => {
    SalesTax.getSalesTax(c.code, "EE").then((tax) => {
        console.log("VAT must be applied to a customer from " + c.name + "? "+JSON.stringify(tax))
    })
})

So what I would expect is to have a rate of 0.20 (20%) for all customers in EU (as my services require human intervention, so they don't fall in the category of MOSS or telecommunications downloads act), and 0% outside of EU (as I'm in Estonia, I can't charge any VAT or any extra tax to customers outside the EU).

So I would expect something like this:

...
VAT must be applied to a customer from Spain? {"type":"vat","rate":0.2,"area":"regional","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Suriname? {"type":"vat","rate":0.0,"area":"worldwide","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Sweden? {"type":"vat","rate":0.2,"area":"regional","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Switzerland? {"type":"vat","rate":0.0,"area":"worldwide","exchange":"consumer","charge":{"direct":true,"reverse":false}}
...

a Spanish o Swedish customer is in Europe: I charge 20% VAT
a customer from Suriname is outside EU: I don't charge VAT
a customer from Sweden is considered to be outside EU for VAT: I don't charge VAT
Instead, I get something like this:

VAT must be applied to a customer from Spain? {"type":"vat","rate":0.2,"area":"regional","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Suriname? {"type":"vat","rate":0.1,"area":"worldwide","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Sweden? {"type":"vat","rate":0.2,"area":"regional","exchange":"consumer","charge":{"direct":true,"reverse":false}}
VAT must be applied to a customer from Switzerland? {"type":"vat","rate":0.077,"area":"worldwide","exchange":"consumer","charge":{"direct":true,"reverse":false}}

I kind of could use the "area" indicative, as it seems all EU countries have the "area" set to regional, but I need to trust this is valid and know why the VAT rate is not properly returned. Probably I'm doing something wrong.

Any help or insight greatly appreciated. Thanks!!!

Calculating VAT considering where the service is provided, not just where the customer is from

First of all great lib, been using for a while and saved us a ton of time.

I am dealing with an edge-case where I think the calculation might be wrong. We're a marketplace for coworkings, so we calculate and charge VAT on behalf of our sellers. Until now I was calculating the VAT based on the customer's VAT number and country and the seller's country.

The calculation is correct when dealing with european customers but I'm not sure when dealing with outside-EU ones.

If a customer from the US pays for a meeting room in Spain, the service is sold to an outside-EU customer but it is provided in the EU, hence VAT should apply. Right now I don't see any way to deal with this case?

Do you think it should be handled by the lib or should I make my own logic for it? Or I am overthinking this?

northern ireland VAT number start with XI

Hello

in some case, a northern ireland company uses their vat number start with XI ( you can also find this code on VIES )

specifically when apply OSS system.

northern ireland is within UK, but they also use EU VAT system.

Can you make an update please ?

PHP version of this lib

first of all, I'd like to thank you for the great work and efforts you put into this project.
is it possible to get a PHP version of this library?

Tax details are generated for regions in Spain with tax exemptions.

Hi, great project! πŸ™‚

I think i found an issue. For regions in Spain with tax exemption, rate and total are correctly calculated, but the tax details still contain the country tax:

SalesTax.getAmountWithSalesTax("ES", "GC", 1000.00)
  .then((amountWithTax) => {
    console.log(amountWithTax);
  });
{
    type: 'vat',
    rate: 0,
    price: 1000,
    total: 1000,
    area: 'worldwide',
    exchange: 'consumer',
    charge: {
        direct: true,
        reverse: false
    },
    details: [{
            type: 'vat',
            rate: 0.21,
            amount: 210
        }
    ]
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.