Skip to content

fix: show the surcharge net or gross per the store's tax display setting - #418

Merged
dgjlindsay merged 2 commits into
stagingfrom
doug/surcharge-tax-display-parity
Sep 2, 2026
Merged

fix: show the surcharge net or gross per the store's tax display setting#418
dgjlindsay merged 2 commits into
stagingfrom
doug/surcharge-tax-display-parity

Conversation

@dgjlindsay

Copy link
Copy Markdown
Contributor

Every buyer- and merchant-facing surcharge amount rendered net regardless of the store's tax display setting, so a store configured to show gross prices showed a net surcharge beside gross everything else.

Display-only fix. Net and tax are already snapshotted at each entity's creation time, so nothing about totals arithmetic changes — the surcharge tax is already in the Tax line and the gross already in the grand total, exactly as core carries "Subtotal (Incl. Tax)" alongside a separate Tax row.

Service\Order\SurchargeDisplay resolves the mode from tax/cart_display/price (checkout, cart, term chips) and tax/sales_display/price (order/invoice/credit-memo views, emails, PDFs), read through Magento\Tax\Model\Config as core's own Subtotal and Shipping totals blocks do. Each renderer then picks net or gross; "Both" emits the paired excl/incl rows core uses for those same totals.

The two term-preview endpoints now return net and gross per term plus the mode, so a chip can render either; their duplicated per-term loops collapse into one Service\Order\TermSurchargePreview.

Every buyer- and merchant-facing surcharge amount was rendered net
regardless of configuration, so a store set to show gross prices showed
a net surcharge beside gross everything else.

Net and tax were already snapshotted at each entity's creation time, so
this is display-only: Service\Order\SurchargeDisplay reads the store's
"Display Prices" switch for the cart and sales tiers and each renderer
picks accordingly. "Both" emits the paired excl/incl rows core uses for
Subtotal and Shipping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@claude claude 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.

First pass on this PR. No unresolved review threads to triage.

Reviewed the display-mode plumbing across SurchargeDisplay, the checkout/quote total collector, the sales (order/invoice/creditmemo) block and PDF renderer, the cart-totals API plugin, and the new TermSurchargePreview service, plus the frontend JS/knockout wiring.

Notes (non-blocking):

  • The "Both" mode in Model\Total\Surcharge::fetch() and Model\Pdf\Total\Surcharge returns a list of two segment arrays from a single collector rather than one assoc array. This mirrors the pattern Magento\Weee\Model\Total\Quote\Weee::fetch() uses for its own multi-line totals, so TotalsReader/TotalsConverter should split it into two segments correctly — worth confirming with a manual checkout run in "Both" mode since it's the one behavior that can't be fully pinned by the PHP unit tests here (they assert the collector's own return shape, not what core's reader does with it).
  • SurchargeTaxCalculator::resolveRateForQuote() reads the tax engine's own declared rate for a synthetic 100.0 basis rather than deriving anything from a ratio, so it doesn't run into the tax/net pitfall called out in AGENTS.md for the order/tax composition path — this is a preview-only estimate feeding the term chips, not the authoritative total.
  • New constructor dependencies (SurchargeDisplay on the total collector, sales block, and PDF renderer) are all concrete, autowirable classes, so no di.xml changes are needed and nothing here trips the "optional constructor arg isn't autowired" trap from AGENTS.md.

No security, data-loss, or breaking-API issues found. Test coverage (PHP unit + JS) looks thorough for the mode matrix (excl/incl/both) across every rendering surface.

Ready for human approval ✅

Per-term failure logging moved into TermSurchargePreview, and pin the
predicate core's TotalsReader uses to split a multi-row fetch().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@claude claude 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.

Re-reviewed the full diff at the current head (a commit landed after the earlier pass here). No unresolved review threads to triage.

Traced the display-mode plumbing end to end again: SurchargeDisplay resolution → Model\Total\Surcharge::fetch() (single segment vs. paired two_surcharge/two_surcharge_incl list in "Both") → the sales block/PDF renderer → the cart-totals API plugin → the two term-preview webapi endpoints now unified behind TermSurchargePreview → the knockout surcharge.js model's displayedTermSurcharges(). Also checked SurchargeTaxCalculator::resolveRateForQuote() — it reads the engine's declared rate off a synthetic 100.0 basis rather than a tax/net ratio, so it stays clear of the composition-path pitfall in AGENTS.md, and it's explicitly preview-only (the authoritative total collector path is untouched by this addition). Confirmed the checkout-session tax value the new fetch() branch reads (getTwoSurchargeTax()) is set in collect()/cleared in the zero-surcharge path, so the "Both"/"Incl" segment values aren't reading a stale or unset field.

No security, data-loss, or breaking-API issues found on this pass either. The one item worth a manual checkout smoke-test before merge (carried over from the prior review, still applicable since Model\Total\Surcharge::fetch() is unchanged in that respect): confirm "Both" mode's two-segment array is split correctly by core's TotalsReader/TotalsConverter, since that's the one behavior the PHP unit tests can pin the collector's own output for but not core's consumption of it.

Ready for human approval ✅

@dgjlindsay

Copy link
Copy Markdown
Contributor Author

Adversarial review — clean round

Round 1 found one real defect and one unverified assumption; round 2 found nothing new.

Fixed in 50a0139: TermSelection still carried a LogRepository it no longer used — the per-term failure logging moved into TermSurchargePreview. Dependency removed.

The "Both" two-segment return, verified rather than assumed. The open question was whether core actually splits a collector's list return into separate segments. Magento\Quote\Model\Quote\TotalsReader::convert() branches on count(array_column($total, 'code')) > 0. Confirmed by invoking that method on a real 2.4.9 install with both shapes:

BOTH   -> list of 2      two_surcharge = 100, two_surcharge_incl = 121
SINGLE -> single         two_surcharge = 121

An assoc array of scalars yields [] from array_column, so the single-row modes cannot be mis-split. Both directions are now pinned by assertion in Test/Unit/Model/Total/SurchargeTest.php.

Checked and cleared:

  • addTotal($row, 'two_surcharge_excl') resolves against the code addTotalBefore just inserted, so the incl row lands directly after the excl row — same ordering core produces for Subtotal.
  • fetch() now dereferences $quote->getStore(), but only after the $amount <= 0 early return, and collect() already dereferences it on the same path for any quote that has a surcharge. No new failure class.
  • getTwoSurchargeTax() is written in collect() and zeroed in every clear path, so no stale tax can reach a gross figure.
  • Grand-total arithmetic is untouched: gross was already added to the grand total and surcharge tax already folded into the Tax line, independent of what the row displays.
  • Plugin/Api/SurchargeExtensionAttributes.php deliberately left alone — a data contract with separate net and tax fields, not a display choice.

Mutation-checked — each of these fails the suite: pick() always returning net; dropping the BOTH branch from either tier; preview gross equalling net; the JS display map always returning net.

Out of scope, worth knowing: magento-hyva-extension reads the two_surcharge segment from its own repo. In excl/incl modes it gets the correct value with no change. In "Both" it would render only the excl row until it learns about two_surcharge_incl.

Review by Claude

@dgjlindsay
dgjlindsay merged commit 62fd38f into staging Sep 2, 2026
30 checks passed
@dgjlindsay
dgjlindsay deleted the doug/surcharge-tax-display-parity branch September 2, 2026 19:24
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