Home > Software design >  Prevent guest users from ordering a product multiple times in WooCommerce
Prevent guest users from ordering a product multiple times in WooCommerce

Time:05-21

What I'm trying to do is utilize a code in my child theme's functions.php to block guests from ordering a specific product again. If they had a specific product_id in their cart (that they've ordered before), they would be redirected to a specific url instead of the order being completed.

Our website uses phone number (without a zero in the first digit) as username and to make the order process as simple as possible, the user is not required to login during checkout.

We're trying to find a way to check if the billing_phone is an existing username, then checking if the user_id have bought specific product(s) before. If they did, trying to prevent that and redirect them to specific url, after they click place order button on checkout page.


Inspired by:

We tried to construct this piece of code, but without success. The order was not prevented from repeating:

function action_woocommerce_check_cart_items() {
    
  // Retrieve the current user from billing_phone
    
      // get all the order data
      $order = new WC_Order($order_id);
  
      //get the user phone from the order
      $order_phone = $order->billing_phone;
  
      //phone without zero in first digit
      $phone_nozero = substr($order_phone, 1);
      
      //gets user_id using phone number and saves it to $current_user
      $current_user = get_user_by('login', $phone_nozero);
    
    
    // Initialize
    $flag = false;
    
    // Loop through cart items
    foreach( WC()->cart->get_cart() as $cart_item ) {

        $product_id = "916";

        // Checks if a user (by email or ID or both) has bought an item
        if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product_id ) ) {
            // Flag becomes true
            $flag = true;
            
            // Break loop
            break;
        }
    }
    
    // True
    if ( $flag ) {
//Here, we likely require a code to prevent the order from being completed.

//redirect to specific url

        wp_redirect( 'https://myspecificurl.com' );
    }
}  

//hook for after clicking place order button. inspired by : 

add_action( 'woocommerce_new_order' , 'action_woocommerce_check_cart_items', 1, 1 );

Any suggestions would be greatly appreciated.

CodePudding user response:

Your code attempt contains multiple mistakes, so some remarks:

  • The woocommerce_new_order will be executed on newly created order event, and is therefore 'too late' for your question
  • Use the woocommerce_check_cart_items action hook instead
  • $order->billing_phone is since WooCommerce 3.0 replaced with $order->get_billing_phone() but does not apply in this case. This is because the $order object is only known after the order is placed, we will use WC()->session->get( 'customer' ) instead
  • You mention that the username is based on a phone number, but your question is about "only for guests". Guests do not have a username
  • In your code attempt you only check for 1 product ID (916), while order(s) usually consist of several different products
  • The wc_customer_bought_product() function only applies to users with an account. So we will have to use/write of a custom function, which is based on the existing wc_customer_bought_product() function
  • Use wp_safe_redirect() vs wp_redirect()

So you get:

function has_bought_items_by_phone_number( $phone_number, $product_ids ) {
    global $wpdb;
    
    $product_ids = is_array( $product_ids ) ? implode( ',', $product_ids ) : $product_ids;

    $line_meta_value = $product_ids != 0 ? 'AND woim.meta_value IN (' . $product_ids . ')' : 'AND woim.meta_value != 0';

    // Count the number of products
    $count = $wpdb->get_var( "
        SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
        INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
        INNER JOIN {$wpdb->prefix}woocommerce_order_items AS woi ON p.ID = woi.order_id
        INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
        WHERE p.post_status IN ( 'wc-processing' )
        AND pm.meta_key = '_billing_phone'
        AND pm.meta_value = '$phone_number'
        AND woim.meta_key IN ( '_product_id', '_variation_id' ) $line_meta_value 
    " );

    // Return true if count is higher than 0 (or false)
    return $count > 0 ? true : false;
}

function action_woocommerce_check_cart_items() {
    // Only for guests
    if ( is_user_logged_in() ) return;

    // Get session
    $customer = WC()->session->get( 'customer' );

    // NOT empty phone field
    if ( ! empty( $customer['phone'] ) ) {
        // Sanatize
        $phone_number = wc_sanitize_phone_number( $customer['phone'] );
        
        // WC Cart NOT null
        if ( ! is_null( WC()->cart ) ) {
            // Initialize (do not change)
            $product_ids = array();
            
            // Loop through cart contents
            foreach ( WC()->cart->get_cart_contents() as $cart_item ) {
                // Get product ID and push to array
                $product_ids[] = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
            }
            
            // NOT empty
            if ( ! empty ( $product_ids ) ) {
                // Call function, and if true
                if ( has_bought_items_by_phone_number( $phone_number, $product_ids ) ) {
                    // Performs a safe redirect
                    wp_safe_redirect( 'https://yoursite.com/custom-url-1' );
                    exit;
                }
            }
        }
    }
}   
add_action( 'woocommerce_check_cart_items' , 'action_woocommerce_check_cart_items', 10 );

Note 1) to apply this action (the redirect) only after they click place order button on checkout page would require using the woocommerce_checkout_process hook opposite the woocommerce_check_cart_items hook.

Because with my current answer, this will only happen in certain cases, if the phone number is not (yet) known.

However, the redirect would then display an error message and not be executed. So if you still want this, you should replace the redirect by displaying a message, which then contains the link to the page


Note 2) my answer will be applied to ALL products, to only apply this to specific products:

Replace:

if ( ! is_null( WC()->cart ) ) {
    // Initialize (do not change)
    $product_ids = array();
    
    // Loop through cart contents
    foreach ( WC()->cart->get_cart_contents() as $cart_item ) {
        // Get product ID and push to array
        $product_ids[] = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
    }

With:

// WC Cart NOT null
if ( ! is_null( WC()->cart ) ) {
    // Specific product IDs
    $specific_product_ids = array( 30, 823, 53, 57 );

    // Initialize (do not change)
    $product_ids = array();

    // Loop through cart contents
    foreach ( WC()->cart->get_cart_contents() as $cart_item ) {
        // Get product ID
        $product_id = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];

        // Checks if a value exists in an array
        if ( in_array( $product_id, $specific_product_ids ) ) {
            // Push to array
            $product_ids[] = $product_id;
        }
    }

Note 3) the has_bought_items_by_phone_number() function is based on Check if a user has purchased specific products in WooCommerce answer code

  • Related