WooCommerce: Shipping Method Not Updating After Partial Refund – How to Recalculate Shipping Costs Dynamically?

I'm working on a WooCommerce store where we offer free shipping for orders over €100. However, when a partial refund is processed from an order with multiple products and for example 1 product is returned and the total order value drops below €100, the shipping method is not recalculated. The "Free Shipping" option remains, but it should revert to a flat rate of €9 for orders under €100. EXAMPLE: Product 1 costs 33€ Product 2 costs 44€ Product 3 costs 55€ Total order price: 132€ = Freeshipping Client requests to return product 2 so the total value changes under the 100€ on the order. Total order price: 88€ + 9€ standard shippingcosts. But now it keeps saying freeshipping when I return 1 product what is incorrect. add_action('woocommerce_order_refunded', 'adjust_shipping_after_refund', 10, 2); function adjust_shipping_after_refund($order_id, $refund_id) { $order = wc_get_order($order_id); $total_sale_price = 0; // Calculate new total after refund foreach ($order->get_items() as $item_id => $item) { $product = $item->get_product(); $quantity = $item->get_quantity(); $sale_price = $product->get_sale_price(); if ($sale_price) { $total_sale_price += $sale_price * $quantity; } else { $total_sale_price += $product->get_regular_price() * $quantity; } } // If the total is under €100, apply flat rate shipping if ($total_sale_price < 100) { foreach ($order->get_shipping_methods() as $shipping_item_id => $shipping_item) { if ($shipping_item->get_method_id() === 'free_shipping') { $order->remove_item($shipping_item_id); // Remove free shipping } } // Add flat rate shipping of €9 if (empty($order->get_shipping_methods())) { $rate = new WC_Shipping_Rate('flat_rate', 'Verzendkosten', 9, array(), 'flat_rate'); $shipping_item = new WC_Order_Item_Shipping(); $shipping_item->set_props(array( 'method_title' => $rate->get_label(), 'method_id' => $rate->get_method_id(), 'total' => wc_format_decimal($rate->get_cost()) )); $order->add_item($shipping_item); } // Recalculate totals $order->calculate_totals(); $order->save(); } } Here’s what I’ve tried so far: Hooked into the woocommerce_order_refunded action to recalculate shipping costs. Removed the free shipping method when the total is less than €100. Added a new shipping method with a flat rate of €9. What am I missing or doing wrong? How can I force WooCommerce to properly update the shipping method dynamically after a refund?

Comment (2)

Jese Leos

September 27, 2024

Verified user

The issue is that WooCommerce isn't automatically recalculating the shipping after a partial refund, leading to Free Shipping being incorrectly applied when the total falls below €100. The solution involves adjusting the total and shipping methods dynamically after a refund. Here's the updated code: add_action('woocommerce_order_refunded', 'adjust_shipping_after_refund', 10, 2); function adjust_shipping_after_refund($order_id, $refund_id) { $order = wc_get_order($order_id); // Use WooCommerce's built-in method to get the total after refunds $total_sale_price = $order->get_total(); // This includes refunds automatically // If the total is under €100, switch to flat rate shipping if ($total_sale_price < 100) { // Remove free shipping if it's applied foreach ($order->get_shipping_methods() as $shipping_item_id => $shipping_item) { if ($shipping_item->get_method_id() === 'free_shipping') { $order->remove_item($shipping_item_id); } } // Add flat rate shipping of €9 if (empty($order->get_shipping_methods())) { $rate = new WC_Shipping_Rate('flat_rate', 'Flat Rate Shipping', 9, array(), 'flat_rate'); $shipping_item = new WC_Order_Item_Shipping(); $shipping_item->set_props(array( 'method_title' => $rate->get_label(), 'method_id' => $rate->get_method_id(), 'total' => wc_format_decimal($rate->get_cost()) )); $order->add_item($shipping_item); } // Recalculate the totals after adding the new shipping cost $order->calculate_totals(); $order->save(); } }

Jese Leos

September 27, 2024

Verified user

I have found the answer on my question: <?php // Zorg ervoor dat de verzendkosten worden aangepast na een terugbetaling add_action('woocommerce_order_refunded', 'adjust_shipping_after_refund', 10, 2); function adjust_shipping_after_refund($order_id, $refund_id) { $order = wc_get_order($order_id); $refund = wc_get_order($refund_id); if (!$order || !$refund) { return; } $total_sale_price = 0; $total_refunded_sale_price = 0; $free_shipping_removed = false; // Pad naar je eigen logbestand in de wp-content map $log_file = WP_CONTENT_DIR . '/verzendkost_update.log'; // Log de initiële status van de bestelling en het totaal error_log("Start processing refund for Order ID: {$order_id}. Refund ID: {$refund_id}" . PHP_EOL, 3, $log_file); // Bereken het totaalbedrag van de bestelling op basis van line items foreach ($order->get_items('line_item') as $item_id => $item) { $product = $item->get_product(); if (!$product) { error_log("Product niet gevonden voor item ID $item_id in order ID $order_id." . PHP_EOL, 3, $log_file); continue; } $quantity = $item->get_quantity(); $line_total = $item->get_total(); // Haal het lijntotaal op $total_sale_price += $line_total; // Log details van elk product en de prijs error_log("Product: {$product->get_name()}, Line Total: {$line_total}, Quantity: {$quantity}, Subtotal: {$line_total}" . PHP_EOL, 3, $log_file); } // Controleer welke itemtypes aanwezig zijn in de refund foreach ($refund->get_items() as $item_id => $item) { $item_type = $item->get_type(); error_log("Refund Item Type: {$item_type}" . PHP_EOL, 3, $log_file); } // Bereken het totaalbedrag van de terugbetalingen op basis van line items in de refund foreach ($refund->get_items('line_item') as $refund_item_id => $refund_item) { $refund_product = $refund_item->get_product(); if (!$refund_product) { error_log("Product niet gevonden voor refund item ID $refund_item_id in order ID $order_id." . PHP_EOL, 3, $log_file); continue; } $refunded_quantity = abs($refund_item->get_quantity()); $refund_total = $refund_item->get_total(); // Dit is een negatieve waarde $refund_subtotal = abs($refund_total); // Absolute waarde voor berekening $total_refunded_sale_price += $refund_subtotal; // Log details van elk refund item en de prijs error_log("Refund Product: {$refund_product->get_name()}, Refund Total: {$refund_total}, Refunded Quantity: {$refunded_quantity}, Refund Subtotal: {$refund_subtotal}" . PHP_EOL, 3, $log_file); } // Bereken het netto totaalbedrag na terugbetalingen $net_sale_price = $total_sale_price - $total_refunded_sale_price; // Log het netto totale bedrag error_log("Net sale price after refund: {$net_sale_price}" . PHP_EOL, 3, $log_file); // Controleer of gratis verzending moet worden verwijderd en de verzendkosten van €9 moeten worden toegevoegd if ($net_sale_price < 100) { error_log("Net sale price is below 100 EUR. Checking for free shipping..." . PHP_EOL, 3, $log_file); // Log alle huidige verzendmethoden foreach ($order->get_shipping_methods() as $shipping_item) { $method_id = $shipping_item->get_method_id(); $instance_id = $shipping_item->get_instance_id(); $method_title = $shipping_item->get_method_title(); error_log("Shipping Method Found - ID: {$method_id}, Instance ID: {$instance_id}, Title: {$method_title}" . PHP_EOL, 3, $log_file); } // Verwijder gratis verzending door te controleren op method_title foreach ($order->get_shipping_methods() as $item_id => $item) { if ($item->get_method_title() === 'Gratis verzending') { $order->remove_item($item_id); // Verwijder gratis verzending $free_shipping_removed = true; // Zet de vlag voor het verwijderen error_log("Free shipping removed." . PHP_EOL, 3, $log_file); } } if ($free_shipping_removed) { // Controleer of er al een flat rate shipping method bestaat $flat_rate_exists = false; foreach ($order->get_shipping_methods() as $item) { if ($item->get_method_id() === 'flat_rate') { $item->set_total(9); $item->save(); $flat_rate_exists = true; error_log("Existing flat rate shipping updated to €9." . PHP_EOL, 3, $log_file); } } if (!$flat_rate_exists) { // Voeg een nieuwe flat rate shipping method toe $shipping_item = new WC_Order_Item_Shipping(); $shipping_item->set_method_title("Verzendkosten"); $shipping_item->set_method_id("flat_rate"); $shipping_item->set_total(9); $order->add_item($shipping_item); $order->save(); error_log("Flat rate shipping of €9 added." . PHP_EOL, 3, $log_file); } // Herbereken de bestelling en sla deze op $order->calculate_totals(); $order->save(); // Log de nieuwe bestellingstotalen error_log("New order totals after adding shipping: " . print_r($order->get_order_item_totals(), true) . PHP_EOL, 3, $log_file); } else { error_log("Free shipping was not removed." . PHP_EOL, 3, $log_file); } } else { error_log("Net sale price is above 100 EUR. No shipping changes required." . PHP_EOL, 3, $log_file); } } ?> I use _sale_price (that I didn't mention earlier why I used it). I use this because the _regular_price isn't our shop price. But further on all is the same as for my code. You just have to change it to _regulare_price if you use this. The above code works correclty.

You’ll be in good company