Commit 5b95a0ba269 for woocommerce
commit 5b95a0ba269084d1200a1033087fac73fa54c181
Author: SH Sajal Chowdhury <72102985+shsajalchowdhury@users.noreply.github.com>
Date: Wed Sep 2 20:04:28 2026 +0600
Fix: Add woocommerce_removed_coupon action hook to order class (#64361)
* Fix: Add woocommerce_removed_coupon action hook to order class
Add do_action('woocommerce_removed_coupon') in WC_Order::remove_coupon()
for parity with WC_Cart::remove_coupon() which already has this hook.
The order version also passes the order instance as a second parameter
for additional context, which the cart version does not have.
Fixes #36114
* Fix: Use order-specific hook name to avoid cart listener cross-talk
Rename woocommerce_removed_coupon to woocommerce_order_removed_coupon
to match the pattern used by apply_coupon() which uses
woocommerce_order_applied_coupon. This prevents cart-scoped listeners
(like WC_Cart_Session::set_session) from firing during order coupon
removal.
Per CodeRabbit review feedback.
* Add changelog entry
* Address review feedback: pass WC_Coupon object and add test
- Change hook first parameter from $code (string) to $coupon_object (WC_Coupon)
for consistency with woocommerce_order_applied_coupon hook
- Update docblock to match apply_coupon hook format (param alignment, @since placement)
- Add test_remove_coupon_fires_order_removed_coupon_hook verifying:
- Hook fires when coupon is removed from order
- First parameter is WC_Coupon instance with correct code
- Second parameter is the same WC_Order instance
- Coupon is actually removed from order items
- Update changelog entry with correct hook name
* Wrap test assertions in try/finally to prevent hook leak on failure
Ensures remove_all_actions('woocommerce_order_removed_coupon') always
runs even if an assertion fails, preventing hook leakage into other tests.
* Fix lint
* Fix PHPStan
* Fix Lint
---------
Co-authored-by: Tom Cafferkey <tjcafferkey@gmail.com>
diff --git a/plugins/woocommerce/changelog/pr-64361 b/plugins/woocommerce/changelog/pr-64361
new file mode 100644
index 00000000000..d9a11366752
--- /dev/null
+++ b/plugins/woocommerce/changelog/pr-64361
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Add woocommerce_order_removed_coupon action hook to WC_Order::remove_coupon() for parity with WC_Cart and the existing woocommerce_order_applied_coupon hook.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index ad074f6836c..102fafe9e0f 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -1874,6 +1874,16 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupon_object->decrease_usage_count( $this->get_user_id() );
$this->recalculate_coupons();
+ /**
+ * Action hook fired when a coupon is removed from an order.
+ *
+ * @param WC_Coupon $coupon_object The removed coupon object.
+ * @param WC_Order $order The current order object.
+ *
+ * @since 10.8.0
+ */
+ do_action( 'woocommerce_order_removed_coupon', $coupon_object, $this );
+
return true;
}
}
@@ -3163,7 +3173,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*
* @param string $total_html The formatted total COGS HTML.
* @param float $total The total COGS value.
- * @param WC_Order $order The order object.
+ * @param WC_Abstract_Order $order The order object.
*/
return apply_filters(
'woocommerce_order_cogs_total_value_html',
diff --git a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
index d50e0419526..53b78cab8a9 100644
--- a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
+++ b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
@@ -121,7 +121,8 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
)
);
- update_user_meta( $admin_id, 'billing_country', 'MV' ); // Different than customer's address and base location.
+ update_user_meta( $admin_id, 'billing_country', 'MV' );
+ // Different than customer's address and base location.
wp_set_current_user( $admin_id );
WC()->customer = null;
WC()->initialize_cart();
@@ -319,6 +320,48 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$this->assertEquals( $coupon_code, $coupon_info[1] );
}
+ /**
+ * Test remove_coupon fires woocommerce_order_removed_coupon hook with WC_Coupon object.
+ */
+ public function test_remove_coupon_fires_order_removed_coupon_hook() {
+ $coupon_code = 'remove_hook_test';
+ $coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
+ $order = WC_Helper_Order::create_order();
+ $order->set_status( OrderStatus::PROCESSING );
+ $order->save();
+
+ $order->apply_coupon( $coupon_code );
+ $this->assertCount( 1, $order->get_items( 'coupon' ) );
+
+ $hook_fired = false;
+ $hook_coupon = null;
+ $hook_order = null;
+
+ add_action(
+ 'woocommerce_order_removed_coupon',
+ function ( $coupon_obj, $order_obj ) use ( &$hook_fired, &$hook_coupon, &$hook_order ) {
+ $hook_fired = true;
+ $hook_coupon = $coupon_obj;
+ $hook_order = $order_obj;
+ },
+ 10,
+ 2
+ );
+
+ try {
+ $result = $order->remove_coupon( $coupon_code );
+
+ $this->assertTrue( $result );
+ $this->assertTrue( $hook_fired, 'woocommerce_order_removed_coupon hook did not fire.' );
+ $this->assertInstanceOf( WC_Coupon::class, $hook_coupon, 'First parameter should be a WC_Coupon instance.' );
+ $this->assertEquals( $coupon->get_code(), $hook_coupon->get_code(), 'Hook coupon code should match.' );
+ $this->assertSame( $order, $hook_order, 'Second parameter should be the same WC_Order instance.' );
+ $this->assertCount( 0, $order->get_items( 'coupon' ) );
+ } finally {
+ remove_all_actions( 'woocommerce_order_removed_coupon' );
+ }
+ }
+
/**
* Create a pending order with one $100 product whose line total was manually edited to $50.
*
@@ -766,7 +809,8 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$this->add_product_with_cogs_to_order( $order, 12.34, 2 );
$this->add_product_with_cogs_to_order( $order, 56.78, 3 );
- $fee = new WC_Order_Item_Fee(); // Example of line item without COGS.
+ $fee = new WC_Order_Item_Fee();
+ // Example of line item without COGS.
$order->add_item( $fee );
$calculated_value = $order->calculate_cogs_total_value();