Woocommerce: How to check if coupon ever used by the user? - wordpress

I create "text field" on woocommerce customer dashboard, that field is used to redeem coupon. Let say that i have coupon code "BIRTHDAY29" that i setup on woocommerce coupon to be used.
User input "BIRTHDAY29" on my "text field", and hit button "REDEEM" so the transaction is applied programaticaly on their transaction history.
How do i know How many times a coupon "BIRTHDAY29" was used by that user? i want to check if "BIRTHDAY29" is ever used by that user or not, then i want to do "something.." if BIRTHDAY29 ever used by that user.
Hope you guys understand what i mean, thankyou!!

Here you go:
function coupon_usage_for_customer( $coupon_code, $user_id = NULL, $email = NULL )
{
$usage = 0;
// There must either be a user ID or email address
if( empty( $user_id ) && empty( $email ) )
return FALSE;
$params = [
'limit' => -1,
'type' => 'shop_order'
];
if( ! empty( $user_id ) ){
$params['customer_id'] = $user_id;
}
else if( ! empty( $email ) ){
$params['billing_email'] = $email;
}
// Get orders of customer
$orders = wc_get_orders( $params );
// Check if coupon used
foreach( $orders as $order )
{
$coupons_used = $order->get_coupon_codes();
if(
is_array( $coupons_used ) &&
in_array( $coupon_code, $coupons_used )
){
$usage++;
}
}
return $usage;
}
add_action('woocommerce_after_register_post_type', function(){
$user_id = 12345;
$email = 'oneguy#example.com';
$birthday29usage = coupon_usage_for_customer( 'BIRTHDAY29', $user_id, $email );
if( ! $birthday29usage )
echo 'The user has not birthday 29ed...';
});

Related

Add custom field (ACF) to WooCommerce completed order email notification

There are many threads which deal with the topic "custom fields in WooCommerce emails" but I couldn't find the exact case I am struggling with.
I could achieve 50% of this project to display the field as a meta in the order table.
add_action( 'woocommerce_order_item_meta_end', 'custom_product_info', 20, 4 );
function custom_product_info ( $item_id, $item, $order, $plain_text ) {
$id = $item->get_product_id();
$erlebnisidgc = get_field('erlebnis_id',$id);
if($erlebnisidgc){
echo "<p>Here's your Link</p><a href=https://b-ceed.de/?eventid='.$erlebnisidgc'>Testlink</a>";
}
}
So this code works perfectly with the custom field. Problem is that this output isn't shown only in the customer_completed_order email but also in all other emails.
Therefore, I tried this code snippet:
add_action( 'woocommerce_order_item_meta_end', 'custom_product_info', 20, 4 );
function custom_product_info ( $item_id, $item, $order, $plain_text ) {
if ( $email->id == 'customer_completed_order' ) {
$id = $item->get_product_id();
$erlebnisidgc = get_field('erlebnis_id',$id);
if($erlebnisidgc){
echo "<p>Here's your Link</p><a href=https://b-ceed.de/?eventid='.$erlebnisidgc'>Testlink</a>";
}
}
}
But now the output won't be displayed in any email anymore and it triggers a internal server error. Any advice?
$email ($email->id) is not passed as argument to the woocommerce_order_item_meta_end hook, therefore it is undefined
So to target specific email notifications a workaround will be needed, this can be done by creating a global variable via the woocommerce_email_before_order_table hook
So you get:
// Setting global variable
function action_woocommerce_email_before_order_table( $order, $sent_to_admin, $plain_text, $email ) {
$GLOBALS['email_data'] = array(
'email_id' => $email->id, // The email ID (to target specific email notification)
'is_email' => true // When it concerns a WooCommerce email notification
);
}
add_action( 'woocommerce_email_before_order_table', 'action_woocommerce_email_before_order_table', 1, 4 );
function action_woocommerce_order_item_meta_end( $item_id, $item, $order, $plain_text ) {
// Getting the custom 'email_data' global variable
$ref_name_globals_var = $GLOBALS;
// Isset & NOT empty
if ( isset ( $ref_name_globals_var ) && ! empty( $ref_name_globals_var ) ) {
// Isset
$email_data = isset( $ref_name_globals_var['email_data'] ) ? $ref_name_globals_var['email_data'] : '';
// NOT empty
if ( ! empty( $email_data ) ) {
// Target specific emails, several can be added in the array, separated by a comma
$target_emails = array( 'customer_completed_order' );
// Target specific WooCommerce email notifications
if ( in_array( $email_data['email_id'], $target_emails ) ) {
// Get product ID
$product_id = $item->get_product_id();
// Get field
$erlebnisidgc = get_field( 'erlebnis_id', $product_id );
// Has some value
if ( $erlebnisidgc ) {
echo '<p>Here is your Link</p>';
echo 'Testlink';
}
}
}
}
}
add_action( 'woocommerce_order_item_meta_end', 'action_woocommerce_order_item_meta_end', 10, 4 );
Used in this answer:
Determine only for WooCommerce admin email notification
How to target other WooCommerce order emails

How to display a custom message during WooCommerce checkout to customers who previously purchase products with order status of 'processing'

I will like to display a message on the checkout page notifying the customers that they have purchased this product in the past (the product they are about to purchase), but this message should only run if these conditions are met.
Customer must be logged in
User role is administrator or customer
previously purchased product should still have order status of 'processing.'
So far, I have been able to get the first 2 conditions working fine:
function user_logged_in_product_already_bought() {
global $woocommerce;
if ( ! is_user_logged_in() ) return;
$items = $woocommerce->cart->get_cart();
$has_bought = false;
foreach($items as $item => $values) {
if ( wc_customer_bought_product( '', get_current_user_id(), $values['data']->get_id() ) ) {
$has_bought = true;
break;
}
}
$user = wp_get_current_user();
$allowed_roles = array( 'administrator', 'customer' );
if ( array_intersect( $allowed_roles, $user->roles ) ) {
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' );
Note: the reason I'm using the foreach is that users only purchase one product at a time (can't have more than a single appointment product in the cart)
But I don't know how to go about with the third condition. Any advice?
Your first 2 steps indeed work, for the 3rd step you will have to use a custom function which only checks for the order status 'processing'.
The advantage of this custom function is that it is much faster and lighter compared to going through all existing orders.
So you get:
function has_bought_items( $user_id = 0, $product_ids = 0 ) {
// When empty, return false
if ( empty ( $product_ids ) ) return false;
global $wpdb;
$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-processing' )
AND pm.meta_key = '_customer_user'
AND pm.meta_value = '$user_id'
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 action_woocommerce_before_checkout_form() {
// Customer must be logged in
if ( ! is_user_logged_in() ) return;
// Get current user
$user = wp_get_current_user();
// Allowed user roles
$allowed_roles = array( 'administrator', 'customer' );
// Compare
if ( array_intersect( $allowed_roles, $user->roles ) ) {
// WC Cart NOT null
if ( ! is_null( WC()->cart ) ) {
// Initialize
$product_ids = array();
// Loop through cart contents
foreach ( WC()->cart->get_cart_contents() as $cart_item ) {
// Get product ID and push to array
$product_ids[] = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
}
// Call function, and if true
if ( has_bought_items( $user->ID, $product_ids ) ) {
// Notice
wc_print_notice( __( 'You purchased this in the past. Buy again?', 'woocommerce' ), 'success' );
}
}
}
}
add_action( 'woocommerce_before_checkout_form', 'action_woocommerce_before_checkout_form' );
Result: a general message
Optional: Instead of displaying a general message, but showing this separately per product, you can use the woocommerce_checkout_cart_item_quantity hook
So you would get:
function has_bought_items( $user_id = 0, $product_ids = 0 ) {
// When empty, return false
if ( empty ( $product_ids ) ) return false;
global $wpdb;
$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-processing' )
AND pm.meta_key = '_customer_user'
AND pm.meta_value = '$user_id'
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 filter_woocommerce_checkout_cart_item_quantity( $item_qty, $cart_item, $cart_item_key ) {
// Customer must be logged in
if ( ! is_user_logged_in() ) return;
// Get current user
$user = wp_get_current_user();
// Allowed user roles
$allowed_roles = array( 'administrator', 'customer' );
// Initialize
$message = '';
// Compare
if ( array_intersect( $allowed_roles, $user->roles ) ) {
// Get product id
$product_id = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
// Call function, and if true
if ( has_bought_items( $user->ID, $product_id ) ) {
$message = '<p>' . __( 'You purchased this in the past. Buy again?', 'woocommerce' ) . '</p>';
}
}
// Return
return $item_qty . $message;
}
add_filter( 'woocommerce_checkout_cart_item_quantity', 'filter_woocommerce_checkout_cart_item_quantity', 10, 3 );
Result: show separately by product
Note: the has_bought_items() function is based on Check if a user has purchased specific products in WooCommerce answer code
Related: Display message below product name on WooCommerce cart page if user has bought product before
according to this article:
you can implement such a login as below :
$customer_orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $user->ID,
'post_type' => wc_get_order_types(),
'post_status' => array_keys( wc_get_is_paid_statuses() ),
) );
// LOOP THROUGH ORDERS AND GET PRODUCT IDS
if ( ! $customer_orders ) return;
$product_ids = array();
foreach($items as $item) {
foreach ( $customer_orders as $customer_order ) {
$order = wc_get_order( $customer_order->ID );
$orderItems = $order->get_items();
foreach ( $orderItems as $orderItem ) {
if ($orderItem->get_product_id() == $item->get_product_id() )
$has_bought = true;
break;
}
}
}
Regarding 3rd point you can get the customer processing orders easily using wc_get_orders()
$user = wp_get_current_user();
$processing_orders = wc_get_orders(
array(
'customer' => $user->ID,
'status' => 'wc-processing',
)
);
You can check if the processing orders are empty or not.
and you can loop over the orders products to get details about each product.
foreach ( $processing_orders as $p_order ) {
$order_products = $p_order->get_items();
foreach ( $order_products as $product_item ) {
$product_id = $product_item->get_id();
}
}

Woocommerce: Override login required for existing email. Add order to user account

Woocommerce Settings:
Guest checkout disabled.
Create account during checkout enabled (user and passowrd auto generated)
If user is REGISTERED. But NOT logged in.
Checkout errors with
"An account is already registered with your email address. Please log in."
How do I override this to
COMPLETE order, and tie order to existing account.
Give a prompt to user to login next time for a faster checkout
Can't find any snippet or module that can do it
none yet. Happy to reward.
There is no hook for this. You may need to modify process_customer function in WC core (plugins/woocommerce/includes/class-wc-checkout.php) Line#935 - Keep in mind, it's not encouraged to edit core ( when updated, your changes will loose)
protected function process_customer( $data ) {
$customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
if ( ! is_user_logged_in() && ( $this->is_registration_required() || ! empty( $data['createaccount'] ) ) ) {
$username = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
$password = ! empty( $data['account_password'] ) ? $data['account_password'] : '';
if(email_exists($data['billing_email'])){
$creds = array();
$creds['user_login'] = $user_login;
$creds['user_password'] = $user_password;
$creds['remember'] = true;
$user = wp_signon($creds, false);
$customer_id = $user->ID;
wp_set_current_user($customer_id, $user_login);
wp_set_auth_cookie($customer_id, true, false);
do_action('wp_login', $user_login);
}else{
$customer_id = wc_create_new_customer( $data['billing_email'], $username, $password );
}
if ( is_wp_error( $customer_id ) ) {
throw new Exception( $customer_id->get_error_message() );
}
wp_set_current_user( $customer_id );
wc_set_customer_auth_cookie( $customer_id );
// As we are now logged in, checkout will need to refresh to show logged in data.
WC()->session->set( 'reload_checkout', true );
// Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering.
WC()->cart->calculate_totals();
}
// On multisite, ensure user exists on current site, if not add them before allowing login.
if ( $customer_id && is_multisite() && is_user_logged_in() && ! is_user_member_of_blog() ) {
add_user_to_blog( get_current_blog_id(), $customer_id, 'customer' );
}
// Add customer info from other fields.
if ( $customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) {
$customer = new WC_Customer( $customer_id );
if ( ! empty( $data['billing_first_name'] ) ) {
$customer->set_first_name( $data['billing_first_name'] );
}
if ( ! empty( $data['billing_last_name'] ) ) {
$customer->set_last_name( $data['billing_last_name'] );
}
// If the display name is an email, update to the user's full name.
if ( is_email( $customer->get_display_name() ) ) {
$customer->set_display_name( $data['billing_first_name'] . ' ' . $data['billing_last_name'] );
}
foreach ( $data as $key => $value ) {
// Use setters where available.
if ( is_callable( array( $customer, "set_{$key}" ) ) ) {
$customer->{"set_{$key}"}( $value );
// Store custom fields prefixed with wither shipping_ or billing_.
} elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
$customer->update_meta_data( $key, $value );
}
}
/**
* Action hook to adjust customer before save.
*
* #since 3.0.0
*/
do_action( 'woocommerce_checkout_update_customer', $customer, $data );
$customer->save();
}
do_action( 'woocommerce_checkout_update_user_meta', $customer_id, $data );
}
If you have enabled allow customers to login on checkout, the option login from checkout page will be coming.

WooCommerce - Change order status and admin e-mail recipient according to product category

I've been asked to configure a specific WooCommerce behaviour, and I can't escape having to do it with filters. Which I'm not really competent in.
What is supposed to happen, exactly, is that when an order consists only of a product from the "abo" category, it is automatically marked as 'Complete' and the admin mail is sent to a different service.
I've gathered a few examples of code, changing the e-mail recipient, the order status, or making generic changes according to the product category. This is my Frankenstein monster of a code. Both the e-mail change and the order status change failed.
/**
* Change email recipient for admin New Order emails when the order only has products from the 'abo' category
*
* #param string $recipient a comma-separated string of email recipients (will turn into an array after this filter!)
* #param \WC_Order $order the order object for which the email is sent
* #return string $recipient the updated list of email recipients
*/
add_filter( 'woocommerce_email_recipient_new_order', 'dada_conditional_email_recipient', 10, 2 );
function dada_conditional_email_recipient( $recipient, $order ) {
// Bail on WC settings pages since the order object isn't yet set yet
// Not sure why this is even a thing, but shikata ga nai
$page = $_GET['page'] = isset( $_GET['page'] ) ? $_GET['page'] : '';
if ( 'wc-settings' === $page ) {
return $recipient;
}
// just in case
if ( ! $order instanceof WC_Order ) {
return $recipient;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product = $order->get_product_from_item( $item );
// check if there's an "abo" in the order, then if there's anything else.
if ( is_product() && has_term( 'abo', 'product_cat' ) ) {
$abo_in_order = 'true';
}
if ( is_product() && has_term( 'livre', 'product_cat' ) || has_term( 'revue', 'product_cat' ) || has_term( 'livre', 'product_cat' ) ) {
$abo_alone_in_order = 'false';
}
else {
$abo_alone_in_order = 'true';
}
}
// if there's an 'abo' and nothing else, change the e-mail recipient to dada#sotiaf.fr
if ( ($abo_in_order == 'true')&&($abo_alone_in_order == 'true') ) $recipient = 'dada#sotiaf.fr';
return $recipient;
}
/**
* Autocomplete orders with only an 'abo' product
*/
add_filter( 'woocommerce_payment_complete_order_status', 'dada_abo_order_autocomplete', 10, 2 );
function dada_abo_order_autocomplete( $order_status, $order_id ) {
$order = wc_get_order( $order_id );
if ('processing' == $order_status && ('on-hold' == $order->status || 'pending' == $order->status || 'failed' == $order->status)) {
// just in case
if ( ! $order instanceof WC_Order ) {
return $order_status;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product = $order->get_product_from_item( $item );
// check if there's an "abo" in the order, then if there's anything else.
if ( is_product() && has_term( 'abo', 'product_cat' ) ) {
$abo_in_order = 'true';
}
if ( is_product() && has_term( 'livre', 'product_cat' ) || has_term( 'revue', 'product_cat' ) || has_term( 'livre', 'product_cat' ) ) {
$abo_alone_in_order = 'false';
}
else {
$abo_alone_in_order = 'true';
}
}
// if there's an 'abo' and nothing else, change the order status to 'completed'
if ( ($abo_in_order == 'true')&&($abo_alone_in_order == 'true') ) $order_status = 'completed';
}
return $order_status;
}
Any idea where the issue comes from?
Thank you,
Joss
You should implement "woocommerce_thankyou" hook for order status update instead of "woocommerce_payment_complete_order_status". Because, your current update order status code will trigerr when any order with completed status is placed. But your requirement is to change status to "Completed".
For adding new email recepient dynamically, you should try this hook : "woocommerce_email_recipient_new_order"
Refer to this : WooCommerce send new order email to customer

WooCommerce New order email manually trigger for some products

Please i find this code as solution for WooCommerce New order email manually trigger for some products but I don't know where exactly putting this code in functions.php or else thanks.
/**
* Modified from https://www.skyverge.com/blog/add-woocommerce-email-recipients-conditionally/
*
* Add another email recipient for admin New Order emails if a product from a specific category or with a specific tag is ordered
*
* #param string $recipient a comma-separated string of email recipients (will turn into an array after this filter!)
* #param \WC_Order $order the order object for which the email is sent
* #return string $recipient the updated list of email recipients
*/
function sv_conditional_email_recipient( $recipient, $order ) {
// Bail on WC settings pages since the order object isn't yet set yet
// Not sure why this is even a thing, but shikata ga nai
$page = $_GET['page'] = isset( $_GET['page'] ) ? $_GET['page'] : '';
if ( 'wc-settings' === $page ) {
return $recipient;
}
// just in case
if ( ! $order instanceof WC_Order ) {
return $recipient;
}
$items = $order->get_items();
// check if product from category or with tag is in order
foreach ( $items as $item ) {
$product = $order->get_product_from_item( $item );
$args = array('orderby' => 'name', 'order' => 'ASC', 'fields' => 'names');
$product_cats = wp_get_post_terms( $product->get_id, 'product_cat', $args ); // could swap product_cat for product_tag
// add our extra recipient if there's a product from the category with slug "dieta" - commas needed!
// we can bail if we've found one, no need to add the recipient more than once
if ( $product && in_array( "dieta", $product_cats ) ) {
$recipient .= ', notify#example.com';
return $recipient;
}
}
return $recipient;
}
add_filter( 'woocommerce_email_recipient_new_order', 'sv_conditional_email_recipient', 10, 2 );
I code this one and it works on my website, don't forget to replace the_name_of_the_cat by the name of the category and first_email#domain.com,second_email#domain.com by the two emails.
function change_email_recipient_depending_of_cat ( $recipient, $order ) {
global $woocommerce;
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item['product_id'];
if ( has_term( 'the_name_of_the_cat', 'product_cat', $product_id ) ) {
$recipient = 'first_email#domain.com,second_email#domain.com';
}
return $recipient;
}
}
add_filter( 'woocommerce_email_recipient_new_order', 'change_email_recipient_depending_of_cat', 10, 2 );

Resources