Round Cart Total
Rounding the WooCommerce cart total to the nearest whole number is a common requirement — especially for stores that want clean, customer-friendly totals. The standard approach is to use the woocommerce_calculated_total filter. However, when Advanced Dynamic Pricing for WooCommerce is active, a hook execution order conflict can cause the plugin’s price calculations to run after your rounding logic, overwriting the rounded total with an unrounded one.
This article explains how to implement cart total rounding correctly so it works seamlessly alongside the plugin’s pricing rules.
The Problem: Hook Execution Order Conflict
When Advanced Dynamic Pricing recalculates cart totals, it hooks into woocommerce_after_calculate_totals. If your rounding code runs at the default priority and the plugin’s recalculation fires afterwards, the plugin will overwrite your rounded total — making the rounding appear to have no effect.
The solution is a two-part approach: keep your existing rounding code and add a companion snippet that forces the plugin to recalculate before your rounding runs, and then allows the rounding hooks to fire at the correct moment.
Code Sample 1 — Your Existing Rounding Code
If you already have a rounding filter in place, make sure it looks like this. If you don’t have one yet, add it to your theme’s functions.php or the Code Snippets plugin:
|
1 2 3 4 5 |
add_filter( 'woocommerce_calculated_total', 'custom_calculated_total' ); function custom_calculated_total( $total ) { $total = round($total); return ceil($total); } |
Code Sample 2 — Fix for Correct Rounding with Advanced Dynamic Pricing
Add this alongside the snippet above. This ensures the plugin’s internal totals recalculation fires before your rounding logic takes effect:
|
1 2 3 4 5 6 7 8 |
add_action("woocommerce_after_calculate_totals", function ($wcCart) { $wcNoFilterWorker = new ADP\BaseVersion\Includes\WC\WcNoFilterWorker(); $wcNoFilterWorker->calculateTotals($wcCart, $wcNoFilterWorker::FLAG_ALLOW_TOTALS_HOOKS); }, PHP_INT_MAX, 1); add_filter('wdp_calculate_totals_hook_priority', function($priority){ return PHP_INT_MAX - 1;}); |
Code Explained (for Developers)
Code Sample 1 — Rounding logic
| Element | Description |
|---|---|
woocommerce_calculated_total | A WooCommerce filter that passes the fully calculated cart total as a float. Returning a modified value from this filter sets the final cart total. |
round( $total ) | Rounds the total to the nearest whole number using standard PHP rounding (e.g. 12.5 → 13, 12.4 → 12). |
ceil( $total ) | After rounding, ceil() ensures the result is always rounded up to the next whole number if any fractional part remains. Since round() already returns a whole number here, ceil() acts as a safety net. You can replace ceil() with floor() if you prefer always rounding down, or remove it entirely to use standard rounding only. |
Code Sample 2 — Execution order fix
| Element | Description |
|---|---|
woocommerce_after_calculate_totals | A WooCommerce action that fires after the cart totals have been calculated. Advanced Dynamic Pricing uses this hook to apply its pricing rules to the cart. |
ADP\BaseVersion\Includes\WC\WcNoFilterWorker | An internal plugin class responsible for recalculating cart totals while optionally allowing other hooks to fire during the process. |
calculateTotals( $wcCart, FLAG_ALLOW_TOTALS_HOOKS ) | Triggers the plugin’s full cart totals recalculation. The FLAG_ALLOW_TOTALS_HOOKS flag explicitly permits downstream hooks — including your woocommerce_calculated_total rounding filter — to run after the plugin finishes. |
PHP_INT_MAX | Sets the action priority to the highest possible integer value in PHP, ensuring this recalculation runs last among all woocommerce_after_calculate_totals callbacks — after WooCommerce core and all other plugins. |
wdp_calculate_totals_hook_priority | A custom filter provided by Advanced Dynamic Pricing to control the priority at which it registers its own woocommerce_after_calculate_totals hook. |
return PHP_INT_MAX - 1 | Sets the plugin’s own calculation priority to one step below PHP_INT_MAX, so it fires just before the custom recalculation in the action above, establishing the correct order: plugin calculates → hooks fire → rounding runs. |
Key concept: The two priority values work together to create a predictable execution order:
- Advanced Dynamic Pricing calculates at priority
PHP_INT_MAX - 1- The
calculateTotals()call withFLAG_ALLOW_TOTALS_HOOKSfires at priorityPHP_INT_MAXwoocommerce_calculated_totalfires, applying your roundingWithout this setup, the plugin’s recalculation can fire after step 3, silently undoing the rounding.
Rounding Behavior Reference
Depending on your needs, you can adjust the rounding logic in Code Sample 1:
| Approach | Code | Example ($12.60) |
|---|---|---|
| Always round up | return ceil( $total ); | $13.00 |
| Always round down | return floor( $total ); | $12.00 |
| Standard rounding | return round( $total ); | $13.00 |
| Round to nearest 0.50 | return round( $total * 2 ) / 2; | $12.50 |
How to Apply This Code
- Open Appearance → Theme File Editor in your WordPress admin, or open the Code Snippets plugin.
- Paste both snippets into your theme’s
functions.phpor create a dedicated snippet for each. - Save and visit your Cart and Checkout pages.
- Add products, apply a discount rule, and verify the cart total is correctly rounded.
⚠️ If you have an existing
woocommerce_calculated_totalrounding filter from a different source, check that it doesn’t conflict with Code Sample 1. Only one rounding filter should be active at a time.
When Should You Use This?
- Your cart total rounding stopped working after installing or enabling Advanced Dynamic Pricing rules.
- You want to display clean, whole-number totals to customers at checkout.
- Your store operates in a currency or region where sub-cent pricing is not conventional (e.g. Japanese Yen, Swiss Franc rounding rules).
- You use a payment gateway that doesn’t support decimal amounts and requires whole-number order totals.
- Your rounding code works correctly on a plain WooCommerce install but breaks when pricing rules are active.