Merge pull request #28592 from woocommerce/fix/27673
Better error messages when coupons are stuck in a pending order.
This commit is contained in:
commit
d2cd61762a
|
@ -1621,12 +1621,16 @@ class WC_Cart extends WC_Legacy_Cart {
|
|||
$coupon_data_store = $coupon->get_data_store();
|
||||
$billing_email = strtolower( sanitize_email( $billing_email ) );
|
||||
if ( $coupon_data_store && $coupon_data_store->get_usage_by_email( $coupon, $billing_email ) >= $coupon_usage_limit ) {
|
||||
if ( $coupon_data_store->get_tentative_usages_for_user( $coupon->get_id(), array( $billing_email ) ) ) {
|
||||
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST );
|
||||
} else {
|
||||
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given email address(es) matches the ones specified on the coupon.
|
||||
|
|
|
@ -67,6 +67,8 @@ class WC_Coupon extends WC_Legacy_Coupon {
|
|||
const E_WC_COUPON_MAX_SPEND_LIMIT_MET = 112;
|
||||
const E_WC_COUPON_EXCLUDED_PRODUCTS = 113;
|
||||
const E_WC_COUPON_EXCLUDED_CATEGORIES = 114;
|
||||
const E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK = 115;
|
||||
const E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST = 116;
|
||||
const WC_COUPON_SUCCESS = 200;
|
||||
const WC_COUPON_REMOVED = 201;
|
||||
|
||||
|
@ -994,6 +996,17 @@ class WC_Coupon extends WC_Legacy_Coupon {
|
|||
case self::E_WC_COUPON_NOT_APPLICABLE:
|
||||
$err = __( 'Sorry, this coupon is not applicable to your cart contents.', 'woocommerce' );
|
||||
break;
|
||||
case self::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK:
|
||||
if ( is_user_logged_in() && wc_get_page_id( 'myaccount' ) > 0 ) {
|
||||
/* translators: %s: myaccount page link. */
|
||||
$err = sprintf( __( 'Coupon usage limit has been reached. If you were using this coupon just now but order was not complete, you can retry or cancel the order by going to the <a href="%s">my account page</a>.', 'woocommerce' ), wc_get_endpoint_url( 'orders', '', wc_get_page_permalink( 'myaccount' ) ) );
|
||||
} else {
|
||||
$err = $this->get_coupon_error( self::E_WC_COUPON_USAGE_LIMIT_REACHED );
|
||||
}
|
||||
break;
|
||||
case self::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST:
|
||||
$err = __( 'Coupon usage limit has been reached. Please try again after some time, or contact us for help.', 'woocommerce' );
|
||||
break;
|
||||
case self::E_WC_COUPON_EXCLUDED_PRODUCTS:
|
||||
// Store excluded products that are in cart in $products.
|
||||
$products = array();
|
||||
|
|
|
@ -599,12 +599,41 @@ class WC_Discounts {
|
|||
* @return bool
|
||||
*/
|
||||
protected function validate_coupon_usage_limit( $coupon ) {
|
||||
if ( $coupon->get_usage_limit() > 0 && $coupon->get_usage_count() >= $coupon->get_usage_limit() ) {
|
||||
throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 );
|
||||
}
|
||||
|
||||
if ( ! $coupon->get_usage_limit() ) {
|
||||
return true;
|
||||
}
|
||||
$usage_count = $coupon->get_usage_count();
|
||||
$data_store = $coupon->get_data_store();
|
||||
$tentative_usage_count = is_callable( array( $data_store, 'get_tentative_usage_count' ) ) ? $data_store->get_tentative_usage_count( $coupon->get_id() ) : 0;
|
||||
if ( $usage_count + $tentative_usage_count < $coupon->get_usage_limit() ) {
|
||||
// All good.
|
||||
return true;
|
||||
}
|
||||
// Coupon usage limit is reached. Let's show as informative error message as we can.
|
||||
if ( 0 === $tentative_usage_count ) {
|
||||
// No held coupon, usage limit is indeed reached.
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED;
|
||||
} elseif ( is_user_logged_in() ) {
|
||||
$recent_pending_orders = wc_get_orders(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'post_status' => array( 'wc-failed', 'wc-pending' ),
|
||||
'customer' => get_current_user_id(),
|
||||
'return' => 'ids',
|
||||
)
|
||||
);
|
||||
if ( count( $recent_pending_orders ) > 0 ) {
|
||||
// User logged in and have a pending order, maybe they are trying to use the coupon.
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK;
|
||||
} else {
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED;
|
||||
}
|
||||
} else {
|
||||
// Maybe this user was trying to use the coupon but got stuck. We can't know for sure (performantly). Show a slightly better error message.
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST;
|
||||
}
|
||||
throw new Exception( $coupon->get_coupon_error( $error_code ), $error_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure coupon user usage limit is valid or throw exception.
|
||||
|
@ -631,7 +660,14 @@ class WC_Discounts {
|
|||
$data_store = $coupon->get_data_store();
|
||||
$usage_count = $data_store->get_usage_by_user_id( $coupon, $user_id );
|
||||
if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
|
||||
throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 );
|
||||
if ( $data_store->get_tentative_usages_for_user( $coupon->get_id(), array( $user_id ) ) > 0 ) {
|
||||
$error_message = $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK );
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK;
|
||||
} else {
|
||||
$error_message = $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
|
||||
$error_code = WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED;
|
||||
}
|
||||
throw new Exception( $error_message, $error_code );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -422,6 +422,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
|
|||
$this->get_tentative_usage_query( $coupon_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of uses for a coupon by user ID.
|
||||
*
|
||||
|
|
|
@ -55,9 +55,6 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
|
|||
$this->assertEquals( strpos( $coupon_held_key[ $coupon->get_id() ], '_coupon_held_' ), 0 );
|
||||
$this->assertEquals( $coupon_data_store->get_tentative_usage_count( $coupon->get_id() ), 1 );
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
||||
WC()->cart->add_discount( $coupon->get_code() );
|
||||
$order2_id = $checkout->create_order(
|
||||
array(
|
||||
'billing_email' => 'a@c.com',
|
||||
|
@ -102,11 +99,6 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
|
|||
$this->assertEquals( $coupon_data_store->get_tentative_usage_count( $coupon1->get_id() ), 1 );
|
||||
$this->assertEquals( $coupon_data_store->get_tentative_usage_count( $coupon2->get_id() ), 1 );
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
||||
WC()->cart->add_discount( $coupon_code1 );
|
||||
WC()->cart->add_discount( $coupon_code2 );
|
||||
|
||||
$order2_id = $checkout->create_order(
|
||||
array(
|
||||
'billing_email' => 'a@b.com',
|
||||
|
@ -175,7 +167,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
|
|||
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
||||
WC()->cart->add_discount( $coupon->get_code() );
|
||||
WC()->cart->check_customer_coupons( $posted_data );
|
||||
$this->assertTrue( wc_has_notice( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ), 'error' ) );
|
||||
$this->assertTrue( wc_has_notice( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST ), 'error' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
/**
|
||||
* Unit tests for WC_Discounts class.
|
||||
*
|
||||
* @package WooCommerce\Tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Discounts_Tests.
|
||||
*/
|
||||
class WC_Discounts_Tests extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Helper method to create limited coupon.
|
||||
*/
|
||||
private function create_limited_coupon() {
|
||||
update_option( 'woocommerce_hold_stock_minutes', 60 );
|
||||
return WC_Helper_Coupon::create_coupon(
|
||||
'coupon4one' . microtime( true ) . wp_generate_password( 6, false, false ),
|
||||
array(
|
||||
'usage_limit' => 1,
|
||||
'usage_limit_per_user' => 1,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create customer.
|
||||
*/
|
||||
public function create_customer() {
|
||||
$username = 'testusername-' . microtime( true ) . wp_generate_password( 6, false, false );
|
||||
$customer = new WC_Customer();
|
||||
$customer->set_username( $username );
|
||||
$customer->set_password( 'test123' );
|
||||
$customer->set_email( "$username@woo.local" );
|
||||
$customer->save();
|
||||
return $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if coupon is valid when usage limit is reached for guest
|
||||
*/
|
||||
public function test_is_coupon_valid_when_limit_reached_for_guest() {
|
||||
$coupon = $this->create_limited_coupon();
|
||||
$data_store = WC_Data_Store::load( 'coupon' );
|
||||
|
||||
$result = $data_store->check_and_hold_coupon( $coupon );
|
||||
$this->assertNotNull( $result );
|
||||
|
||||
wp_set_current_user( 0 );
|
||||
$valid = ( new WC_Discounts() )->is_coupon_valid( $coupon );
|
||||
$this->assertWPError( $valid );
|
||||
$this->assertEquals( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST ), $valid->get_error_message() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if coupon is valid when usage limit is reached for logged in user.
|
||||
*/
|
||||
public function test_is_coupon_valid_when_limit_reached_for_user() {
|
||||
$coupon = $this->create_limited_coupon();
|
||||
$customer = $this->create_customer();
|
||||
$data_store = WC_Data_Store::load( 'coupon' );
|
||||
$order = wc_create_order(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'customer_id' => $customer->get_id(),
|
||||
)
|
||||
);
|
||||
$order->save();
|
||||
|
||||
$result = $data_store->check_and_hold_coupon( $coupon );
|
||||
$this->assertNotNull( $result );
|
||||
|
||||
wp_set_current_user( $customer->get_id() );
|
||||
$valid = ( new WC_Discounts() )->is_coupon_valid( $coupon );
|
||||
$this->assertWPError( $valid );
|
||||
$this->assertEquals( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK ), $valid->get_error_message() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if coupon is valid when usage limit per user is reached for logged in user.
|
||||
*/
|
||||
public function test_is_coupon_valid_per_user_when_limit_reached_for_user() {
|
||||
$coupon = $this->create_limited_coupon();
|
||||
$data_store = WC_Data_Store::load( 'coupon' );
|
||||
$customer = $this->create_customer();
|
||||
|
||||
$result = $data_store->check_and_hold_coupon_for_user( $coupon, array( $customer->get_id() ), $customer->get_id() );
|
||||
$this->assertNotNull( $result );
|
||||
|
||||
wp_set_current_user( $customer->get_id() );
|
||||
$valid = ( new WC_Discounts() )->is_coupon_valid( $coupon );
|
||||
$this->assertWPError( $valid );
|
||||
$this->assertEquals( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK ), $valid->get_error_message() );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue