Check if a user has purchased specific products in WooCommerce
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:
- The template for Shop page concerning
add-to-cart
button isloop/add-to-cart.php
. - The templates for Product pages concerning
add-to-cart
button are insingle-product/add-to-cart
folder depending on your product types.
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,
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;
}
- 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.
Admin
Updated on July 26, 2020Comments
-
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 almost 8 yearsThanks @LoicTheAztec it works just as expected! I really appreciate all your help, thanks again.
-
Firefog over 7 years@loictheaztec do u know How to check how many times a product has been bought by a customer
-
Bowe Frankema almost 5 yearsLoic 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 almost 4 years@BoweFrankema New updated version, that handle guest customers and works in last WC 4.1+ version.
-
Stanley Aloh over 2 yearsfor 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.