Commit b786163a6c3 for woocommerce
commit b786163a6c30b277d8268fd218b2463077b761c3
Author: Daniel Kam <hey@danielk.am>
Date: Wed Oct 14 23:12:48 2026 +0800
Use filterable coupon-removed message when removing a coupon via AJAX (#69271)
WC_AJAX::remove_coupon() hardcoded "Coupon has been removed.", bypassing
WC_Coupon::get_coupon_message() and the woocommerce_coupon_message filter.
It now uses WC_Coupon::add_coupon_message( WC_COUPON_REMOVED ).
Fixes #31198
Co-authored-by: Daniel Kam <danielkam@Daniels-MacBook-Pro.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/31198-filterable-coupon-removed-message b/plugins/woocommerce/changelog/31198-filterable-coupon-removed-message
new file mode 100644
index 00000000000..aa79184d14d
--- /dev/null
+++ b/plugins/woocommerce/changelog/31198-filterable-coupon-removed-message
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Use the filterable coupon-removed message from `WC_Coupon::get_coupon_message()` when removing a coupon via AJAX.
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index e54da4f5c7f..95fb13f26b3 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -322,7 +322,7 @@ class WC_AJAX {
wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' );
} else {
WC()->cart->remove_coupon( $coupon );
- wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
+ ( new WC_Coupon( $coupon ) )->add_coupon_message( WC_Coupon::WC_COUPON_REMOVED );
}
wc_print_notices();
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index b7536ca0986..4c18626fad9 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -146,6 +146,71 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
$this->assertEquals( $response['data']['message'], 'There was an error generating your API Key.' );
}
+ /**
+ * @testdox Removing a coupon via AJAX shows the coupon-removed message from WC_Coupon::get_coupon_message().
+ */
+ public function test_remove_coupon_uses_coupon_removed_message(): void {
+ $coupon = WC_Helper_Coupon::create_coupon( 'ajaxremove' );
+ WC()->cart->empty_cart();
+ WC()->cart->add_to_cart( WC_Helper_Product::create_simple_product()->get_id() );
+ WC()->cart->apply_coupon( $coupon->get_code() );
+ wc_clear_notices();
+
+ $response = $this->do_remove_coupon_request( $coupon->get_code() );
+
+ $this->assertStringContainsString( 'Coupon code removed successfully.', $response, 'The default coupon-removed message should be shown.' );
+ $this->assertStringNotContainsString( 'Coupon has been removed.', $response, 'The old hardcoded message should no longer be shown.' );
+ $this->assertNotContains( $coupon->get_code(), WC()->cart->get_applied_coupons(), 'The coupon should be removed from the cart.' );
+ }
+
+ /**
+ * @testdox Removing a coupon via AJAX respects the woocommerce_coupon_message filter.
+ */
+ public function test_remove_coupon_message_is_filterable(): void {
+ $coupon = WC_Helper_Coupon::create_coupon( 'ajaxremovefiltered' );
+ WC()->cart->empty_cart();
+ WC()->cart->add_to_cart( WC_Helper_Product::create_simple_product()->get_id() );
+ WC()->cart->apply_coupon( $coupon->get_code() );
+ wc_clear_notices();
+
+ $filter_args = array();
+ $filter = function ( $msg, $msg_code, $filtered_coupon ) use ( &$filter_args ) {
+ $filter_args = array( $msg_code, $filtered_coupon->get_code() );
+ return WC_Coupon::WC_COUPON_REMOVED === $msg_code ? 'Custom coupon removed message.' : $msg;
+ };
+ add_filter( 'woocommerce_coupon_message', $filter, 10, 3 );
+
+ try {
+ $response = $this->do_remove_coupon_request( $coupon->get_code() );
+ } finally {
+ remove_filter( 'woocommerce_coupon_message', $filter, 10 );
+ }
+
+ $this->assertStringContainsString( 'Custom coupon removed message.', $response, 'The filtered message should be shown.' );
+ $this->assertSame( array( WC_Coupon::WC_COUPON_REMOVED, $coupon->get_code() ), $filter_args, 'The filter should receive the removed code and the removed coupon.' );
+ }
+
+ /**
+ * Runs the woocommerce_remove_coupon AJAX action and returns its output.
+ *
+ * @param string $coupon_code Coupon code to remove.
+ * @return string
+ */
+ private function do_remove_coupon_request( string $coupon_code ): string {
+ $_POST['security'] = wp_create_nonce( 'remove-coupon' );
+ $_POST['coupon'] = $coupon_code;
+
+ try {
+ $this->_handleAjax( 'woocommerce_remove_coupon' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ } finally {
+ unset( $_POST['security'], $_POST['coupon'] );
+ }
+
+ return (string) $this->_last_response;
+ }
+
/**
* @testdox Saving a new shipping class with a blank slug generates and returns its persisted slug.
*/