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.
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.
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):
References
'numberposts' => 1,
? –
Czechoslovakia 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:
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):
References
'numberposts' => 1,
? –
Czechoslovakia 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;
}
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
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;
}
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;
}
}
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;
}
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:
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 );
}
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;
}
© 2022 - 2024 — McMap. All rights reserved.