Adobe Commerce Developer Expert study materials
Adobe Commerce Developer Expert study resources

Plugin Ordering Conflict on Price Calculation

Adobe Commerce Developer Expert · Put your knowledge to work in context.

All case studies

Plugin Ordering Conflict on Price Calculation

A module adds a beforeGetPrice plugin on Magento\Catalog\Model\Product\Type\Price to apply a loyalty discount. A second, third-party module adds an afterGetPrice plugin on the same class to round to the nearest cent and log the result. During integration testing, the loyalty discount is applied after rounding, so a $10.005 price rounds to $10.01 and then the discount yields $9.01 instead of the expected $9.00. Both modules declare no sort order. The third-party module cannot be modified. The team needs deterministic ordering and a test that catches the regression.

1. Explain how plugin execution order is determined here and why the observed result occurs.

Read the worked answer

Plugins on the same method execute in the order defined by sortOrder in di.xml, then by module load order (the order modules appear in app/etc/config.php, driven by dependency declarations) when sortOrder is equal. With no sortOrder, both default to a value and the tie is broken by module sequence.

before plugins run before the original method; after plugins run after it and receive the original result. So the before loyalty plugin always wraps the original getPrice, and the after rounding plugin transforms whatever the original returned. The interaction that matters is which value each sees. If the discount is applied in before by modifying arguments, the original returns the discounted price and rounding then rounds the discounted value - but if the rounding plugin was ordered such that it rounds the pre-discount value first, the discount applies to the rounded number, producing $9.01. The missing sortOrder makes the tie-break accidental.

2. Propose a fix that does not touch the third-party module, and describe the test.

Read the worked answer

Set an explicit sortOrder on the loyalty plugin's beforeGetPrice in the first module's di.xml so its ordering relative to the third-party plugin is deterministic. Because before and after are different interceptor types, the cleanest fix is to make the discount a after plugin with a sortOrder lower than the third-party's after plugin, so rounding runs last on the already-discounted price. Confirm the third-party plugin's effective sortOrder (read its di.xml) and place ours below it.

Test: an integration test that resolves the price model through the object manager (so interceptors are active), sets a price of $10.005 with the loyalty rule, and asserts the final value is $9.00. Assert both the discounted-then-rounded path and a second fixture where no discount applies ($10.01). Add a unit test asserting the plugin's sortOrder is set, so a future refactor that drops it fails fast.