I made a code to save a value based on a meta key when a certain product is purchased (order status = completed).
I have written the code below to complete this task. However, the meta data does not appear to be saved? Any advice?
add_action( 'woocommerce_order_status_completed', 'wpglorify_change_role_on_purchase2' );
function wpglorify_change_role_on_purchase2( $order_id ) {
// get order object and items
$order = new WC_Order( $order_id );
$items = $order->get_items();
$user_is= $user->ID;
$meta_key = get_field("field");
$field = get_user_meta($user_is, $meta_key);
$meta_value = 5;
$product_id = 1289;
foreach ( $items as $item ) {
if( $product_id == $item['product_id'] && $order->user_id ) {
$user = new WP_User( $order->user_id );
update_user_meta($user_is, $field, $meta_value, '');
}
}
}
Your code contains some mistakes:
get_field() is a ACF function, and doesn't seem to apply here
You're using $user->ID; but $user is undefined
Furthermore, it is important to determine whether you want to update user meta or post meta data
1) To update user meta you can use:
// Update user meta
function action_woocommerce_order_status_completed( $order_id, $order ) {
// Product IDs
$product_id = 1289;
// Meta data
$meta_value = 5;
$meta_key = 'my_meta_key';
// Is a order
if ( is_a( $order, 'WC_Order' ) ) {
// Getting the user ID
$user_id = $order->get_user_id();
// User ID exists
if ( $user_id >= 1 ) {
// Loop through order items
foreach ( $order->get_items() as $key => $item ) {
// Compare
if ( $item->get_product_id() == $product_id ) {
// Update user meta
update_user_meta( $user_id, $meta_key, $meta_value );
// Break loop
break;
}
}
}
}
}
add_action( 'woocommerce_order_status_completed', 'action_woocommerce_order_status_completed', 10, 2 );
2) To update post meta you can use:
// Update post meta
function action_woocommerce_order_status_completed( $order_id, $order ) {
// Product IDs
$product_id = 1289;
// Meta data
$meta_value = 5;
$meta_key = 'my_meta_key';
// Is a order
if ( is_a( $order, 'WC_Order' ) ) {
// Loop through order items
foreach ( $order->get_items() as $key => $item ) {
// Compare
if ( $item->get_product_id() == $product_id ) {
// Add the meta data
$order->update_meta_data( $meta_key, $meta_value );
// Save
$order->save();
/** OR the older way **/
// update_post_meta( $order_id, $meta_key, $meta_value );
// Break loop
break;
}
}
}
}
add_action( 'woocommerce_order_status_completed', 'action_woocommerce_order_status_completed', 10, 2 );
Related
I use WooCommerce with WordPress and I would like to update a custom field for the purchase of a single product at the time of payment validation with the display of the thank you page.
I started from this code but it does not work for the moment.
add_action( 'woocommerce_thankyou', 'checkout_update_user_meta' );
function checkout_update_user_meta( $order_id ) {
$order = new WC_Order( '$order_id' );
$user_id = $order->get_user_id();
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item['product_id'];
if ( $product_id == '777' ) {
update_user_meta( $user_id, 'masterclass-1', 'ok' );
}
}
}
add_action( 'woocommerce_thankyou', 'checkout_update_user_meta' );
function checkout_update_user_meta( $order_id ) {
$order = new WC_Order( $order_id );
$user_id = $order->get_user_id();
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item['product_id'];
if ( $product_id === 777 ) {
update_user_meta( $user_id, 'masterclass-1', 'ok' );
}
}
}
The issue with your code was that you wrapped the $order_id in single quotes. This line $order = new WC_Order( $order_id ); can also be replaced as $order = wc_get_order( $order_id );
I just modified your code to Update user meta field after woocommerce checkout according to your need. Please check my changes and hope it will work đź‘Ť
add_action( 'woocommerce_thankyou', 'checkout_update_user_meta' );
function checkout_update_user_meta( $order_id ) {
$order = wc_get_order( $order_id );
$user_id = $order->get_user_id();
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
if ( $product_id == 777 ) {
update_user_meta( $user_id, 'masterclass-1', 'ok' );
}
}
}
I'm using the save_post_{$post->post_type} hook, which fires once a post (order) has been saved.
The intention is to save product meta based on certain order statuses. This is the code I wrote/used for this:
add_action ( 'save_post_shop_order', function (int $postId, \WP_Post $post, bool $update): void {
$order = new WC_Order( $postId );
$order_status = $order->get_status();
$status_arrays = array( 'processing', 'on-hold', 'to-order', 'needed' );
if ( in_array($order_status, $status_arrays) ) {
return;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
$product = wc_get_product( $product_id );
$final_product->update_meta_data( '_test', '_test' );
$final_product->save();
}
},
10,
3
);
However, I can't find the new metadata in my database. Any advice? can anyone help me how to achieve this?
Your code contains some mistakes, for example $final_product
is not equal to $product and is therefore not defined.
This should suffice: (explanation via comment tags, added in the code)
function action_save_post_shop_order( $post_id, $post, $update ) {
// Checking that is not an autosave && current user has the specified capability
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_shop_order', $post_id ) ) {
return;
}
// Get $order object
$order = wc_get_order( $post_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// NOT has status
if ( ! $order->has_status( array( 'processing', 'on-hold', 'to-order', 'needed' ) ) ) {
// Loop through order items
foreach( $order->get_items() as $item ) {
// Get an instance of corresponding the WC_Product object
$product = $item->get_product();
// Meta: key - value
$product->update_meta_data( '_test', '_test' );
// Save
$product->save();
}
}
}
}
add_action( 'save_post_shop_order', 'action_save_post_shop_order', 10, 3 );
Note: If the order should have a certain status versus NOT having the status, just remove the ! from:
// NOT has status
if ( ! $order->has_status(..
I am trying to implement a function that will check if a customer has ever bought ANY product from my shop before, and if not - provide them with a free "sign-up gift" on first purchase.
I am able to automatically add the product to the cart fine enough, but the issue occurs afterwards - the product keeps getting added to the cart even after a customer has made a purchase.
Code below - can't figure what the issue might be.
function has_bought( $user_id = 0 ) {
global $wpdb;
$customer_id = $user_id == 0 ? get_current_user_id() : $user_id;
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_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 ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
" );
// Count number of orders and return a boolean value depending if higher than 0
return count( $results ) > 0 ? true : false;
}
function aaptc_add_product_to_cart() {
if( ! has_bought() && ! is_admin() && is_user_logged_in() ) {
$product_id = 2449; // Product Id of the free product which will get added to cart
$found = false;
//check if product already in cart
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->get_id() == $product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $product_id );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $product_id );
}
}
}
add_action( 'init', 'aaptc_add_product_to_cart' );
**** EDIT ****
Based on the support provided I have some new code that auto-adds the product to the cart, checks for orders and removes the product if orders exist.
Can someone please confirm whether the code below is correct?
/*
* Automatically add product to cart
*/
function insta_add_product_to_cart() {
if ( ! is_admin() ) {
$product_id = 2449; // Product Id of the free product which will get added to cart
$found = false;
//check if product already in cart
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->get_id() == $product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $product_id );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $product_id );
}
}
}
add_action( 'init', 'insta_add_product_to_cart' );
function remove_shirt_returning_customer() {
$product_id = 2449; // Product Id of the free product which will get added to cart
$user_id = get_current_user_id();
// Get orders by customer.
$args = array(
'customer_id' => $user_id,
);
$orders = wc_get_orders( $args );
if ( !empty($orders) ) {
WC()->cart->remove_cart_item( $product_id );
}
}
add_action( 'init', 'remove_shirt_returning_customer' );
**** EDIT ***
THE SOLUTION
So the previous code still gave me errors. What seems to work effectively (in my localhost environment) is the code below.
/*
* Automatically add product to cart
*/
function insta_add_product_to_cart() {
if ( ! is_admin() ) {
$product_id = 51; // Product Id of the free product which will get added to cart
$found = false;
//check if product already in cart
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->get_id() == $product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $product_id );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $product_id );
}
}
}
add_action( 'init', 'insta_add_product_to_cart' );
/*
* Remove item from cart if previous order exists
*/
function remove_shirt_returning_customer() {
if ( ! is_admin() ) {
$cart_items = WC()->cart->get_cart();
$product_id = 51; // Product Id of the free product which will get added to cart
$user_id = get_current_user_id();
// Get orders by customer.
$args = array(
'customer_id' => $user_id,
);
$orders = wc_get_orders( $args );
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( $cart_item['product_id'] == $product_id && !empty( $orders ) ) {
WC()->cart->remove_cart_item( $cart_item_key );
}
}
}
}
add_action( 'init', 'remove_shirt_returning_customer' );
Instead of using that custom query try using the wc_get_orders function you can retrieve all the orders from a customer using the email address or the user id
// Get orders by customer with ID 12.
$args = array(
'customer_id' => 12,
);
$orders = wc_get_orders( $args );
More info about the wp_get_orders can be found here
You're only going to be able to tell whether the user has ordered from you before or not if they are logged into an account. So if the user is on your site and is logged into the account, you will have to find out which user that is:
$user_id = get_current_user_id();
From there use Enrique's answer to obtain whether that customer has placed an order with you or not.
$user_id = get_current_user_id();
$args = array(
'customer_id' => 12,
);
$orders = wc_get_orders( $args );
From there, test whether $orders is empty or not, if empty leave the item as a default in the cart, if not remove it.
A better approach would be to just automatically have the item in all carts, then conditionally check whether this user is logged in, and has placed an order with you before. If so, remove the item.
In woocommerce, I have maisd wome customizations and I can add a custom text field in my products, Display the values in cart and checkout (see the screenshots below).
Text field in product page:
Value in the cart:
Value in the checkout:
But I can not get it to appear in the purchase details or in the administration section (see the screenshots below).
Checkout details without the value:
Administration orders without the value:
In my code below, could someone tell what I am doing wrong?
The code that I use in my functions.php file:
// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_checkout_field');
function my_custom_checkout_field() {
global $product;
$id = $product->get_id();
// Get the field name of InputText1
$InputText1Name = get_post_meta($id, 'InputText1', true);
if ((!empty(get_post_meta($id, $InputText1, true)))){
echo '<div id="InputText1">';
echo '<label>'.__($InputText1Name).'</label> <input type="text" name="$InputText1V">';
echo '</div>';
}
}
// Store custom field
function save_my_custom_checkout_field( $cart_item_data, $product_id ) {
if( isset( $_REQUEST['$InputText1V'] ) ) {
$cart_item_data[ '$InputText1V' ] = $_REQUEST['$InputText1V'];
/* below statement make sure every add to cart action as unique line item */
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_my_custom_checkout_field', 10, 2 );
// Render meta on cart and checkout
function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ){
// Get the product id inside the cart
foreach( WC()->cart->get_cart() as $cart_item ){
$product_id = $cart_item['product_id'];
}
// Get the field name of InputText1
$InputText1Name = get_post_meta($product_id, 'InputText1', true);
$custom_items = array();
/* Woo 2.4.2 updates */
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['$InputText1V'] ) ) {
$custom_items[] = array( "name" => $InputText1Name, "value" => $cart_item['$InputText1V'] );
}
return $custom_items;
}
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
// Display as order meta
function my_field_order_meta_handler( $item_id, $values, $cart_item_key ) {
if( isset( $values['$InputText1V'] ) ) {
wc_add_order_item_meta( $product_id, "$InputText1V", $values['$InputText1V'] );
}
}
// Update the order meta with field value
add_action('woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta');
function my_custom_checkout_field_update_order_meta( $order_id ) {
if ($_POST['$InputText1V']) update_post_meta( $order_id, '$InputText1Name', esc_attr($_POST['$InputText1V']));
}
// Update the user meta with field value
add_action('woocommerce_checkout_update_user_meta', 'my_custom_checkout_field_update_user_meta');
function my_custom_checkout_field_update_user_meta( $user_id ) {
if ($user_id && $_POST['$InputText1V']) update_user_meta( $user_id, '$InputText1V', esc_attr($_POST['$InputText1V']) );
}
add_filter( 'woocommerce_hidden_order_itemmeta', 'hide_order_item_meta_fields' );
// Display field value on the order edit page
add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 );
function my_custom_checkout_field_display_admin_order_meta( $order ){
$order_id = method_exists( $order, 'get_id' ) ? $order->get_id() : $order->id;
echo '<p><strong>'.__($InputText1V).':</strong> ' . get_post_meta( $order_id, $InputText1V, true ) . '</p>';
}
function hide_order_item_meta_fields( $fields ) {
$fields[] = 'current_view';
$fields[] = 'custom_image';//Add all meta keys to this array,so that it will not be displayed in order meta box
return $fields;
}
add_action( 'woocommerce_after_order_itemmeta', 'order_meta_customized_display',10, 3 );
function order_meta_customized_display( $item_id, $item, $product ){
$order_product_id = $item['product_id'];
$field1name = get_post_meta($order_product_id, 'InputText1', true);
echo'<br>';
print_r($InputText1V);
echo'<br>';
echo $field1name;
echo ': ';
}
There are errors and missing things in your code. You don't need all that functions too and you should really avoid to use $ (and if you can capitals) in your custom field slugs (or in key slugs and even in function names) in your code.
I have tested and revisited your code. Here is what you need and can remove everything else:
// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_checkout_field');
function my_custom_checkout_field() {
global $product;
$product_id = $product->get_id();
// Get the field name of InputText1
$label = get_post_meta($product_id, 'InputText1', true);
if( ! empty( $label ) ){
echo '<div id="InputText1">
<label>'.$label.':</label> <input type="text" name="custom_slug" value="">
</div>';
}
}
// Store custom field label and value in cart item data
add_filter( 'woocommerce_add_cart_item_data', 'save_my_custom_checkout_field', 10, 2 );
function save_my_custom_checkout_field( $cart_item_data, $product_id ) {
if( isset( $_REQUEST['custom_slug'] ) ) {
$cart_item_data['custom_data']['label'] = get_post_meta($product_id, 'InputText1', true);
$cart_item_data['custom_data']['value'] = sanitize_text_field( $_REQUEST['custom_slug'] );
$cart_item_data['custom_data']['ukey'] = md5( microtime().rand() );
}
return $cart_item_data;
}
// Display items custom fields label and value in cart and checkout pages
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
function render_meta_on_cart_and_checkout( $cart_data, $cart_item ){
$custom_items = array();
/* Woo 2.4.2 updates */
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['custom_data'] ) ) {
$custom_items[] = array(
'name' => $cart_item['custom_data']['label'],
'value' => $cart_item['custom_data']['value'],
);
}
return $custom_items;
}
// Save item custom fields label and value as order item meta data
add_action('woocommerce_add_order_item_meta','save_in_order_item_meta', 10, 3 );
function save_in_order_item_meta( $item_id, $values, $cart_item_key ) {
if( isset( $values['custom_data'] ) ) {
wc_add_order_item_meta( $item_id, $values['custom_data']['label'], $values['custom_data']['value'] );
}
}
Code goes in function.php file of your active child theme (or active theme).
Tested and works.
This way you will get the display in Order received, Order view, Edit order (admin) and email notifications…
I need to change item product prices in Woocommerce Backend Order. I tried tu use the following hook, but I have a problem trying to obtain the order id. Any suggestion? Thanks in advance!
function my_custom_prices ($price, $product)
{
if ( !is_admin() || ( is_admin() && is_post_type_archive() ) ) return $price;
global $post, $woocommerce;
$order = new WC_Order( $post_id );
$user_id = $order->user_id;
$user = get_user_by('id', $user_id);
if ( in_array( 'my_role', (array) $user->roles ) ) {
return $price * 2;
}
else {
return $price;
}
}
add_filter('woocommerce_get_price', 'my_custom_prices ', 10, 2);
Complete problem:
The complete problem is as follows. I am using a plugin that adds a field to the product called wholesale price. If the customer has the wholesale customer role, the order uses those prices. The plugin works fine, but it does not take the price in the backend. I talked to the author and it's something they do not plan to change yet. My client needs to modify the orders. But when it enters the backend, it takes the common price, not the wholesaler. I need to do something in the backend that allows me to detect if the order is from a client with a wholesale customer role. If yes, take the correct price when adding products. There is more information on the discussion with the author here. https://wordpress.org/support/topic/wholesale-prices-in-backend-editing-orders/ Thank you very much for the help you can give me.
Options:
woocommerce_get_price: does not work because I cannot obtain the customer id
woocommerce_ajax_add_order_item_meta: nice option, but I could not find a sample
Button: nice option, but I do not know how can I change the price. I tryed the follow:
add_action( 'woocommerce_order_item_add_action_buttons', 'action_aplicar_mayoristas', 10, 1);
function action_aplicar_mayoristas( $order )
{
echo '<button type="button" onclick="document.post.submit();" class="button button-primary generate-items">Aplicar precios mayoristas</button>';
echo '<input type="hidden" value="1" name="aplicar_mayoristas" />';
};
add_action('save_post', 'aplicar_mayoristas', 10, 3);
function aplicar_mayoristas($post_id, $post, $update){
$slug = 'shop_order';
if(is_admin()){
if ( $slug != $post->post_type ) {
return;
}
if(isset($_POST['aplicar_mayoristas']) && $_POST['aplicar_mayoristas']){
$order = wc_get_order( $post_id);
//$order_id = $order->get_user_id();
// Iterating through each "line" items in the order
foreach ($order->get_items() as $item_id => $item_data) {
//$item_data->set_subtotal("798");
$item_data->set_price("798");
//->set_price($custom_price);
}
}
}
}
Updated
The hook that you are using is not for orders, but only for products, and is made to change the displayed prices only. So you will not get the order ID with it.
You could change the price display in many hooks, but if you want to change order item prices for real (not only the displayed formatted prices), you should trigger this prices changes when order is updated for example.
In this case you can use a custom function hooked in save_post action hook:
add_action( 'save_post', 'change_order_item_prices', 11, 1 );
function change_order_item_prices( $post_id ) {
// If this is an autosave (has not been submitted).
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user's permissions.
if ( 'shop_order' == $_POST[ 'post_type' ] ){
if ( ! current_user_can( 'edit_shop_order', $post_id ) )
return $post_id;
} else {
if ( ! current_user_can( 'edit_post', $post_id ) )
return $post_id;
}
## ------------------- Changing prices Start code ------------------- ##
## ===> HERE define the targeted user role
$user_role = 'my_role';
## ===> HERE define the rate multiplier (for price calculations)
$multiplier = 2;
// If this Order items prices have already been updated, we exit
$items_prices_updated = get_post_meta( $post_id, 'line_item_updated', true );
if( ! empty( $items_prices_updated ) ) return $post_id; // Exit
$order = new WC_Order( $post_id ); // Get the order object
$user_id = $order->get_user_id(); // Get the user ID
$user_data = get_userdata( $user_id ); // Get the user data
// Check the user role
if ( ! in_array( $user_role, $user_data->roles ) ) return;
// Loop through order items
foreach( $order->get_items() as $item_id => $item ){
$item_data = $item->get_data(); // The item data
$taxes = array();
foreach( $item_data['taxes'] as $key_tax => $values ){
if( ! empty( $values ) ){
foreach( $values as $key => $tax_price ){
$taxes[$key_tax][$key] = floatval($tax_price) * $multiplier;
}
}
}
$new_line_subtotal = floatval( $item_data['subtotal'] ) * $multiplier;
$new_line_subt_tax = floatval( $item_data['subtotal_tax'] ) * $multiplier;
$new_line_total = floatval( $item_data['total'] ) * $multiplier;
$new_line_total_tax = floatval( $item_data['total_tax'] ) * $multiplier;
// Update Order item prices
$item->set_subtotal($new_line_subtotal);
$item->set_subtotal_tax($new_line_subt_tax);
$item->set_total($new_line_total);
$item->set_total_tax($new_line_total_tax);
$item->set_taxes($taxes);
// Save the updated data
$item->save();
}
// Udpate order totals and cache
$order->calculate_totals();
// We mark the order as updated (to avoid repetition)
update_post_meta( $post_id, 'line_item_updated', true );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and and finally works.
I have added a security to avoid the order items to be updated twice.
The method $order->calculate_totals(); slow down the process a little bit… It's normal as it will calculate totals, update data and refresh caches.
Your code needs to be debugged.
$post_id - there is not such variable or parameter inside your function. So, use $post->ID instead.
do
var_dump( (array) $user->roles);
before the
if (in_array( 'my_role', (array) $user->roles ) )
line. And make sure that my_role exists in that array.
Temporary comment this line for debugging purpose:
// if (is_admin() && is_post_type_archive())
Then you will see the reason and able to be fix it.