Checking if customer has already bought something in WooCommerce
Asked Answered
R

10

14

I would like to create a WooCommerce plugin to add some offers for customers (that have a purchase History).

How can I check a user bought something before?

Thanks.

Robbins answered 10/8, 2016 at 13:0 Comment(0)
B
24

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

Blackmun answered 10/8, 2016 at 13:54 Comment(5)
Any way to show/return the date of the most recent purchase at the same time?Messier
@RodrigoM Return true if a customer has bought successfully at once one timeBlackmun
the function helped a lot. Currently I need to check if the customer has already made a purchase. if he did not, I would like to apply a discount, how do I change the current value of the purchase? (This function will be called upon confirmation of purchase / cart)Jeffries
Why do we query for ALL the orders, don't we just need 'numberposts' => 1, ?Czechoslovakia
@Blackmun How to use this function on checkout page?Trimetallic
B
27

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.

Blackmun answered 14/9, 2017 at 9:59 Comment(0)
B
24

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

Blackmun answered 10/8, 2016 at 13:54 Comment(5)
Any way to show/return the date of the most recent purchase at the same time?Messier
@RodrigoM Return true if a customer has bought successfully at once one timeBlackmun
the function helped a lot. Currently I need to check if the customer has already made a purchase. if he did not, I would like to apply a discount, how do I change the current value of the purchase? (This function will be called upon confirmation of purchase / cart)Jeffries
Why do we query for ALL the orders, don't we just need 'numberposts' => 1, ?Czechoslovakia
@Blackmun How to use this function on checkout page?Trimetallic
I
3

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;
}
Icy answered 30/7, 2020 at 22:28 Comment(0)
H
3

You can use built-in woocommerce order query to query by email/phone/etc...

$args = [
    //'customer_id' => $user_id, // can used for query by users
    'billing_phone'  => $phone, // you can also query by 'billing_email', 'billing_first_name' and etc.
    'status' => ['wc-processing', 'wc-completed'],
    'limit' => -1,
];

$query = new WC_Order_Query($args);
$orders = $query->get_orders();

return count($orders) > 1 ? true : false;

more details for WC_Order_Query found here: https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query

Hoang answered 21/8, 2021 at 23:41 Comment(0)
A
2

Simplified version:

function has_bought() {
    // Get all customer orders
    $customer_orders = get_posts( array(
        'numberposts' => -1,
        '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 status "completed"
    ) );
    // return "true" when customer has already one order
    return count( $customer_orders ) > 0 ? true : false;
}
Arnulfo answered 12/9, 2017 at 7:33 Comment(0)
C
2

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;
}

}
Cottar answered 25/10, 2017 at 8:42 Comment(0)
B
2

Version with cache layer (using transient API key, one week long). Not logged user returns false.

function has_bought() {

  if(!is_user_logged_in()) return false;

  $transient = 'has_bought_'.get_current_user_id();
  $has_bought = get_transient($transient);

  if(!$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"
  ) );

    $has_bought = count($customer_orders) > 0 ? true : false;

    set_transient($transient, $has_bought, WEEK_IN_SECONDS);

  }

  // return "true" when customer has already at least one order (false if not)
  return $has_bought;
}
Behaviorism answered 20/10, 2020 at 15:42 Comment(0)
C
2

You can use this one-liner for logged in users:

$has_bought_before = wc_get_customer_order_count( get_current_user_id() ) >= 1 ? true : false;

WordPress/WooCommerce functions used:

Categorize answered 18/5, 2022 at 12:32 Comment(0)
C
1

Enhanced version with paid state or just status agnostic query, also improved a bit code typing, for PHP 5.4+ :

function has_bought(int $user_id = 0, bool $paid = true ) {

    global $wpdb;

    $user_id = (empty($user_id)) ? get_current_user_id() : $user_id;

    $sql_str = "
        SELECT p.ID FROM ".$wpdb->posts." AS p
        INNER JOIN 
            ".$wpdb->postmeta." AS pm ON p.ID = pm.post_id
        WHERE 
            p.post_type LIKE 'shop_order'
        AND pm.meta_key = '_customer_user'
        AND pm.meta_value = %d
    ";

    $args = [(int) $user_id];

    if ($paid === true) {
        $paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
        $sql_str .= "AND p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )";
    }

    $sql = $wpdb->prepare($sql_str, $args);

    return (bool) $wpdb->get_var( $sql );
}
Corrianne answered 9/10, 2018 at 15:30 Comment(0)
D
0

This solution is simple because it is using WooCommerce internal wc_get_orders function to fetch the orders for the same customer.

And it is fast, because we're not retrieving the entire order objects but only the order IDs. (If the order object is not required retrieving just the IDs is much, much faster)

Also, we're limiting to retrieve only 1 order, not all of them. That means the query stops as soon as it finds one existing, paid order. And that's all we need in order to determine if this is an existing customer.


// returns true or false

function is_existing_customer( $order ) {

    $query_arguments = [
        'return'      => 'ids',
        'post_status' => wc_get_is_paid_statuses(),
        'limit'       => 1,
    ];

    // If the user is looged in we try to fetch all his orders
    if (is_user_logged_in()) {
        $current_user                = wp_get_current_user();
        $query_arguments['customer'] = $current_user->user_email;

    } else {  // Otherwise we use the billing email to fetch all orders
        $query_arguments['billing_email'] = $order->get_billing_email();
    }

    $orders = wc_get_orders($query_arguments);

    return count($orders) > 0;
}
Dahabeah answered 8/5, 2022 at 6:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.