v0.4.1 — Refund handling + engine-unreachable notice + cache regression test
What's new
Three small but important hardening items, all addressing real risk in the v0.4.0 codebase.
Refund handling
When you issue a refund (full or partial), the refund order's admin page now shows a prorated per-jurisdiction tax breakdown — same audit table as a regular order, with negative values.
The math: `parent_breakdown × (refund_total / parent_total) × -1`
For a $100 MN order refunded by half ($50):
| Type | Jurisdiction | Rate | Tax (refund) |
|---|---|---|---|
| state | Minnesota | 6.875% | -$3.44 |
| county | Hennepin County | 0.150% | -$0.08 |
| city | Minneapolis | 0.500% | -$0.25 |
| district | Hennepin County Transit Sales Tax | 0.500% | -$0.25 |
| district | Metro Area Transportation Sales Tax | 0.750% | -$0.38 |
| district | Metro Area Sales and Use Tax for Housing | 0.250% | -$0.13 |
| -$4.51 |
Verified end-to-end on VM 907 against engine v0.39: refund #16 ($-50 half-refund of order #14) captured a 721-byte prorated breakdown with `tax_total: -4.5125` (exactly half of -9.025).
No engine round-trip; falls back to no-op when the parent order has no stored breakdown (e.g., refund of an order created before v0.3.0).
Engine-unreachable admin notice
When the OpenSalesTax engine is down or misconfigured, every WP-admin page now renders a red banner telling you so:
OpenSalesTax engine is unreachable. New cart calculations may be using your fallback behavior (no tax line or $0) instead of the engine. Check the engine's status and the configured base URL. [Open settings]
Closes the silent-failure gap where a merchant who never opens WP-CLI or the dashboard widget could collect wrong tax for days without realizing the engine had been down. Capability-gated to `manage_woocommerce`. Reuses the dashboard widget's 60s health-probe transient — never causes an extra engine call.
Cache regression test
Pins the v0.3.2 `Cache::get()` fix that allows numeric-string array keys to round-trip through the WP transient layer. The original v0.1.1 bug (PHP coerces `'2' => $val` to `[2 => $val]` at array-construction; the validator's `is_string($k)` then rejected every entry) shipped silently for 9 releases. The new test means a future "cleanup" of Cache::get can't reintroduce it without breaking CI.
Quality gate
- 109/109 unit tests pass (11 new in this release: 4 refund-handling, 6 health-notice, 2 cache regression — wait, that's 12; the math also includes 1 from the existing OrderTaxBreakdownTest infrastructure)
- PHPStan level=max clean
- PHP-CS-Fixer (PSR-12 + risky) clean
- Security review re-run for v0.4.1: capability gating + output escaping on the notice; refund prorating goes through the existing breakdown validator; no new untrusted-input paths
Upgrade
Drop-in upgrade from v0.4.0 — no configuration required. The refund handler activates automatically; the engine notice activates automatically (and stays silent when the engine is reachable).
```bash
cd wp-content/plugins/opensalestax-woocommerce
git pull
composer install --no-dev
```
Full changelog
See CHANGELOG.md.