I'm trying to update the quantities of an ordered product and when doing so I would like the order to reflect the actual cost. What I find happens is that the Product Cost is reduced to match the total and the order total is never actually updated. I've supplied a simple sample below:
function prefix_update_woo_order() {
$order_id = 123; // This needs to be a real order or there will be errors
$order_item_id = 5; // This needs to be a real order item ID or there will be errors.
$order = new WC_Order( $order_id );
$order_items = $order->get_items();
$order_items[ $order_item_id ]->set_quantity( 2 );
$order->calculate_taxes();
$order->calculate_totals();
$order->save();
}
add_action( 'admin_init', 'prefix_update_woo_order' );
For example, a "Beanie with Logo" product is on sale for $18.00 and I originally buy 1. I want to programmitically update the order item to a quantity of 2 instead of 1 after the order has been placed. I would expect the total to be $36.00 but what I'm finding is that the product cost changes to match the total price. Instead of a cost of $18.00 for a "Beanie with Logo" the quantity is updated to 2 and the cost is reduced to $9.00ea.
In short, what I want to do is update an existing order items quantity and have the totals updated to reflect the new quantity cost, discount, taxes. What methods do I need to use to achieve this?
Hello I think this code will change your problem
add_action( 'admin_init', 'test_order_update_order' );
function test_order_update_order() {
$order_id = 80; // This needs to be a real order or there will be errors
$order_item_id = 11; // This needs to be a real order item ID or there will be errors.
$quantity = 2; //quantity which you want to set.
$order = new WC_Order( $order_id );
$order_items = $order->get_items();
foreach ( $order_items as $key => $value ) {
if ( $order_item_id == $key ) {
$product_value = $value->get_data();
$product_id = $product_value['product_id'];
}
}
$product = wc_get_product( $product_id );
$price = $product->get_price();
$price = ( int ) $quantity * $price;
$order_items[ $order_item_id ]->set_quantity( 2 );
$order_items[ $order_item_id ]->set_subtotal( $price );
$order->calculate_taxes();
$order->calculate_totals();
$order->save();
}
i have not tried what you are trying to achieve before. What i see in your code is that $order_items is an array of Items Objects produced from WC_Order::get_items(), but i dont see the WC_Order Instance being notified that the order items have changed. I would expect a method like $order->update_cart($order_items); I believe i found some usefull links for more research
https://hotexamples.com/examples/-/WC_Order/-/php-wc_order-class-examples.html
woocommerce - programmatically update cart item quantity
Sorry, i was not much of help!
Use the follows code snippet to do the above task -
function prefix_update_woo_order() {
$order_id = 123; // This needs to be a real order or there will be errors
$order_item_id = 5; // This needs to be a real order item ID or there will be errors.
$order = wc_get_order( $order_id );
if( $order && !wc_get_order_item_meta( $order_item_id, '_order_item_data_updated', true ) ) {
$item_price = wc_get_order_item_meta( $order_item_id, '_line_total', true );
$updated_item_quantity = 2;
wc_update_order_item_meta( $order_item_id, '_qty', $updated_item_quantity );
wc_update_order_item_meta( $order_item_id, '_line_total', $item_price * $updated_item_quantity );
$order->calculate_totals();
$order->save();
// set flag
wc_add_order_item_meta( $order_item_id, '_order_item_data_updated', true, true );
}
}
add_action( 'admin_init', 'prefix_update_woo_order' );
Codes goes to your active theme's functions.php
This is what I've come up with. I've tried to use the wc_format_decimal() where applicable. Seems like a lot of work to simply update an order items quantity but it is what it is.
The bottom comment is unnecessary but if you're using the Cost of Goods plugin then that will take care of that.
/**
* Update the order item quantity and totals
* #param Integer $order_id
* #param Integer $order_item_id
* #param Integer $quantity - Quantity to set
*
* #return void
*/
function prefix_update_woo_order( $order_id, $order_item_id, $quantity ) {
// Get Order, Item, and Product Data
$order = new WC_Order( $order_id );
$order_items = $order->get_items();
$line_item = $order_items[ $order_item_id ];
$variation_id = $line_item->get_variation_id();
$product_id = $line_item->get_product_id();
$product = wc_get_product( $variation_id ? $variation_id : $product_id );
$quantity_old = $line_item->get_quantity();
// Calculate Old and New Discounts
$discount = wc_format_decimal( $line_item->get_subtotal() - $line_item->get_total(), '' );
if( ! empty( $discount ) ) {
$discount_per_qty = wc_format_decimal( ( $discount / $quantity_old ), '' );
$discount = wc_format_decimal( ( $discount_per_qty * $quantity ), '' );
}
// Set Quantity and Order Totals
$line_item->set_quantity( $quantity );
$total = wc_get_price_excluding_tax( $product, array( 'qty' => $line_item->get_quantity() ) ); // Also see `wc_get_price_excluding_tax()`
$line_item->set_subtotal( $total ); // Without Discount
$line_item->set_total( $total - $discount ); // With Discount
// Save Everything
$line_item->save();
wc_save_order_items( $order_id, $order_items );
/**
* If using the 'Cost of Goods' Plugin
* - - - - - -
* $cog = $line_item->get_meta( '_wc_cog_item_cost', true );
* $new_cog = wc_format_decimal( ( $quantity * $cog ), '' );
* $line_item->update_meta_data( '_wc_cog_item_total_cost', $new_cog );
* wc_cog()->set_order_cost_meta( $order_id, true );
*/
}
Related
This code I wrote displays personalized information on the WooCommerce checkout page.
add_action( 'woocommerce_cart_totals_after_order_total', 'show_total_discount_cart_checkout', 9999 );
add_action( 'woocommerce_review_order_after_order_total', 'show_total_discount_cart_checkout', 9999 );
function show_total_discount_cart_checkout() {
$discount_total = 0;
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$subtotal = WC()->cart->get_product_subtotal( $product, $cart_item['quantity'] );
$total = WC()->cart->total;
$pctm = 90.00;
$valor_descontado = $total - ($total / 100 * $pctm);
$sale_price = '10%';
$discount = ( WC()->cart->total - $valor_descontado );
$discount_total = $discount;
}
if ( $discount_total > 0 ) {
echo '<tr><th>VOCÊ RECEBERÁ DE CASHBACK:</th><td data-title="You">' . wc_price( $discount_total + WC()->cart->get_discount_total() ) .'</td></tr>';
}
}
The result:
I need this information to also be displayed in WooCommerce orders and emails. I believe I can come up with a solution myself to display this value on several other pages, but can someone first tell me how to save/store the value of this calculation?
First of all, I've rewritten your existing code for the following reasons:
Requesting subtotals and totals is best done outside the foreach loop, because otherwise these values will be overwritten every time
The result of $subtotal is not used anywhere in your code
Since the result of $subtotal is not used anyway, loop through the cart seems unnecessary
$sale_price is also not used anywhere in your code
Since $discount_total = $discount it is not necessary to use a new variable
A session variable is created/added
Your existing code, but optimized:
function action_woocommerce_after_order_total() {
// WC Cart NOT null
if ( ! is_null( WC()->cart ) ) {
// Get cart
$cart = WC()->cart;
// Getters
$cart_total = $cart->total;
$cart_discount_total = $cart->get_discount_total();
// Settings
$pctm = 90;
// Calculations
$discounted_value = $cart_total - ( $cart_total / 100 * $pctm );
$discount_total = $cart_total - $discounted_value;
// Greater than
if ( $discount_total > 0 ) {
// Result
$result = $discount_total + $cart_discount_total;
// The Output
echo '<tr class="my-class">
<th>' . __( 'VOCÊ RECEBERÁ DE CASHBACK', 'woocommerce' ) . '</th>
<td data-title="You">' . wc_price( $result ) . '</td>
</tr>';
// Set session
WC()->session->set( 'session_result', $result );
}
}
}
add_action( 'woocommerce_cart_totals_after_order_total', 'action_woocommerce_after_order_total', 10 );
add_action( 'woocommerce_review_order_after_order_total', 'action_woocommerce_after_order_total', 10 );
To answer your question:
Step 1) We get the result from the session variable and add it as order data, so that we can use/obtain this information everywhere via the $order object
// Add as custom order meta data and reset WC Session variable
function action_woocommerce_checkout_create_order( $order, $data ) {
// Isset
if ( WC()->session->__isset( 'session_result' ) ) {
// Get
$result = (float) WC()->session->get( 'session_result' );
// Add as meta data
$order->update_meta_data( 'result', $result );
// Unset
WC()->session->__unset( 'session_result' );
}
}
add_action( 'woocommerce_checkout_create_order', 'action_woocommerce_checkout_create_order', 10, 2 );
Step 2) Use the woocommerce_get_order_item_totals filter hook, which will allow you to add a new row to the existing tables with the $result.
The new row will be added in:
Email notifications
Order received (thank you page)
My account -> view order
function filter_woocommerce_get_order_item_totals( $total_rows, $order, $tax_display ) {
// Get meta
$result = $order->get_meta( 'result' );
// NOT empty
if ( ! empty ( $result ) ) {
// Add new row
$total_rows['total_result']['label'] = __( 'VOCÊ RECEBERÁ DE CASHBACK', 'woocommerce' );
$total_rows['total_result']['value'] = wc_price( $result );
}
return $total_rows;
}
add_filter( 'woocommerce_get_order_item_totals', 'filter_woocommerce_get_order_item_totals', 10, 3 );
I have an ACF field "DeliverySpeed" attached to Products with the values 'Fast' and 'Slow'
I would like to update this field to change to value 'Slow' every time the product stock is zero or less (product on backorder)
I am still learning PHP so far this is what I got to but I am sure different things are missing, I just don't know in which direction to go:
based on acf update field documentation
function automatically_change_delivery( ) {
global $product;
$fieldkey = "DeliverySpeed";
$valueslow = "Slow";
if($product->get_stock_quantity()<0) { update_field( $field_key, $valueslow, $post_id );
} }
Thank you in advance for the attention and advice.
The following code will update automatically your product custom field once order has reduced product stock levels. So when product has stock the custom field value will be 'Fast' otherwise 'Slow'.
The code:
add_action( 'woocommerce_payment_complete', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_completed', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_processing', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_on-hold', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
function update_product_custom_field_( $order_id, $order = '' ) {
// Continue only when order has reduced product stock levels
if ( wc_string_to_bool( get_post_meta( $order_id, '_order_stock_reduced', true ) ) )
return $order_id; // Exit
if( ! $order || ! is_a( $order, 'WC_Order') ) {
$order = wc_get_order( $order_id ); // Get the WC_Order object if it's empty
}
$field_key = 'DeliverySpeed';
// Loop through order items
foreach ( $order->get_items() as $item ) {
$product = $cart_item['data'];
$product_id = $product->get_id();
$stock_qty = $product->get_stock_quantity();
$field_value = get_field( $field_key, $product_id ); // Get ACF field value
if ( $stock_qty <= 0 && $field_value === 'Fast' ) {
update_field( $field_key, 'Slow', $product_id );
}
elseif ( $stock_qty > 0 && $field_value === 'Slow' ) {
update_field( $field_key, 'Fast', $product_id );
}
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
We have customers who have different discount percentages. This is all programmed and working on the front end when adding products to the cart, but if using the back end order admin to add a new product I can't seem to find a way to calculate the new product price based on the user's discount. Is there a way to change the price in the woocommerce_new_order_item hook when adding a product to an existing order in the order admin?
Here's what I have so far:
function action_woocommerce_new_order_item( $item_id, $item, $order_id ) {
// only run this from the WP admin section
if ( !is_admin() )
return;
$item_type = $item->get_type();
// return if this is not a product (i.e. fee, tax, etc.)
if ( $item_type != 'line_item' )
return;
$product = wc_get_product( $item->get_product_id() );
if ( !$product )
return;
$current_price = $product->get_price();
$quantity = $item->get_quantity();
// here I get the order's user's discount percentage and calculate the new discounted price
// custom function
$discounted_price = get_discounted_price( $current_price, $users_discount );
$new_price = ( !empty($discounted_price) ) ? $discounted_price : $current_price;
// this doesn't work
$item->set_price( $new_price );
// and this doesn't work
$product->set_price( $new_price );
// this appears to work but I'm not sure if this the best way to accomplish this
$item->set_total( $price * $quantity );
}
add_action( 'woocommerce_new_order_item', 'action_woocommerce_new_order_item', 10, 3 );
Any help would be greatly appreciated!
Thanks
This works for adding item meta and changing the item price in the order admin screen:
function action_woocommerce_new_order_item( $item_id, $item, $order_id ) {
// only run this from the WP admin section
if ( !is_admin() )
return;
$item_type = $item->get_type();
// return if this is not a product (i.e. fee, tax, etc.)
if ( $item_type != 'line_item' )
return;
$product = wc_get_product( $item->get_product_id() );
if ( !$product )
return;
$current_price = $product->get_price();
$size = $product->get_attribute('size');
// add custom item meta to order
if ( $size ) {
wc_add_order_item_meta( $item_id, 'Size', $size , false );
}
$order = wc_get_order( $order_id );
$order_user = $order->get_user();
$order_user_id = $order->get_user_id();
// get this order's user's discount %
$users_discount = get_user_meta( $order_user_id, 'discount', true );
$users_discount = ( $users_discount > 0 ) ? $users_discount : 0;
// calculate the discounted price based on the user's discount (custom function)
$discounted_price = get_discounted_price( $current_price, $users_discount );
$quantity = $item->get_quantity();
$new_price = ( !empty($discounted_price) ) ? $discounted_price : $current_price;
$item->set_total( $new_price * $quantity );
};
add_action( 'woocommerce_new_order_item', 'action_woocommerce_new_order_item', 10, 3 );
In our woocommerce shop , customer can enter the custom width and height of the product and product price calculated based on this details .
For example if the initial price for a product is 50 . And customer add width =2, height=3 , then the price for this product is going to 50*2*3=300
for this we are using following code
// Save custom field value in cart item as custom data
add_filter( 'woocommerce_add_cart_item', 'calculate_custom_cart_item_prices', 30, 3 );
function calculate_custom_cart_item_prices( $cart_item_data, $product_id, $variation_id ) {
if ( isset($_POST['width']) && isset($_POST['height']) ) {
// Get the correct Id to be used (compatible with product variations)
$the_id = $variation_id > 0 ? $variation_id : $product_id;
$product = wc_get_product( $the_id ); // Get the WC_Product object
$product_price = (float) $product->get_price(); // Get the product price
// Get the posted data
$width = (float) sanitize_text_field( $_POST['width'] );
$height = (float) sanitize_text_field( $_POST['height'] );
$new_price = $width * $height * $product_price; // Calculated price
$cart_item_data['calculated-price'] = $new_price; // Save this price as custom data
}
return $cart_item_data;
}
// Set custom calculated price in cart item price
add_action( 'woocommerce_before_calculate_totals', 'set_calculated_cart_item_price', 20, 1 );
function set_calculated_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ){
if( ! empty( $cart_item['calculated-price'] ) ){
// Set the calculated item price (if there is one)
$cart_item['data']->set_price( $cart_item['calculated-price'] );
}
}
And it is wormking , but the problem is:
when customer apply a 50% coupon code for this product then the
discount is coming as 25 , because it calculating based on 50*(50/100)=25;
But actually product new price is 300, so the discount should be
300*(50/100)=150;
Try updating your 'calculate_custom_cart_item_prices' function to something like this and see if that helps.
add_filter( 'woocommerce_add_cart_item', 'calculate_custom_cart_item_prices', 30, 2 );
function calculate_custom_cart_item_prices( $cart_item_data, $cart_item_key ) {
if ( isset($_POST['width']) && isset($_POST['height']) ) {
// Get the correct Id to be used (compatible with product variations)
$the_id = $cart_item_data['variation_id'] > 0 ? $cart_item_data['variation_id'] : $cart_item_data['product_id'];
$product = wc_get_product( $the_id ); // Get the WC_Product object
$product_price = (float) $product->get_price(); // Get the product price
// Get the posted data
$width = (float) sanitize_text_field( $_POST['width'] );
$height = (float) sanitize_text_field( $_POST['height'] );
$new_price = $width * $height * $product_price; // Calculated price
$cart_item_data['calculated-price'] = $new_price; // Save this price as custom data
}
return $cart_item_data;
}
My guess as to what is happening is a change in Woocommerce has changed the way the 'woocommerce_add_cart_item' filter works and so you need to update this function.
I have used the hook woocommerce_before_calculate_totals but stuck in getting dynamic value in my function. Even i have used session but it didn't work on passing dynamic price.
The following snippet adds 100 to all the products in the cart, you can put your own conditions and pricing calculation there.
function calculate_product_price( $cart_object ) {
/* Gift wrap price */
$additionalPrice = 100;
foreach ( $cart_object->cart_contents as $key => $value ) {
//You can add your condition here
$quantity = floatval( $value['quantity'] );
$orgPrice = floatval( $value['data']->price );
$value['data']->price = ( ( $orgPrice + $additionalPrice ) * $quantity );
}
}
add_action( 'woocommerce_before_calculate_totals', 'calculate_product_price', 1, 1 );