diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 7ff1c8a22c7..9780c4d3c5d 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -764,12 +764,12 @@ class WC_Discounts { * @return bool */ protected function validate_coupon_sale_items( $coupon ) { - if ( $coupon->get_exclude_sale_items() ) { - $valid = false; + if ( $coupon->get_exclude_sale_items() && 'fixed_product' !== $coupon->get_discount_type() ) { + $valid = true; foreach ( $this->get_items_to_validate() as $item ) { - if ( $item->product && ! $item->product->is_on_sale() ) { - $valid = true; + if ( $item->product && $item->product->is_on_sale() ) { + $valid = false; break; } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index a9e318a9474..2dff8493279 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -1393,4 +1393,78 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { remove_filter( 'woocommerce_coupon_get_discount_amount', array( $this, 'filter_woocommerce_coupon_get_discount_amount' ) ); } + + /** + * Test the is_coupon_valid logic with sale items. + * + * @since 3.4.5 + */ + public function test_is_coupon_valid_sale_items() { + + $product1 = new WC_Product_Simple(); + $product1->set_regular_price( 20 ); + $product1->save(); + + $product2 = new WC_Product_Simple(); + $product2->set_regular_price( 20 ); + $product2->set_sale_price( 10 ); + $product2->save(); + + $coupon_cart = new WC_Coupon(); + $coupon_cart->set_props( array( + 'amount' => 5, + 'discount_type' => 'fixed_cart', + 'exclude_sale_items' => false, + ) ); + $coupon_cart->save(); + + $coupon_cart_no_sale = new WC_Coupon(); + $coupon_cart_no_sale->set_props( array( + 'amount' => 5, + 'discount_type' => 'fixed_cart', + 'exclude_sale_items' => true, + ) ); + $coupon_cart_no_sale->save(); + + $coupon_product = new WC_Coupon(); + $coupon_product->set_props( array( + 'amount' => 5, + 'discount_type' => 'fixed_product', + 'exclude_sale_items' => false, + ) ); + $coupon_product->save(); + + $coupon_product_no_sale = new WC_Coupon(); + $coupon_product_no_sale->set_props( array( + 'amount' => 5, + 'discount_type' => 'fixed_product', + 'exclude_sale_items' => true, + ) ); + $coupon_product_no_sale->save(); + + WC()->cart->empty_cart(); + WC()->cart->add_to_cart( $product1->get_id(), 1 ); + WC()->cart->add_to_cart( $product2->get_id(), 1 ); + $discounts = new WC_Discounts( WC()->cart ); + + // Cart coupon with no sale restriction should be valid if sale item is in cart. + $this->assertTrue( $discounts->is_coupon_valid( $coupon_cart ) ); + + // Cart coupon with sale restriction should not be valid if sale item is in cart. + $this->assertTrue( is_wp_error( $discounts->is_coupon_valid( $coupon_cart_no_sale ) ) ); + + // Product coupon with no sale restriction should be valid if sale item is in cart. + $this->assertTrue( $discounts->is_coupon_valid( $coupon_product ) ); + + // Product coupon with sale restriction should be valid if sale item is in cart, + // but it should not apply the discount to the sale item. + $this->assertTrue( $discounts->is_coupon_valid( $coupon_product_no_sale ) ); + + $discounts->apply_coupon( $coupon_product_no_sale ); + $coupon_discounts = $discounts->get_discounts_by_coupon(); + $coupon_discount = reset( $coupon_discounts ); + + // $5 coupon should only get applied to the one cart item not on sale. + $this->assertEquals( 5, $coupon_discount ); + } }