OrchardCMS / OrchardCore.Commerce

The commerce module for Orchard Core.
MIT License
210 stars 87 forks source link

Tax infrastructure and default implementation (OCC-76) #33

Open bleroy opened 5 years ago

bleroy commented 5 years ago

Oh boy that's a tough one :D

Taxation is super-duper-complicated. It has lots of details, local subtleties, and unless you're selling very, very locally, you can't afford to ignore it.

In the MVP, we'll implement extensible infrastructure, enabling tax services to act on and modify an order. That's reasonably simple.

We'll also include a default implementation, that will also be fairly simple(istic), but will likely require more work for the shop administrator. It will likely involve a way to map customers to regions, and then regions to tax rates. Open questions include whether we should import data, under what format.

Jira issue

sarahelsaig commented 2 years ago

Not a simple topic, let's dig in deeper.

Kinds of Taxes

I think there are only two kinds that come up for e-commerce: VAT or sales tax, and customs.

While there are many accounting and legal differences between VAT and sales tax, for the purpose of a sales platform like OCC they behave identically, as a percentage cost added to the line item. It's typically a single rate, but some product classes (e.g. essential food or hygiene goods) may have different rates. Though unless you are extremely sure, just use the full rate. In e-commerce the tax is paid according to the purchaser's country/state or origin.

(Customs depend on the source and destination countries too. Local orders, or orders within a customs-union (e.g. the Schengen Zone in Europe or MERCOSUR in South-America) suffer no customs and many countries waive customs on small value shipments. For the rest, it's incredibly complicated with individual line item classes, the total value, and possibly various treaties can affect the result. Luckily customs is the importer's responsibility. While bigger sites offer informative estimates, it's not a requirement.)

Generic Implementation

As a general solution I think we should have an event, something like PreparingCartForCheckout that gets the ShoppingCartItem collection and updates it. There the line items could be updated individually, in an extensible way. (The same event could be useful for shipping too to add a shipping cost entry.) When accessing the cart this even would be triggered before returning the items.

I'm unsure how we should express the tax on line items. Another question is if the prices should be net or gross. As I understand, in the USA it's typical to show net prices. This is also common in VAT-using countries for shops that primarily sell to other businesses (for VAT write-off reasons). I think we have to bite the bullet and replace Money.Amount.Value with separate NetValue and GrossValue properties, to avoid potentially costly confusion. Then choosing which to display would be a matter of configuration. What do you think? Additionally we should have a string TaxName property, it would be important in the future if we need invoice integration.

Default VAT Implementation

This heavily depends on the above, but it should be flexible because these rates do change as legislation evolves (so don't use pre-calculated tax rate tables). As a start, we should add a feature that represents a local VAT implementation, with an admin menu where you can pick a single percentage. Then it would just apply it from the above mentioned event. This is enough if you only sell domestically. A different feature for or international orders, we might need to use a VAT API that gets the current and up-to-date numbers. I'm not aware of a reliable free API for that. If anyone has need or preference for this in the MVP, please speak up!

Nothing is Set in Stone

This is just my recommendation, please chime in if I missed something crucial. Please don't start working on the issue until we have heard some other voices on the topic.

Skrypt commented 2 years ago

Luckily customs is the importer's responsibility

I agree.

As a start, we should add a feature that represents a local VAT implementation, with an admin menu where you can pick a single percentage.

Here in Quebec we have 2 sales taxes. We have one that is federal HST (harmonized sales tax) and one that is provincial QST (Quebec sales tax). This means that it is a compound tax that needs to be calculated in the right order.

http://www.calculconversion.com/sales-tax-calculator-gst-qst.html

Here you can see how it is calculated. So, I believe that taxes should be based on some rules. Maybe looking into some known e-commerce platform could help find how it should be integrated.

sarahelsaig commented 2 years ago

Based on the linked tax calculator (from OCC's perspective) boils down to a 114.975% sales tax that applies to a region instead of the whole country. Do you have to pay QST if you import something into Quebec or just for local sales?

sarahelsaig commented 2 years ago

Anyway, you make a good point about looking at some other e-commerce platforms. I will look into it soon. In the meantime if anyone can point me to specific relevant parts in an open source e-commerce project, please don't hesitate.

UR-ScottShew commented 2 years ago

nopCommercehttps://github.com/nopSolutions/nopCommerce has a pretty good tax calc system. Look in /src/Plugins/Nop.Plugin.Tax.Avalara

From: Dávid El-Saig @.> Sent: Thursday, August 4, 2022 10:15 AM To: OrchardCMS/OrchardCore.Commerce @.> Cc: Subscribed @.***> Subject: Re: [OrchardCMS/OrchardCore.Commerce] Tax infrastructure and default implementation (#33)

Anyway, you make a good point about looking at some other e-commerce platforms. I will look into it soon. In the meantime if anyone can point me to specific relevant parts in an open source e-commerce project, please don't hesitate.

— Reply to this email directly, view it on GitHubhttps://github.com/OrchardCMS/OrchardCore.Commerce/issues/33#issuecomment-1205317417, or unsubscribehttps://github.com/notifications/unsubscribe-auth/APYRPBHSBFTCQHS6EBL3MXTVXPF45ANCNFSM4HKXC3VA. You are receiving this because you are subscribed to this thread.Message ID: @.**@.>>

Skrypt commented 2 years ago

QST is for local sales but as you can see it is also charged by Newegg.ca

image

It always depends if you need to produce a tax report for that country/state and I believe that it is now encouraged.

GST(HST) = Goods and service tax (Harmonized sales tax) = VAT (Value added tax) PST(QST) = Provincial sales tax (Quebec sales tax)

As you can see here they always have 2 taxes which are defined by GST and PST.

And I believe that some States in the USA also have city taxes and special jurisdiction taxes.

Skrypt commented 2 years ago

+1 for Avalara but you also need a simple table solution for small websites.

https://github.com/avadev/AvaTax-REST-V2-DotNet-SDK

UR-ScottShew commented 2 years ago

Not only can there be city taxes in the USA, there are (occasionally) regional taxes as well. One of the more common implementations is when two nearby cities charge a tax to fund a common public transportation infrastructure.

UR-ScottShew commented 2 years ago

In the USA, a purchaser can present an exemption certificate and not get charged sales tax, so OCC should take that into account as well.

Unlike a VAT, sales tax in the US is only charged at the final, retail sale of an item. So if you are a middleman who, say, buys goods from a manufacturer and sells to a retail store, you do not have to pay sales tax. If you are legally considered a Non-Profit Organization, you are also exempt from paying sales tax. In these cases, you can apply to your State of residence for a sales tax exemption certificate. When you present the certificate to a seller, they are not supposed to charge sales tax.

Full disclosure: I am not a sales tax professional, but I did implement a sales tax collection system while working at a previous employer

UR-ScottShew commented 2 years ago

Another thing about sales tax in the USA:

Sales Tax is paid by the purchaser of an item. All States in the US require that this tax be collected by the seller ON BEHALF of the purchaser, and then periodically be remitted to the taxing authority. But due to the legal concept of nexus*, if a seller does not have a physical presence in a particular State, that State cannot require that the seller collect sales tax. In these situations, the purchaser is supposed the calculate the required sales tax and remit it to their home taxing authority themselves, but this almost never happens in practice.

So it would help if OCC has some way to mark a Country/Province/State/City as “I am required to collect sales tax here” and then ignore sales tax calculations on all other locations.

*Nexushttps://en.wikipedia.org/wiki/State_income_tax#Nexus can get a bit thorny, but it boils down to the concept that a State can only enforce its laws within its own borders. If a Seller does not have physical presence in a State, it can’t be subject to that State’s laws.

Again disclosure: I am not a sales tax professional, but I did implement a sales tax collection system while working at a previous employer

Skrypt commented 2 years ago

I believe that big e-commerce companies have been recently encouraged to pay sales tax in different countries at least (Amazon). So, while a state cannot require it we still need to take it into consideration. Also, at that point, you would surely be using Avalara.

bleroy commented 2 years ago

As a reference point, here are some pointers to the Nwazet implementation, which was built for a US business frequently shipping overseas:

It relies heavily on the model of geography:

sarahelsaig commented 2 years ago

Avalara looks very interesting. Also I see an alternative, TaxJar (a Stripe company). Both have NuGet libraries. In either case you need full source and destination address, and line items with prices and tax codes. I guess the tax code would be the one you use for the product domestically, because how on earth would you know/maintain all the different tax codes for every country? We could add an optional field in ProductPart to store the product's tax code. As for the source address, that could be a site setting. So these are the info we should include in the abstraction (the equivalent of ITax in Nwazet) to cover every implementation. The result would be a collection of tax name and amount pairs. It has to be for each line item, because applying tax on just the total is not allowed everywhere.

As for the implementation without an external service, we could do something similar to the ZipCodeTaxPart.Rates, but use the whole address and the tax code to match against (either as a table of regex expressions or perhaps create a DSL with Irony). Then every item that matches would be applied.

bleroy commented 2 years ago

Probably shouldn't use ProductPart, which is deliberately minimalist in terms of concerns it addresses. There should be a separate part that encapsulates tax concerns and only that.

sarahelsaig commented 2 years ago

Good point, I agree.