*** Taxually for WooCommerce Changelog ***

2026-08-24 - version 1.4.7
* Fix - The 1.4.6 fix for Blocks checkout was itself ineffective: reapply_tax_after_blocks_recalculation() rebuilt its cache-lookup key from WC()->cart at the point WooCommerce Blocks builds the order, but WC_Cart::get_cart_hash() folds in the cart's own total() -- which apply_response_to_cart() had itself already mutated moments earlier in the same request via set_total(). Reconstructing the hash after that mutation produced a different key than the one the calculation was actually cached under, so the lookup silently missed every time (confirmed live: order #23, checkout showed 9.50 EUR tax on a 50.00 EUR item correctly, but the placed order persisted at 50.00 EUR / 0.00 tax, with no trace of the correction hook having run at all in the plugin's own debug log). Fix: the calculated tax, rate, and per-item amounts are now carried forward directly in a request-scoped value from the moment they're first calculated, rather than being looked up again later by reconstructing a key that can drift. Also added a safeguard so this carried-forward value is never reapplied if the order's own totals changed for an unrelated reason in the meantime (e.g. WooCommerce Blocks removing an invalid coupon and recalculating), which would otherwise risk applying tax calculated against a discount that no longer applies.

2026-08-24 - version 1.4.6
* Fix - The 1.4.5 fix only corrected classic checkout. WooCommerce Blocks checkout (Automattic\WooCommerce\StoreApi\Utilities\OrderController::update_order_from_cart()) builds the order's tax line items correctly from the cart via create_order_tax_lines(), then unconditionally calls $order->calculate_totals() -- which recomputes every line item's, shipping item's, and fee item's tax from scratch via a fresh WC_Tax::find_rates()/find_shipping_rates() lookup, silently overwriting the value this plugin had just correctly applied (worse than the original bug: the order's total itself dropped to the bare subtotal, not just its tax breakdown). Classic checkout's WC_Checkout::create_order() never calls calculate_totals(), so it was never affected. Reproduced live on two separate real Blocks orders. Fix: a new handler on the woocommerce_order_after_calculate_totals hook (fired at the end of calculate_totals(), confirmed via WooCommerce core source, and the same hook TaxJar's own production plugin uses for this exact problem) re-applies the already-calculated tax to line items, shipping, and fees using the same cached calculation the customer was shown at checkout -- with an explicit check for WooCommerce's own VAT-exemption flag first, so a legitimately tax-exempt order (e.g. valid reverse-charge B2B) is never overridden.

2026-08-24 - version 1.4.5
* Fix - Orders placed with checkout tax calculation enabled could be persisted with $0.00 tax on the order itself, even though the correct amount was charged and displayed throughout checkout. Root cause was two separate WooCommerce internals this plugin's cart-tax-override wasn't writing to: (1) WC_Checkout::create_order() reads the order's overall tax from the cart's `cart_contents_tax` field, which is populated by WooCommerce's own native tax engine before this plugin's hook runs -- the plugin was only setting the aggregate `total_tax` field (which drove the correct-looking checkout page), never `cart_contents_tax` (which drove what the order actually persisted); (2) the cart's `line_tax_data` was keyed by a synthetic tax-rate id of `0`, and WooCommerce's own order-tax-line builder (`create_order_tax_lines()`) treats a falsy rate id as "no rate", silently skipping creation of the order's itemized tax line entirely -- affecting classic checkout's invoice/tax-report data even after the total was fixed. Confirmed reproducible on real orders across two independent demo stores (Germany and Ireland). Fix: the plugin now also sets `cart_contents_tax` and its per-rate `cart_contents_taxes` array, and creates (or reuses) a real WooCommerce tax-rate row per billing country instead of the placeholder `0`, matching the approach real production tax-calculation plugins use for externally-computed tax. Checkout-supplied country input is validated against WooCommerce's own country list before use, so malformed input can't mint unbounded rows.

2026-08-24 - version 1.4.4
* Fix - The 1.4.3 fix only closed the race on the token-refresh call itself; a second, related race remained everywhere else the plugin talks to Taxually (tax calculation, refunds, product sync, the toggle push, and initial connect registration) -- five separate places each independently decided whether a 401 meant the connection was really revoked, and none of them accounted for a token being rotated by a different concurrent request between when this request fetched it and when its own API call actually reached the server. Reproduced live: a burst of real orders against the demo store, run specifically after the 1.4.3 fix, still disconnected the store even though the connection had never actually been revoked (confirmed against Taxually's own backend, which showed the API key as never revoked). Fix: introduced a single `authenticated_request()` method that every authenticated call in the plugin now goes through -- it owns fetching a valid token, and on a 401 it forces a fresh token and retries the same request exactly once before ever concluding the connection was really revoked. This is the only place in the plugin that decides a 401 means revoked, replacing five independent (and incompletely correct) copies of that judgment call.

2026-08-22 - version 1.4.3
* Fix - Concurrent checkout requests could disconnect the store from Taxually entirely. OAuth 2.1 rotates the refresh token on every use, so two shoppers checking out around the same moment the access token happened to need renewing could both read the same (about-to-expire) refresh token and race to use it -- only the first succeeds, and the second's refresh attempt is rejected by the server because that token was already consumed. The code treated that rejection (`invalid_grant`) as a genuine revocation and disconnected the store, which also silently cleared the merchant's own "Enable tax calculations at checkout" toggle (`disconnect()` clears it along with the connection), with no warning until someone checked settings. Fix: before treating an `invalid_grant` refresh failure as a real revocation, check whether the refresh token that was tried still matches what's currently stored -- if it doesn't, another concurrent request already won the race and stored a fresh, valid pair, so the losing request now just uses that instead of disconnecting the store. Found via live testing: placing several real orders back-to-back on a demo store reproduced the race and silently zeroed out tax on every one of them.

2026-08-22 - version 1.4.2
* Fix - "Enable tax calculations at checkout" could not actually be turned on. The click handler read the checkbox's `checked` state to decide whether the merchant was turning it on or off, but the browser had already flipped that property to the target value before dispatching the click event -- so the code inverted the actual direction. This produced backwards confirmation-dialog copy ("Turn off live tax calculations?" when switching it on) and, since the inverted direction was also what got submitted to save, confirming through the dialog never actually enabled checkout tax calculation. Found via a live end-to-end test against a real WooCommerce store ahead of the WooCommerce Marketplace submission -- this shipped in 1.4.1 undetected.

2026-08-20 - version 1.4.1
* Tweak - Expanded the External Services disclosure to explicitly describe real-time checkout data transmission (billing/shipping address and cart contents) when checkout tax calculation is enabled, not just the background compliance sync

2026-07-09 - version 1.4.0
* Fix - Disconnecting from the Taxually dashboard now properly disconnects the plugin too. Previously the plugin's "Connected" status was a pure local check that never re-validated against Clearvo, so a dashboard-initiated disconnect (which revokes the plugin's OAuth grant server-side, see the companion Taxually-Einvoicing change) went unnoticed indefinitely -- the settings page kept showing Connected with a token that no longer worked for anything. The plugin now detects a 401 (data calls) or invalid_grant (token refresh) and clears its own local connection state when it sees one, on the tax calculation call, the tax-calc toggle push, the product sync push, and any token refresh attempt.

2026-07-09 - version 1.3.1
* Fix - Tax Radar / registrations statuses now use WooCommerce's actual colored order-status classes (blue/green/tan/red) instead of two that don't exist in WooCommerce's own stylesheet ("status-cancelled", "status-pending"), which made an urgent "Registration required" status render identically to a routine "Monitoring" one

2026-07-09 - version 1.3.0
* Fix - "Enable tax calculations at checkout" is now a native checkbox instead of a custom toggle-switch widget, matching every other setting on this page and WooCommerce Marketplace UX guidance to use existing WooCommerce/WordPress components rather than custom-built ones
* Fix - Both Disconnect actions now show the same styled confirmation dialog as the checkout toggle, instead of mixing a plain browser confirm() for one and a styled dialog for the other
* Fix - Tax Radar, tax registrations, and product classification statuses now render via WooCommerce's own <mark class="order-status"> component instead of custom badge CSS
* Tweak - "Debug Mode" is now "Debug mode" for consistent sentence casing with every other setting label
* Tweak - "(sandbox)" next to the connection status now reads "(test mode)"
* Tweak - The setup admin notice leads with the action to take instead of a "get started" preamble
* Tweak - Reworded a checkout tax-line-item message to active voice

2026-07-09 - version 1.2.4
* Fix - Removed an unused REST endpoint that returned the store's WooCommerce API secret to any shop-manager-capable user; nothing in the plugin or Taxually's backend ever called it -- the real legacy connection flow generates this key locally and pushes it to Taxually, it never gets pulled back out

2026-07-08 - version 1.2.3
* Fix - Renamed the plugin's main file and text domain from taxually-for-woocommerce to taxually, matching the actual live WordPress.org plugin slug -- updating from an older version no longer changes the plugin's file path, so existing installs stay active instead of silently deactivating on update

2026-07-08 - version 1.2.2
* Fix - Corrected nonce-verification lint suppressions in the tax calculation module that WordPress's Plugin Check tool was flagging; no behavior change

2026-07-08 - version 1.2.1
* Fix - The live-checkout toggle now syncs to your Taxually dashboard, so it correctly shows whether tax calculation is enabled for this store
* Fix - Clicking Disconnect (or deleting the plugin) now tells your Taxually dashboard, so it stops showing as connected

2026-07-07 - version 1.2.0
* New - Automatic tax calculation at checkout (VAT, GST, US sales tax) — classic and Blocks checkout both supported
* New - Automatic background product classification, no manual tax-code mapping required
* New - Tax registrations summary and free Tax Radar (economic-nexus threshold tracking) on the settings page
* New - Imports an existing customer VAT/tax ID from EU VAT Number, EU VAT for WooCommerce, or Germanized if present, so repeat customers don't have to re-enter it
* New - Partial and full refund support, kept in sync with Taxually's compliance records
* New - Enable/disable audit trail (who, when) for the live-checkout toggle
* Fix - Debug-mode activity log for diagnosing calculation issues on hosts where debug.log isn't reachable

2026-06-04 - version 1.0.1
* Fix - Declared compatibility with WordPress 7.0
* Fix - Updated license header to use valid GPL SPDX identifier (GPL-3.0-or-later)
* Fix - Corrected text domain to match plugin slug (taxually-for-woocommerce)
* Fix - Added function existence checks to prevent fatal errors when multiple plugin versions are present

2026-03-18 - version 1.0.0
* New - Initial release
* New - Secure OAuth 2.0 with PKCE authentication flow
* New - WooCommerce REST API integration
* New - Connection status dashboard
* New - HPOS compatibility
