diff --git a/plugins/woocommerce/changelog/issue-30104 b/plugins/woocommerce/changelog/issue-30104 new file mode 100644 index 00000000000..ac9273945ad --- /dev/null +++ b/plugins/woocommerce/changelog/issue-30104 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Log to order notes when coupons are removed or applied. diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js index 44e68b622e4..b70110994ee 100644 --- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js +++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js @@ -488,6 +488,13 @@ jQuery( function ( $ ) { if ( response.success ) { $( '#woocommerce-order-items' ).find( '.inside' ).empty(); $( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html ); + + // Update notes. + if ( response.data.notes_html ) { + $( 'ul.order_notes' ).empty(); + $( 'ul.order_notes' ).append( $( response.data.notes_html ).find( 'li' ) ); + } + wc_meta_boxes_order_items.reloaded_items(); wc_meta_boxes_order_items.unblock(); } else { @@ -524,6 +531,13 @@ jQuery( function ( $ ) { if ( response.success ) { $( '#woocommerce-order-items' ).find( '.inside' ).empty(); $( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html ); + + // Update notes. + if ( response.data.notes_html ) { + $( 'ul.order_notes' ).empty(); + $( 'ul.order_notes' ).append( $( response.data.notes_html ).find( 'li' ) ); + } + wc_meta_boxes_order_items.reloaded_items(); wc_meta_boxes_order_items.unblock(); } else { diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index ae3bbb8a5f1..951cb4dfab1 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -1285,9 +1285,11 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * Manual discounts are not affected; those are separate and do not affect * stored line totals. * - * @since 3.2.0 + * @since 3.2.0 + * @since 7.6.0 Returns a boolean indicating success. + * * @param string $code Coupon code. - * @return void + * @return bool TRUE if coupon was removed, FALSE otherwise. */ public function remove_coupon( $code ) { $coupons = $this->get_items( 'coupon' ); @@ -1299,9 +1301,12 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { $coupon_object = new WC_Coupon( $code ); $coupon_object->decrease_usage_count( $this->get_user_id() ); $this->recalculate_coupons(); - break; + + return true; } } + + return false; } /** diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php index f81f25d564c..e291c78a2a0 100644 --- a/plugins/woocommerce/includes/class-wc-ajax.php +++ b/plugins/woocommerce/includes/class-wc-ajax.php @@ -1255,13 +1255,23 @@ class WC_AJAX { throw new Exception( __( 'Invalid coupon', 'woocommerce' ) ); } - $order->remove_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $code = wc_format_coupon_code( wp_unslash( $coupon ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( $order->remove_coupon( $code ) ) { + // translators: %s coupon code. + $order->add_order_note( esc_html( sprintf( __( 'Coupon removed: "%s".', 'woocommerce' ), $code ) ), 0, true ); + } $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); ob_start(); include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; $response['html'] = ob_get_clean(); + + ob_start(); + $notes = wc_get_order_notes( array( 'order_id' => $order_id ) ); + include __DIR__ . '/admin/meta-boxes/views/html-order-notes.php'; + $response['notes_html'] = ob_get_clean(); + } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } diff --git a/plugins/woocommerce/src/Internal/Orders/CouponsController.php b/plugins/woocommerce/src/Internal/Orders/CouponsController.php index 57dd39269e2..f88df83af0a 100644 --- a/plugins/woocommerce/src/Internal/Orders/CouponsController.php +++ b/plugins/woocommerce/src/Internal/Orders/CouponsController.php @@ -31,6 +31,11 @@ class CouponsController { ob_start(); include __DIR__ . '/../../../includes/admin/meta-boxes/views/html-order-items.php'; $response['html'] = ob_get_clean(); + + ob_start(); + $notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) ); + include __DIR__ . '/../../../includes/admin/meta-boxes/views/html-order-notes.php'; + $response['notes_html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } @@ -79,12 +84,16 @@ class CouponsController { $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); - $result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $code = wc_format_coupon_code( wp_unslash( $coupon ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $result = $order->apply_coupon( $code ); if ( is_wp_error( $result ) ) { throw new Exception( html_entity_decode( wp_strip_all_tags( $result->get_error_message() ) ) ); } + // translators: %s coupon code. + $order->add_order_note( esc_html( sprintf( __( 'Coupon applied: "%s".', 'woocommerce' ), $code ) ), 0, true ); + return $order; } } diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-coupon.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-coupon.spec.js index 0870bacd38d..3176601a737 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-coupon.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-coupon.spec.js @@ -95,7 +95,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { page.on( 'dialog', ( dialog ) => dialog.accept( couponCode ) ); await page.click( 'button.add-coupon' ); - await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible(); + await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Coupon(s)' ); @@ -113,7 +113,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { test( 'can remove a coupon', async ( { page } ) => { await page.goto( `/wp-admin/post.php?post=${ orderId }&action=edit` ); // assert that there is a coupon on the order - await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible(); + await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Coupon(s)' ); @@ -130,9 +130,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { await page.dispatchEvent( 'a.remove-coupon', 'click' ); // have to use dispatchEvent because nothing visible to click on // make sure the coupon was removed - await expect( - page.locator( `text=${ couponCode }` ) - ).not.toBeVisible(); + await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).not.toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Order Total' );