In B2B commerce, especially in the hospitality and restaurant sector, buyers are accustomed to browsing product prices without VAT included .
However, at checkout, they expect to see the correct final total including VAT , calculated based on:
Supplier location
Buyer VAT status
Intracommunity rules
EU tax exemptions
The browsing experience reflects common B2B expectations with this new feature and setting:
Product listing prices → Without VAT
Checkout totals → With VAT (calculated properly)
Displayed price per product:
Net price (Excluding VAT)Optional label:
Excl. VAT
Display include:
Net subtotal
VAT amount (per rate if applicable)
Total including VAT
Tax exemption messaging if applicable
✅ When B2B + Show Without VAT = Active → Articles display net prices
✅ Cart line items show net prices
✅ Checkout shows full VAT breakdown
✅ Final payable amount always includes VAT (unless exemption logic applies)
✅ VAT calculation logic remains unchanged
✅ No regression in B2C shops
✅ No regression when “Show Articles Without VAT” is disabled
✅ Works with:
Multiple VAT rates
Intracommunity VAT reverse charge
VAT exempt customers
✅ Currency formatting remains correct
Mixed VAT rate products
Customer with valid EU VAT number
Customer without VAT number
Supplier inside EU vs outside EU
VAT reverse charge scenario
Customer switches country mid-checkout
Rounding differences between net display and final total
Discount codes applied
Quantity changes in cart
The Restaurants and professional buyers think in net pricing.
Forcing VAT-inclusive browsing:
Breaks B2B expectation
Reduces clarity
Feels retail (B2C)
This change aligns OrderLemon Catalogue with:
Supplier platforms
Wholesale marketplaces
Professional procurement systems
Now we enter the fun territory: tax logic. The place where tiny assumptions grow fangs.
Important principle:
Display mode changes. Tax engine does not.
Feature is active ONLY if:
B2B is active in your shop
Otherwise OrderLemon behaves normally (VAT-inclusive browsing if that was default).
Every product must internally store:
net price
vat rate
gross price = net_price * (1 + vat_rate)
If the system currently stores only gross, you must reverse-calculate:
net price = gross_price / (1 + vat rate)
When feature is active:
Display:
display_price = product.net_price
label = "Excl. VAT"Cart Line Total:
line_total_display = quantity * net_priceNo VAT breakdown shown in browsing.
Important:
This is pure UI logic. The cart internally must still carry:
line_net
line_vat
line_gross
vat_rate
Even if VAT isn’t displayed yet.
At checkout, VAT must be recalculated based on:
Supplier country
Customer billing country
Customer VAT number validity
EU membership status
Reverse charge rules
Here is the decision matrix simplified.
Let:
SC = Supplier Country
CC = Customer Country
VAT_ID = Valid VAT number provided?
EU(SC) / EU(CC) = Is country in EU?
SC == CC
Result:
Apply local VAT
Customer pays VAT
Show:
Net
VAT
Gross
EU(SC) == true
EU(CC) == true
SC != CC
Now split:
Reverse charge applies.
Result:
VAT rate = 0%
Show:
Net subtotal
VAT = 0
Message: “Reverse charge applied”
Invoice must mention reverse charge directive
Apply supplier country VAT.
Result:
VAT added
Customer pays VAT
EU(SC) == true
EU(CC) == false
Usually export scenario.
Result:
VAT = 0%
Show export message
But confirm compliance rules per country (implementation config-based).
Country-specific logic required.
Default safe assumption:
Tax rules determined by supplier configuration.
This should be configurable.
Pseudo logic:
function calculateCheckout(cart, customer, supplier):
for each line in cart:
vat_rate = determineVATRate(customer, supplier, line.product)
line.net = line.quantity * line.product.net_price
line.vat = line.net * vat_rate
line.gross = line.net + line.vat
subtotal_net = sum(line.net)
total_vat = sum(line.vat)
total_gross = sum(line.gross)
return {
subtotal_net,
total_vat,
total_gross,
breakdown_per_rate
}
Notice:
VAT rate determination happens at checkout, not browsing.
Never round per unit before multiplying.
Correct:
line_net = round(quantity * unit_net, 2)
line_vat = round(line_net * vat_rate, 2)
Wrong:
round(unit_net * vat_rate) * quantityRounding per unit causes cumulative discrepancies.
Always round at line level.
Even though browsing shows net prices:
Internally store:
cart_line = {
product_id,
quantity,
unit_net,
vat_rate_snapshot,
unit_gross_snapshot
}
But do NOT trust vat_rate_snapshot at checkout.
Revalidate VAT at checkout using customer data.
Taxes are contextual, not static.
Browsing:
Net prices only
“Excl. VAT” indicator
Checkout:
Net subtotal
VAT per rate
Total VAT
Grand total incl. VAT
Reverse charge or exemption messaging if applicable
Mixed VAT rates in cart
Discount codes (percentage vs fixed)
Shipping VAT rate different from product VAT
Manual price overrides
Currency rounding in multi-currency shops
Customer changing VAT ID mid-checkout
Country change after VAT validated
Tax is not arithmetic. It’s conditional law disguised as arithmetic.