Checking if customer has already bought something in WooCommerce

2021 UPDATE - Handling guests "billing email" - Improved and secured SQL query

Here is a much light and faster conditional function that will return true if a customer has already made a purchase.

It handles registered users guest from their user ID and guests from their billing email:

  • For registered users: If the optional argument is not set with a user ID, the current user id will be used.
  • For guests: the billing email will be required as an argument in the function.

This lighter and improved function (based partially on wc_customer_bought_product() function source code) will return a boolean value based on orders count (false for O orders and true when there is at least one paid order):

function has_bought( $value = 0 ) {
    if ( ! is_user_logged_in() && $value === 0 ) {
        return false;
    }

    global $wpdb;
    
    // Based on user ID (registered users)
    if ( is_numeric( $value) ) { 
        $meta_key   = '_customer_user';
        $meta_value = $value == 0 ? (int) get_current_user_id() : (int) $value;
    } 
    // Based on billing email (Guest users)
    else { 
        $meta_key   = '_billing_email';
        $meta_value = sanitize_email( $value );
    }
    
    $paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );

    $count = $wpdb->get_var( $wpdb->prepare("
        SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
        INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
        WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
        AND p.post_type LIKE 'shop_order'
        AND pm.meta_key = '%s'
        AND pm.meta_value = %s
        LIMIT 1
    ", $meta_key, $meta_value ) );

    // Return a boolean value based on orders count
    return $count > 0;
}

Code goes in functions.php file of your active child theme (or theme) or also in any plugin file.

This code is tested on Woocommerce 3+ and works (It should work on previous versions too).

For multiple products: Check if a user has purchased specific products in WooCommerce


Usage example 1 (logged in customer)

if( has_bought() )
    echo '<p>You have already made a purchase</p>';
else
    echo '<p>Welcome, for your first purchase you will get a discount of 10%</p>';

Usage example 2 (setting the user ID)

// Define the user ID
$user_id = 85;

if( has_bought( $user_id ) )
        echo '<p>customer have already made a purchase</p>';
    else
        echo '<p>Customer with 0 purchases</p>';

If the $user_id is not defined and the current user is not logged in, this function will return false.

Usage example 3 - For guests (setting the billing email)

// Define the billing email (string)
$email = '[email protected]';

if( has_bought( $email ) )
        echo '<p>customer have already made a purchase</p>';
    else
        echo '<p>Customer with 0 purchases</p>'

The code of this function is partially based on the built in WooCommerce function wc_customer_bought_product() source code.


Update 2020: New updated improved, light and faster version HERE that handle also guests from billing email


Yes it is possible creating a conditional function that return true when a customer has already at least one order with status completed.

Here is the code for this conditional function:

function has_bought() {
    // Get all customer orders
    $customer_orders = get_posts( array(
        'numberposts' => 1, // one order is enough
        'meta_key'    => '_customer_user',
        'meta_value'  => get_current_user_id(),
        'post_type'   => 'shop_order', // WC orders post type
        'post_status' => 'wc-completed', // Only orders with "completed" status
        'fields'      => 'ids', // Return Ids "completed"
    ) );

    // return "true" when customer has already at least one order (false if not)
   return count($customer_orders) > 0 ? true : false; 
}

This code is tested and works.

This code goes in function.php file of your active child theme or theme, or in a plugin php file.


USAGE (as a condition):

  • You can use it in some WooCommerce templates that you will have previously copied to your active child theme or theme.
  • In your theme php files.
  • In plugin php files.
  • Any php function or WordPress/WooCommerce.

References

  • Template Structure + Overriding Templates via a Theme
  • Check if a user has purchased specific products in WooCommerce

If Guest Checking is enabled then this function might help

Just send $customer_email as argument and function will check in all the orders for that email and return true or false.

function has_bought($customer_email){

  $orders = get_posts(array(
  'numberposts' => -1,
  'post_type' => 'shop_order',
  'post_status' => array('wc-processing', 'wc-completed'),
  ));

$email_array = array();

foreach($orders as $order) {

$order_obj = wc_get_order($order->ID);
$order_obj_data = $order_obj->get_data();

array_push($email_array, $order_obj_data['billing']['email']);

}


if (in_array($customer_email, $email_array)) {
return true;
} else {
return false;
}

}

Starting from simplified version of @Antonio Novak, I made some improvements to coverage all possible order status that would indicate that user has already bought.

The Status List

The WooCommerce provides a deitaled documentation about status. Check it out: https://docs.woocommerce.com/document/managing-orders/

But, here I transcript the list using the slugs:

$order_statuses = array(
    'wc-pending'    => _x( 'Pending payment', 'Order status', 'woocommerce' ),
    'wc-processing' => _x( 'Processing', 'Order status', 'woocommerce' ),
    'wc-on-hold'    => _x( 'On hold', 'Order status', 'woocommerce' ),
    'wc-completed'  => _x( 'Completed', 'Order status', 'woocommerce' ),
    'wc-cancelled'  => _x( 'Cancelled', 'Order status', 'woocommerce' ),
    'wc-refunded'   => _x( 'Refunded', 'Order status', 'woocommerce' ),
    'wc-failed'     => _x( 'Failed', 'Order status', 'woocommerce' ),
);

Additionally, I have decided to add wc-shipped status. Of course, is a custom status, but typically used by ecommerce, right?

Instead of asking me if the user has already purchased, assuming that the biggest interest may be to offer more incentives to win a new user, I took the liberty of inverting the question and also the answer of the function.

When using the proposed function, the return will be true when it comes to the user's first purchase and false when he already has an order in the statuses listed within the query.

In addition, I also made the user id parameter optional, so the function can be reused in several cases, and not just in a user's session.

The Function

function is_the_first_purchase( $user_id = 0) {
  
  if( $user_id == 0 ){
    $user_id = get_current_user_id();
  }
  
  if( ! $user_id ){
    return true;
  }
  
  $customer_orders = get_posts( array(
    'numberposts' => -1,
    'post_type'   => 'shop_order',
    'post_status' => array(
      'wc-completed', 
      'wc-on-hold', 
      'wc-processing', 
      'wc-shipped', 
      'wc-refunded'
    ),
    'meta_query' => array(
      array(
        'key'         => '_customer_user',
        'meta_value'  => $user_id,
      )
    )
  ));
  
  // return "true" when customer 
  // does not have an completed order

  return count( $customer_orders ) > 0 ? false : true;
}