Check if a user has purchased specific products in WooCommerce

30,004

Solution 1

Lighter and improved code version in HERE that handle multiple product IDs

Updated (compatibility for Woocommerce 3+)

Yes it's possible, writing a conditional function that returns "true" if current customer has already bought specifics defined products IDs. This code goes on function.php file of your active child theme or theme.

Here is the conditional function:

function has_bought_items() {
    $bought = false;

    // Set HERE ine the array your specific target product IDs
    $prod_arr = array( '21', '67' );

    // 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"
    ) );
    foreach ( $customer_orders as $customer_order ) {
        // Updated compatibility with WooCommerce 3+
        $order_id = method_exists( $order, 'get_id' ) ? $order->get_id() : $order->id;
        $order = wc_get_order( $customer_order );

        // Iterating through each current customer products bought in the order
        foreach ($order->get_items() as $item) {
            // WC 3+ compatibility
            if ( version_compare( WC_VERSION, '3.0', '<' ) ) 
                $product_id = $item['product_id'];
            else
                $product_id = $item->get_product_id();

            // Your condition related to your 2 specific products Ids
            if ( in_array( $product_id, $prod_arr ) ) 
                $bought = true;
        }
    }
    // return "true" if one the specifics products have been bought before by customer
    return $bought;
}

This code is tested and works.


USAGE:
For example, you can use it in some WooCommerce templates that you will have previously copied to your active child theme or theme:

Here is an example that you could use in those templates (above):

// Replace the numbers by your special restricted products IDs
$restricted_products = array( '20', '32', '75' );

// compatibility with WC +3
$product_id = method_exists( $product, 'get_id' ) ? $product->get_id() : $product->id;

// customer has NOT already bought a specific product for this restricted products
if ( !has_bought_items() && in_array( $product_id, $restricted_products ) ) { 

    // Displaying an INACTIVE add-to-cart button (With a custom text, style and without the link).
    // (AND optionally) an explicit message for example.

// ALL OTHER PRODUCTS OR RESTRICTED PRODUCTS IF COSTUMER HAS ALREADY BOUGHT SPECIAL PRODUCTS
} else { 

    // place for normal Add-To-Cart button code here
}

And here the complete applied example to add-to-cart button template on Shop page:

<?php
/**
 * Loop Add to Cart
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/loop/add-to-cart.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see         https://docs.woocommerce.com/document/template-structure/
 * @author      WooThemes
 * @package     WooCommerce/Templates
 * @version     2.5.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

global $product;

// Replace the numbers by your special restricted products IDs
$restricted_products = array( '37', '53', '70' );

// compatibility with WC +3
$product_id = method_exists( $product, 'get_id' ) ? $product->get_id() : $product->id;

if ( !has_bought_items() && in_array( $product_id, $restricted_products ) ) {

    echo '<a class="button greyed_button">' . __("Disabled", "your_theme_slug") . '</a>';
    echo '<br><span class="greyed_button-message">' . __("Your message goes here…", "your_theme_slug") . '</span>';

} else {

echo apply_filters( 'woocommerce_loop_add_to_cart_link',
    sprintf( '<a rel="nofollow" href="%s" data-quantity="%s" data-product_id="%s" data-product_sku="%s" class="%s">%s</a>',
        esc_url( $product->add_to_cart_url() ),
        esc_attr( isset( $quantity ) ? $quantity : 1 ),
        esc_attr( $product_id ),
        esc_attr( $product->get_sku() ),
        esc_attr( isset( $class ) ? $class : 'button' ),
        esc_html( $product->add_to_cart_text() )
    ),
$product );

}

You will style the inactive button with greyed_button class in the style.css file of your active child theme or theme. Same thing for the message with greyed_button-message class.

Solution 2

2020 update: Now handles guest users from their billing email.

New version compact, lighter, faster and compatible with all versions of woocommerce (from version 2.4 and above)

This is a new enhanced and lighter conditional function partially based on built-in woocommerce function wc_customer_bought_product source code.

There are 2 optional arguments:

  • $user_var will allow you to:
  • specify a defined user ID (when is not used for current logged in user)
  • or the billing email for guests users;
  • $product_ids (array) will allow to specify one or multiple product Ids to check

Here is that code:

function has_bought_items( $user_var = 0,  $product_ids = 0 ) {
    global $wpdb;
    
    // Based on user ID (registered users)
    if ( is_numeric( $user_var) ) { 
        $meta_key     = '_customer_user';
        $meta_value   = $user_var == 0 ? (int) get_current_user_id() : (int) $user_var;
    } 
    // Based on billing email (Guest users)
    else { 
        $meta_key     = '_billing_email';
        $meta_value   = sanitize_email( $user_var );
    }
    
    $paid_statuses    = array_map( 'esc_sql', wc_get_is_paid_statuses() );
    $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-" . implode( "','wc-", $paid_statuses ) . "' )
        AND pm.meta_key = '$meta_key'
        AND pm.meta_value = '$meta_value'
        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;
}

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).


USAGE EXAMPLES:

Example 1 (logged in customer): Detecting if current user has bought one of the defined products (product Ids needs to be an array)

// Define the targeted Products IDs
$product_ids = array( 38, 41, 85, 95 );

if( has_bought_items( '', $product_ids ) )
    echo "<p>You have already purchased one of this products</p>";
else
    echo "<p>You have not yet purchased one of this products</p>";

Example 2 (for a defined user id) Detecting if the defined user has bought one of the defined products (product Ids needs to be set in an array)

// Define the user ID
$user_id = 85;

// Define the targeted Products IDs
$product_ids = array( 38, 41, 85, 95 );

if( has_bought_items( $user_id, $product_ids ) )
    echo "<p>This user have already purchased one of this products</p>";
else
    echo "<p>This user have not yet purchased one of this products</p>";

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

Example 3 (for guest user) Detecting if guest user has bought one of the defined products from his billing email (product Ids needs to be set in an array)

// Define guest Billing email (string)
$user_email = '[email protected]';

// Define the targeted Products IDs
$product_ids = array( 38, 41, 85, 95 );

if( has_bought_items( $user_email, $product_ids ) )
    echo "<p>This user have already purchased one of this products</p>";
else
    echo "<p>This user have not yet purchased one of this products</p>";

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

Example 4 (logged in customer): Detecting if current user has already made a purchase

if( has_bought_items() )
    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>';

Example 5 (Defining the user id) - Detecting if the defined user has already made a purchase

// Define the user ID
$user_id = 85;

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

Now, if user id is equal to 0 and the current user is not logged in, this function will return false (if no billing email is defined for guest users).

Solution 3

The built-in woocommerce function wc_customer_bought_product can also be used in this case.

See function usage here.

Solution 4

I'd be doing it this way;

this won't be doing any woocommerce template modification but only use the filter woocommerce_is_purchasable

These are all the functions,

  1. check if current product ID is already brought by customer, in version 2.6 +, woocommerce implemented the function wc_customer_bought_product to check if the customer already brought the product, I've never used it before but based on the docs you can create a function like below to check an array of product ID if one of them has been already brought;

    function cmk_check_product_brought( $ids=array() ) {
        if ( ! $ids ) return false;
        foreach ( $ids as $product => $id ) {
            if ( wc_customer_bought_product( wp_get_current_user()->user_email, get_current_user_id(), $id ) ) {
                return true;
            }
        }
    }
    

The previous method I do to check if specific product ID has already been brought by the customer is below, so you can use any of these function, though the one I created is not for array of ID's

function cmk_product_ordered( $id ) {
    // Get All order of current user
    $orders = get_posts( array(
        'numberposts' => -1,
        'meta_key'    => '_customer_user',
        'meta_value'  => get_current_user_id(),
        'post_type'   => wc_get_order_types( 'view-orders' ),
        'post_status' => array_keys( wc_get_order_statuses() )
    ) );

    if ( !$orders ) return false; // return if no order found

    $all_ordered_product = array(); // store all products ordered by ID in an array

    foreach ( $orders as $order => $data ) { // Loop through each order
        $order_data = new WC_Order( $data->ID ); // create new object for each order
        foreach ( $order_data->get_items() as $key => $item ) {  // loop through each order item
            // store in array with product ID as key and order date a value
            $all_ordered_product[ $item['product_id'] ] = $data->post_date; 
        }
    }
    // check if defined ID is found in array
    if ( isset( $all_ordered_product[ $id ] ) ) return true;
    else return false;
}
  1. Now that we can check if the product ID is already brought or not, we can simply add a filter on woocommerce_is_purchasable to create our own condition, this function below is a simply example of what you are trying to achieve,

just changed the $required_purchased and $conditional_purchase values.

function cmk_disable_product_purchase( $purchasable, $product ) {

    // array of products required to be purchase first
    $required_purchased = array( 1, 2);

    // array of restricted/conditional products to be purchase
    $conditional_purchase = array( 3,4,5);

    // Get the ID for the current product
    $product_id = $product->is_type( 'variation' ) ? $product->variation_id : $product->id;

    //return default $purchasable if current product ID is not in restricted array
    if ( !in_array($product_id, $conditional_purchase)) return $purchasable;

    /**
     ** Check if one required products has been purchase;
     **/

    // using cmk_check_product_brought() function, return false if product is not purchase
    if ( ! cmk_check_product_brought( $required_purchased ) ) $purchasable = false;

    // using cmk_product_ordered() function, you can use this instead
    /*if ( cmk_product_ordered( 1 )  || cmk_product_ordered( 2 ) ) {
        $purchasable = $purchasable; //return default if one product is purchased
    } else {
        $purchasable = false;
    }*/

    // Double-check for variations: if parent is not purchasable, then variation is not
    if ( $purchasable && $product->is_type( 'variation' ) ) {
        $purchasable = $product->parent->is_purchasable();
    }

    return $purchasable;
}
add_filter( 'woocommerce_variation_is_purchasable', 'cmk_disable_product_purchase', 10, 2 );
add_filter( 'woocommerce_is_purchasable', 'cmk_disable_product_purchase', 10, 2 );

And that should set the product as non-purchasable (add to cart button will be automatically hidden ). Now if you want to add a message for non-purchasable product, you can simply use the same condition of cmk_disable_product_purchase, add your message and simply hook it on woocommerce_single_product_summary or anywhere you want it to display.

Share:
30,004
Admin
Author by

Admin

Updated on July 26, 2020

Comments

  • Admin
    Admin almost 4 years

    I need to check if a customer has purchased a specific product earlier in WooCommerce.

    The case is this: The customer shall not be able to purchase product "c", "d", "e" unless they have purchased product "a" or "b" at an earlier time.

    If the customer has purchased product "a" or "b" earlier, then the purchase button of product "c", "d" and "e" is activated and they are allowed to buy them.

    If they haven´t purchased "a" or "b" earlier, they will not be allowed to purchase "c", "d", "e" and the purchase button is deactivated.

    How can I achieve this?

    Thanks.

  • Admin
    Admin almost 8 years
    Thanks @LoicTheAztec it works just as expected! I really appreciate all your help, thanks again.
  • Firefog
    Firefog over 7 years
  • Bowe Frankema
    Bowe Frankema almost 5 years
    Loic I've been using this snippet for a while but it seems to have broken in the most recent version of WooCommerce. It always seems to result false, do you know by any chance what would be the issue?
  • LoicTheAztec
    LoicTheAztec almost 4 years
    @BoweFrankema New updated version, that handle guest customers and works in last WC 4.1+ version.
  • Stanley Aloh
    Stanley Aloh over 2 years
    for those currently using this methods, for logged in user please use has_bought_items(0, [1, 12, 3] ); instead of supplying empty string which will fail the is_numeric() check.