I'm trying to show how much each customer has spent in total WITHOUT including the shipping cost. Everything from the code below, which creates a new column and makes it sortable, works fine except for the calculation.
I get an error on line 37 which is this one:
$money_spent = wc_get_customer_total_spent( $user_id ) - $order->get_total_tax() - $order->get_total_shipping() - $order->get_shipping_tax(), wc_get_price_decimals(), '.', '' );
Here is all the code I'm using =>
class Total_Spent_By_Customer_WooCommerce {
public function __construct() {
add_action( 'init', array( &$this, 'init' ) );
}
public function init() {
add_filter( 'manage_users_columns', array( $this,'users_columns') );
add_action( 'manage_users_custom_column', array( $this ,'users_custom_column'), 10, 3);
add_filter( 'manage_users_sortable_columns', array( $this ,'users_sortable_columns') );
add_filter( 'users_list_table_query_args', array( $this ,'users_orderby_column'), 10, 1 );
add_action( 'plugins_loaded', array( $this ,'load_this_textdomain') );
}
public static function users_columns( $columns ) {
unset($columns['posts']);
$columns['money_spent'] = _x( 'Money Spent', 'user', 'total-spent-by-customer-for-woocommerce' );
return $columns;
}
public static function users_custom_column( $value, $column_name, $user_id ) {
if ( 'money_spent' != $column_name ) {
return $value;
} else {
$money_spent = wc_get_customer_total_spent( $user_id ) - $order->get_total_tax() - $order->get_total_shipping() - $order->get_shipping_tax(), wc_get_price_decimals(), '.', '' );
return wc_price( wc_get_customer_total_spent ( $user_id ));
}
}
public static function users_sortable_columns($columns) {
$custom = array(
'money_spent' => 'money_spent',
);
return wp_parse_args( $custom, $columns );
}
public static function users_orderby_column( $args ) {
if ( isset( $args['orderby'] ) && 'money_spent' == $args['orderby'] ) {
$args = array_merge( $args, array(
'meta_key' => '_money_spent',
'orderby' => 'meta_value_num',
));
}
return $args;
}
public function load_this_textdomain() {
load_plugin_textdomain( 'total-spent-by-customer-for-woocommerce' );
}
}
new Total_Spent_By_Customer_WooCommerce();
I would really appreciate any type of help with this.
Update 2
You can not get Customer total spent without shipping and taxes totals using wc_get_customer_total_spent( $user_id ) function, as you should need to remove the taxes and shipping for each order that the current customer has made.
Also you can not get and use The WC_Order object in your function. So the WC_Order methods get_total_tax(), get_total_shipping() and get_shipping_tax() can't be used.
The alternative to get the customer total spent without shipping and taxes:
Based on the SQL Woocommerce query source code involved in wc_get_customer_total_spent() function we can build your own query.
So you will replace your function by this:
public static function users_custom_column( $value, $column_name, $user_id ) {
if ( 'money_spent' == $column_name ) {
global $wpdb;
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$spent = $wpdb->get_var("
SELECT SUM(pm2.meta_value - (pm3.meta_value + pm4.meta_value))
FROM $wpdb->posts as p
LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
LEFT JOIN {$wpdb->postmeta} AS pm2 ON p.ID = pm2.post_id
LEFT JOIN {$wpdb->postmeta} AS pm3 ON p.ID = pm3.post_id
LEFT JOIN {$wpdb->postmeta} AS pm4 ON p.ID = pm4.post_id
WHERE pm.meta_key = '_customer_user'
AND pm.meta_value = '" . esc_sql( $user_id ) . "'
AND p.post_type = 'shop_order'
AND p.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' )
AND pm2.meta_key = '_order_total'
AND pm3.meta_key = '_order_tax'
AND pm4.meta_key = '_order_shipping'
");
if ( ! $spent ) {
$spent = 0;
}
$value = wc_price( $spent );
}
return $value;
}
Tested and perfectly works.
Calculations note:
As you are removing the total taxes from the orders you don't need to remove the shipping taxes (as they are included in the total taxes).
So we just remove the total taxes and the total shipping (excluding taxes).
Related
The code below displays a message on the product page that has the "free" tag to the logged in user
function add_text_after_excerpt_single_product_free2( $short_description ) {
global $product;
$terms = get_the_terms( $product->get_id(), 'product_tag' );
foreach ( $terms as $term ) {
if ( $term->name == 'Free' && is_user_logged_in() ) {
$short_description .= 'Downloading this file is free and does not require subscription';
}
}
return $short_description;
}
add_filter('woocommerce_short_description','add_text_after_excerpt_single_product_free2', 20, 1);
I want the above code to be executed if a user has purchased a specific product
For example, a user buys a product called Photoshop training, and from now on the above code will be executed for him
thanx
You can write a custom function to check whether user buy a specific product. Try the below code. code will go in your active theme functions.php file.
This below function is inspired from here - Check if a user/guest has purchased specific products in WooCommerce
function has_bought_items( $user_id = 0, $product_ids = 0 ) {
global $wpdb;
// Based on user ID (registered users)
$meta_key = '_customer_user';
$meta_value = $user_id;
$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;
}
function add_text_after_excerpt_single_product_free2( $short_description ) {
// Define the targeted specific Products IDs
$product_ids = array( 74 );
if ( is_user_logged_in() && has_bought_items( get_current_user_id(), $product_ids ) ) {
$short_description .= 'Downloading this file is free and does not require subscription';
}
return $short_description;
}
add_filter( 'woocommerce_short_description','add_text_after_excerpt_single_product_free2', 20, 1 );
Tested and works
You'd need to store the data in the user, and then access it via their login session. It looks to me like
add_user_meta( int $user_id, string $meta_key, mixed $meta_value, bool $unique = false )
Would do the trick. For reference:
https://developer.wordpress.org/reference/functions/add_user_meta/
https://wordpress.stackexchange.com/questions/111839/add-user-meta-vs-update-user-meta
on my website I will like to display a custom message on the checkout page if a user have previously purchased the product they are about to buy. so I came across this code that actually displays a custom message if a user already bought the product, the code only display on the single product page. please how can I display this message on the checkout page?
add_action( 'woocommerce_before_add_to_cart_form', 'user_logged_in_product_already_bought', 30 );
function user_logged_in_product_already_bought() {
global $product;
if ( ! is_user_logged_in() ) return;
if ( wc_customer_bought_product( '', get_current_user_id(), $product->get_id() ) ) {
echo '<div>You purchased this in the past. Buy again?</div>';
}
}
As #Dmitry suggested you can use the woocommerce_before_checkout_form action hook. but you can't access global $product; on the checkout page. try the below code.
function user_logged_in_product_already_bought() {
global $woocommerce;
if ( ! is_user_logged_in() ) return;
// check if current user in specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
$items = $woocommerce->cart->get_cart();
$has_bought = false;
foreach($items as $item => $values) {
if ( has_bought_items( get_current_user_id(), $values['data']->get_id() ) ) {
$has_bought = true;
break;
}
}
if( $has_bought ){
wc_print_notice( "You purchased this in the past. Buy again?", 'success' );
}
}
add_action( 'woocommerce_before_checkout_form', 'user_logged_in_product_already_bought' );
Tested and works
As per #7uc1f3r comment, I will like to prevent the double purchase of product per user. you can use the woocommerce_add_to_cart_validation action hook to prevent adding products from the cart.
function prevent_from_adding_cart_if_user_product_already_bought( $passed, $product_id, $quantity ) {
if ( has_bought_items( get_current_user_id(), $product_id ) ) {
wc_add_notice( __( "You purchased this in the past. Buy again?", 'woocommerce' ), 'error' );
$passed = false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'prevent_from_adding_cart_if_user_product_already_bought', 10, 3 );
Tested and works
You can use this awesome function made by LoicTheAztec to check if a user has purchased specific products.
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;
}
UPDATE as per OP request.
You can use the below code for check if the current user is in a specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
and only products with the status of 'processing in has_bought_items function change the value of the $paid_statuses variable. as per your need.
for various reasons a webshop needs to allow guest orders, but then saves the data to an existing user if the email is already registered. However, a product (parent variable product) should not be bought twice.
In that case, the customer should be returned to the checkout page and get a notice that this product is already bought.
So far i have the following code (not complete):
add_action('woocommerce_checkout_process', function () {
if (!is_user_logged_in() && $_POST['billing_email'] && $_POST['createaccount'] == 1) {
$user = get_user_by('email', $_POST['billing_email']);
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
if (!empty($user)){
if (wc_customer_bought_product($user->user_email,$user->ID,$product_id))
return false;
wp_set_current_user($user->ID);
}
if (!empty($user)) {
if (wc_customer_bought_product($user->user_email,$user->ID,$product_id))
return true;
exit;
}
else{
///guest, continue
}
}}});
But that gives only an internal server error.
Anyone?
First, instead of using wc_customer_bought_product(), you can use the following enhanced conditional function from this existing answer (based on the same source code than wc_customer_bought_product()). This function will handle your variable products case.
// Conditional function( stackOverFlow answer: https://stackoverflow.com/a/46217461/3730754 )
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;
}
You can't get yet the order or order items… Instead you will check cart items.
Then here is your revisited code that handle all kind of customers and variable products as asked:
add_action('woocommerce_checkout_process', 'wc_action_checkout_process' );
function wc_action_checkout_process() {
$user_id = get_current_user_id();
// 1. - For logged in users
if( $user_id > 0 ) {
$user_var = $user_id;
}
// 2. - For others
else {
if ( isset($_POST['billing_email']) && ! empty($_POST['billing_email']) ) {
$email = sanitize_email( $_POST['billing_email'] );
$user = get_user_by( 'email', $email );
// 2.a - When a user exist for the billing email return an error notice
if ( is_a($user, 'WP_User') ) {
wc_add_notice( sprintf( __('The email "%s" is already registered for "%s" user name. Please login.'), $email, $user->user_login ), 'error' );
}
// 2.b - User doesn't exits (real "guest")
else {
$user_var = $email;
}
}
}
// Loop through cart items to check products
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( has_bought_items( $user_var, $cart_item['product_id'] ) ) {
wc_add_notice( sprintf( __('You have already purchased "%s" product before. You can only purchase it once.'), $cart_item['data']->get_name() ), 'error' );
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
I would like to add a column in the "admin order list" page of WooCommerce, which informs me if the user is the first time that he buys.
I think this "Checking if customer has already bought something in WooCommerce" can be used for my case.
But I don't know how to add it in a column on the "orders" page of WooCommerce
With manage_edit-shop_order_columns & manage_shop_order_posts_custom_column
you can add a new column.
With wc_get_customer_order_count( $user_id ) you get the total orders by a customer.
Note 1: Because guests don't have a $user_id, we use the $billing_email to determine the current and previous orders.
Note 2: I partly used the following function - CREDITS: #LoicTheAztec
So then you get
// Add a Header
function my_shop_order_column( $columns ) {
// Add new column
$columns['first_order'] = 'First order';
return $columns;
}
add_filter( 'manage_edit-shop_order_columns', 'my_shop_order_column', 10, 1 );
// Populate the Column
function my_shop_order_list_column_content( $column, $post_id ) {
// Compare
if ( $column == 'first_order' ) {
// Get order
$order = wc_get_order( $post_id );
// Get user id
$user_id = $order->get_user_id();
// Set variable
$output = '';
// Not a guest
if ( $user_id > 0 ) {
// Get the total orders by a customer.
$count = wc_get_customer_order_count( $user_id );
} else {
// Guest 'user', don't have a user id so we will determine the previous orders based on the billing email
// Get billing email
$billing_email = $order->get_billing_email();
if ( ! empty ( $billing_email ) ) {
// Call function
$count = has_guest_bought_by_email( $billing_email );
}
}
// Output
if ( $count == 1 ) {
$output = 'First order';
} elseif ( $count > 1 ) {
$output = 'Total orders = ' . $count;
}
echo $output;
}
}
add_action( 'manage_shop_order_posts_custom_column' , 'my_shop_order_list_column_content', 10, 2 );
// CREDITS: #LoicTheAztec - https://stackoverflow.com/a/46216073/11987538
function has_guest_bought_by_email( $email ) {
global $wpdb;
// Get all order statuses.
$order_statuses = array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) );
$results = $wpdb->get_col( "
SELECT 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 ( '" . implode( "','", $order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '_billing_email'
AND pm.meta_value = '$email'
" );
// Return result
return count( $results );
}
When I added this $product->get_id() I got HTTP Error 500
and got this message in error log:
[13-Mar-2018 08:47:07 UTC] PHP Fatal error: Call to a member function
get_id() on null in
/home/healthnwellness/public_html/wp-content/themes/betheme-child/functions.php
on line 926
My function is in functions.php:
//Function return price (commission based) as per their purchase count
function return_custom_price($price, $product) {
global $product;
$current_user = wp_get_current_user();
$count_purchase = wc_product_sold_count();
if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product->get_id() ) )
{
if($count_purchase == 1){
$price = get_post_meta( $product->get_id(), '_comm_1_kit');
$post_id = $product->get_id();
$price = ($price[0]);
return $price;
}
elseif($count_purchase == 2){
$price = get_post_meta( $product->get_id(), '_comm_2_kit');
$post_id = $product->get_id();
$price = ($price[0]);
return $price;
}
elseif($count_purchase >= 3){
$price = get_post_meta($product->get_id(), '_comm_3_kit');
$post_id = $product->get_id();
$price = ($price[0]);
return $price;
}
}
else
{
$price = get_post_meta($product->get_id(), '_discounted_price');
$post_id = $product->get_id();
$price = ($price[0]);
return $price;
}
}
How can I solve this error? what I am doing wrong?
Any help is appreciated.
Note: The wc_product_sold_count() function comes from this answer and has been changed to return the count only.
There is some missing parts and a lot of errors in your code.
You need laso to make changes the function a bit wc_product_sold_count() from this answer.
So try this instead:
function wc_product_sold_count_for_user( $product, $user_id ) {
global $wpdb;
$product_id = $product->get_id(); // Current Product ID
// The SQL request
return $wpdb->get_var( "
SELECT SUM(woim2.meta_value)
FROM {$wpdb->prefix}woocommerce_order_items AS woi
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta woim ON woi.order_item_id = woim.order_item_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta woim2 ON woi.order_item_id = woim2.order_item_id
INNER JOIN {$wpdb->prefix}postmeta pm ON woi.order_id = pm.post_id
INNER JOIN {$wpdb->prefix}posts AS p ON woi.order_id = p.ID
WHERE woi.order_item_type LIKE 'line_item'
AND p.post_type LIKE 'shop_order'
AND p.post_status IN ('wc-completed','wc-processing')
AND pm.meta_key = '_customer_user'
AND pm.meta_value = '$user_id'
AND woim.meta_key = '_product_id'
AND woim.meta_value = '$product_id'
AND woim2.meta_key = '_qty'
");
}
//Function return price (commission based) as per their purchase count
add_filter('woocommerce_product_get_price','return_custom_price', 10, 2);
function return_custom_price( $price, $product ) {
// If user is not logged in return the default price
if ( ! is_user_logged_in() ) return $price;
$customer = wp_get_current_user();
$count_purchase = wc_product_sold_count_for_user( $product, $customer->ID );
if ( wc_customer_bought_product( $customer->user_email, $customer->ID, $product->get_id() ) ){
if( $count_purchase == 1 )
$price = get_post_meta( $product->get_id(), '_comm_1_kit', true );
elseif( $count_purchase == 2 )
$price = get_post_meta( $product->get_id(), '_comm_2_kit', true );
elseif( $count_purchase >= 3 )
$price = get_post_meta($product->get_id(), '_comm_3_kit', true );
} else {
$price = get_post_meta($product->get_id(), '_discounted_price', true );
}
return $price;
}
This code goes on function.php file of your active child theme (or theme). Tested and works.
$product->get_id() = $_SESSION["iddd"];
Is a faulty assignment. The get_id() returns an int, and you try to set it with another value.
It also appears before the global $product; now so it will be null.