Skip to content

fix: stop double tax when prices are entered inclusive of tax#222

Open
kzamanbd wants to merge 5 commits into
developfrom
fix/inclusive-tax-double-calculation
Open

fix: stop double tax when prices are entered inclusive of tax#222
kzamanbd wants to merge 5 commits into
developfrom
fix/inclusive-tax-double-calculation

Conversation

@kzamanbd

@kzamanbd kzamanbd commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

With WooCommerce set to "Yes, I will enter prices inclusive of tax", POS orders were taxed twice: a 100.00 product (incl. 10% VAT) saved as net 100 + VAT 10 = 110.00 instead of net 90.91 + VAT 9.09 = 100.00. WC order line totals are always tax-exclusive, but the POS posted the raw (gross) price as the line total, so WC calculated tax on top of it.

Related display bugs: cart totals drifted to 109.09 when the tax display mode changed or before settings loaded, and the payment modal showed Subtotal 100 + Tax 9.09 = Total 100, which read as broken math.

Fix

Server (authoritative):

  • Manager.php::convert_inclusive_line_totals — nets gross POS line totals with exact WC_Tax::calc_inclusive_tax math (base rates) before WC calculates tax. Skips payloads that post no line totals (legacy Vue frontend — WC already nets those), non-taxable products, and misc lines flagged tax_status: none via _wepos_pos_data.
  • Common.php::get_tax_location — walk-in orders with no billing/shipping address now fall back to the store base address; previously tax based on billing/shipping resolved zero rates and the customer was charged the net price.
  • Manager.php::validate_item_stock_before_order — no longer fatals on product-less (misc) lines.

Frontend:

  • Cart line display prices derived at render time from the entry-mode raw price + per-unit tax_amount (cartItemDisplayPrices), so totals always follow the current woocommerce_tax_display_cart / woocommerce_prices_include_tax settings instead of stale stored values. Both flags are mirrored into the cart store and persisted so the first render is correct before the settings request resolves.
  • WC-cart-style inclusive display: no separate tax row; an "Including Tax X" note on the subtotal (cart), order total (payment modal), and receipt. Exclusive mode keeps the additive Tax row.
  • Misc products: tax class/status carried through the cart item, order payload (_wepos_pos_data), and held-order restore; distinct custom lines no longer merge (all share product_id 0); placeholder SKU passes WC's product-reference validation on line create.
  • Held-order restore reconstructs entry-mode raw prices + per-unit tax so restored carts display and re-save consistently; server line items match by line id first (disambiguates multiple custom lines).

Testing

Verified on VAT 10%, prices inclusive (wp eval REST dispatch + browser E2E):

Payload Result
Catalog product, gross 100 net 90.91, tax 9.09, total 100.00
Legacy Vue shape (no totals) untouched, total 100.00
Misc taxable, gross 50 net 45.45, tax 4.55, total 50.00
Misc non-taxable, 50 total 50.00, tax 0
Qty 3, gross 300 total 300.00, tax 27.27 (no rounding drift)
tax_based_on=shipping, no customer total 100.00 (was 90.91 / tax 0)

Browser: cart, update-to-server, qty change, cash checkout, receipt — totals match the WC cart in both incl and excl display modes, including across a display-mode change with a persisted cart.

Summary by CodeRabbit

  • New Features

    • Enhanced tax-inclusive/tax-exclusive pricing across the cart, payment modal, receipt, and restored orders.
    • Improved “Including Tax” messaging and tax row rendering based on current tax display mode.
    • Persisted cart pricing/tax display settings and added explicit support for toggling “prices include tax”.
    • Preserved tax details for custom/misc lines, including tax class/status.
  • Bug Fixes

    • Prevented double taxation when converting inclusive line totals for POS orders.
    • Improved handling for order/cart items that lack an associated product.
    • Corrected tax location fallback when country data is missing.

WC order line totals are tax-exclusive, but the POS sent the raw
(gross) product price as the line total, so WC added tax on top and
inclusive-price orders were taxed twice (100 became 110).

- net gross POS line totals server-side with WC_Tax::calc_inclusive_tax
  (Manager.php); skips legacy Vue payloads, which post no totals and
  are already netted by WC
- fall back to the store base address for tax location when a walk-in
  POS order has no billing/shipping address (tax resolved to zero and
  the customer was charged the net price)
- derive cart display prices at render time from entry-mode raw price
  + per-unit tax_amount so totals follow the current tax display
  settings instead of stale stored values; sync prices_include_tax
  into the cart store and persist both tax flags so first render is
  correct before settings load
- WC-style inclusive display: no separate tax row; "Including Tax X"
  note on subtotal/total in cart, payment modal, and receipt
- misc products: carry tax_class/tax_status through cart, payload
  (_wepos_pos_data) and held-order restore; never merge distinct
  custom lines; send placeholder SKU to pass WC product-reference
  validation; skip stock validation for product-less lines
- restore held orders with entry-mode raw prices and per-unit tax so
  display and re-save stay consistent
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d1c33e9-5e0a-4a05-a396-974b7876a2a1

📥 Commits

Reviewing files that changed from the base of the PR and between 841af61 and fced59d.

📒 Files selected for processing (1)
  • src/frontend/components/Cart.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/components/Cart.tsx

📝 Walkthrough

Walkthrough

POS tax handling now shares tax-inclusion state and conversion helpers across cart calculations, checkout displays, order restoration, custom lines, and REST order creation. Missing POS tax locations and product references receive explicit fallbacks.

Changes

Tax-inclusive POS pricing

Layer / File(s) Summary
Pricing state and conversion helpers
src/frontend/store/cart/*, src/frontend/utils/helpers.ts, src/frontend/types/index.ts
Cart state persists tax inclusion settings, while shared helpers convert item prices and resolve tax rates.
Cart tax-aware display
src/frontend/components/Cart.tsx, src/frontend/store/cart/reducer.ts
Cart items, miscellaneous products, subtotals, tax notes, and custom-line merging use tax-aware prices and metadata.
Order restoration and server conversion
src/frontend/pages/Home.tsx, includes/REST/Manager.php, includes/Common.php
Restored and updated orders preserve custom-line tax data; REST processing nets tax from inclusive POS totals and falls back for missing products or addresses.
Checkout and receipt tax presentation
src/frontend/components/PaymentModal.tsx, src/frontend/components/ReceiptModal.tsx
Payment and receipt views show additive tax only for exclusive displays and annotate inclusive totals with included tax.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HomePage
  participant RESTManager
  participant WooCommerceOrder
  HomePage->>RESTManager: submit POS line totals and tax metadata
  RESTManager->>WooCommerceOrder: convert inclusive totals to net line totals
  WooCommerceOrder-->>RESTManager: persist order items
  RESTManager-->>HomePage: return saved order data
Loading

Possibly related PRs

  • getdokan/wepos#214: Related frontend tax handling across cart display, order restoration, and cart selectors.

Suggested reviewers: mrabbani

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preventing double taxation for tax-inclusive prices.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/inclusive-tax-double-calculation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kzamanbd kzamanbd force-pushed the fix/inclusive-tax-double-calculation branch from c64094c to afe848f Compare July 10, 2026 12:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/components/Cart.tsx`:
- Line 41: Update the helpers import in Cart.tsx to include findTaxRate, which
is referenced by handleAddMiscProduct but currently unresolved. Preserve the
existing imports and use the exported helper so taxable miscellaneous products
can be added successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57d73e34-a164-48ae-9ab6-c32e33b175e9

📥 Commits

Reviewing files that changed from the base of the PR and between 61c8b50 and afe848f.

📒 Files selected for processing (13)
  • includes/Common.php
  • includes/REST/Manager.php
  • src/frontend/components/Cart.tsx
  • src/frontend/components/PaymentModal.tsx
  • src/frontend/components/ReceiptModal.tsx
  • src/frontend/pages/Home.tsx
  • src/frontend/store/cart/actions.ts
  • src/frontend/store/cart/reducer.ts
  • src/frontend/store/cart/selectors.ts
  • src/frontend/store/cart/store.ts
  • src/frontend/store/cart/types.ts
  • src/frontend/types/index.ts
  • src/frontend/utils/helpers.ts

Comment thread src/frontend/components/Cart.tsx Outdated
@kzamanbd kzamanbd self-assigned this Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant