Reduce Product Stock on Adding to Cart - Woocommerce - php

Got a (hopefully quick!) question on updating stock levels for products when they are added to the cart (and preferably "releasing" them back to stock if they are removed from the cart prior to checkout) - how easy is it to achieve something like this, and what would the best steps be?
I can see there are a few functions/filters that could be called in to play like woocommerce_stock_amount, wc_update_product_stock and add_to_cart_class and so forth, but I'd love any guidance you could spare on how to bring it all together :)
So far, this is what has been arrived at, and it's working well, but with one rather important issue! If a product is a "one-off", in other words, only has one available in stock before it is added to the cart, the visitor cannot checkout as the script below has reduced the stock to zero!
Thanks in advance for any help with this :)
add_action('woocommerce_add_to_cart', 'update_product_stock_on_add', 10, 6);
function update_product_stock_on_add($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
global $woocommerce;
$product = get_product($product_id);
$woocommerce->cart->cart_contents[$cart_item_key]['old_stock_quantity'] = $product->get_stock_quantity();
$woocommerce->cart->cart_contents[$cart_item_key]['add_time'] = time();
wc_update_product_stock($product_id, $product->get_stock_quantity() - $quantity);
}
add_action('woocommerce_after_cart_item_quantity_update', 'update_product_stock_on_update', 10, 2);
function update_product_stock_on_update($cart_item_key, $quantity) {
global $woocommerce;
$cart_item = $woocommerce->cart->cart_contents[$cart_item_key];
wc_update_product_stock($cart_item['product_id'], $cart_item['old_stock_quantity'] - $quantity);
}
add_action('woocommerce_before_cart_item_quantity_zero', 'update_product_stock_on_zero', 10, 2);
function update_product_stock_on_zero($cart_item_key, $quantity) {
global $woocommerce;
$cart_item = $woocommerce->cart->cart_contents[$cart_item_key];
wc_update_product_stock($cart_item['product_id'], $cart_item['old_stock_quantity']);
}
add_filter('woocommerce_get_cart_item_from_session', 'load_old_stock_from_session', 10, 3);
function load_old_stock_from_session($cart_item, $values, $key) {
$cart_item['old_stock_quantity'] = $values['old_stock_quantity'];
$cart_item['add_time'] = $values['add_time'];
return $cart_item;
}
add_action('woocommerce_cart_loaded_from_session', 'check_cart_item_timeout');
function check_cart_item_timeout($cart) {
foreach ($cart->get_cart() as $cart_item_key => $values) {
if ((time() - $values['add_time']) >= 10) {
wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $values['data']->get_title() ), 'error' );
unset($cart->cart_contents[$cart_item_key]);
wc_update_product_stock($values['product_id'], $values['old_stock_quantity']);
}
}
}

Related

Checkout price different from cart price using woocommerce_product_get_price hook

I´m running a WooCommerce (WordPress 6.1.1 and WooCommerce 7.3.0), and I´m trying to set prices according to the user role.
To do this I have introduced a new field named Webprice (precio_web) in product definition using the plugin: "Advanced Custom Fields". Customer users and not logged users must use this special price.
Also I added this code in my functions.php child-theme:
add_filter('woocommerce_product_get_price', 'ui_custom_price_role', 99, 2);
add_filter('woocommerce_product_get_regular_price', 'ui_custom_price_role', 99, 2);
add_filter('woocommerce_product_variation_get_regular_price', 'ui_custom_price_role', 99, 2);
add_filter('woocommerce_product_variation_get_price', 'ui_custom_price_role', 99, 2);
function ui_custom_price_role($price, $product) {
$price = ui_custom_price_handling($price, $product);
return $price;
}
Variable add_filter('woocommerce_variation_prices_price', 'ui_custom_variable_price', 99, 3);
add_filter('woocommerce_variation_prices_regular_price', 'ui_custom_variable_price', 99, 3);
function ui_custom_variable_price($price, $variation, $product) {
$price = ui_custom_price_handling($price, $product);
return $price;
function ui_custom_price_handling($price, $product) {
//get our current user
$current_user = wp_get_current_user();
//check if the user role is the role we want or is not logged
if ((!is_user_logged_in()) || (isset($current_user - \ > roles\[0\]) && '' != $current_user - \ > roles\[0\] && in_array('customer', $current_user - \ > roles))) { //load the custom price for our product $custom_price = get_post_meta( $product-\>get_id(), 'precio_web', true );
// custom price
if (!empty($custom_price)) {
$price = $custom_price;
}
}
return $price;
}
}
So far It works, when adding items to the cart I can see the new price.
enter image description here
The problem is at checkout for some reason prices displayed are the standard, not the ones corresponding to the new field.
enter image description here
Any help is welcome. Thanks.
I was expecting that the modification of the get_price function was enough because the prices are displayed correctly. However, the order is recorded with the standard price of the item.
WooCommerce recalculates all prices during the checkout process multiple times, so you need to hook into this recalculation also. This should work for you:
add_action( 'woocommerce_before_calculate_totals', 'update_cart_price'), 99);
function update_cart_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) return;
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
$cart_item = $cart_item['data'];
$cart_item_product_id = $cart_item->get_id();
$product = wc_get_product( $cart_item_product_id );
$orig_price = $product->get_regular_price();
$new_price = ui_custom_price_handling( $orig_price, $product );
$cart_item->set_price( $new_price );
}
}

Woocommerce cart quantity change stop changing the final price

By default when woocommerce cart quantity is changed it updates the item price by multiplying it by the quantity. I would like to change the default behaviour and ensure that when in the cart when the quantity is changed it doesnt change the default price passed when add to cart is clicked
I have added this action hook but i cannot figure out how to stop price change when quantity is changed in a cart.
add_action( 'woocommerce_after_cart_item_quantity_update', 'on_quantity_changed_in_cart', 20, 4 );
function on_quantity_changed_in_cart( $cart_item_key, $quantity, $old_quantity, $cart){
if( ! is_cart() ) return; // Only on cart page
//here stop price from been changed by default
}
Looking to "Disable Woocommerce cart line item quantity price calculation" answer thread, I found out I need to override an action hook to replace the final cost per line:
add_filter('woocommerce_cart_product_subtotal', [$this, 'filter_woocommerce_cart_product_subtotal'], 10, 4);
public function filter_woocommerce_cart_product_subtotal($product_subtotal, $product, $quantity, $cart){
$sub_value = 0;
foreach ($cart->cart_contents as $hash => $value) {
if ($value["product_id"] === wc_get_product($product)->get_id()) {
$sub_value = $value["wcform-custom_price"];
//wcform-custom_price is passed from when adding to the cart.
}
};
return wc_price($sub_value);
}
Now on the totals as well I overriden the following
add_action( 'woocommerce_calculate_totals', [$this,'custom_item_price'],20,1);
add_filter( 'woocommerce_calculated_total', [$this,"calculateFinalTotal"], 20, 2 );
public function custom_item_price( $wc_cart ) {
$cart_contents_total = 0;
foreach ( $wc_cart->get_cart() as $cart_item_key => $cart_item ){
$cart_contents_total += $cart_item["wcform-custom_price"];
}
$wc_cart->subtotal = $cart_contents_total;
}
public function calculateFinalTotal($total,$cart){
$cart_contents_total = 0;
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
$cart_contents_total += $cart_item["wcform-custom_price"];
}
return $cart_contents_total;;///print_r($cart->get_cart());
}

Conditionally making WooCommerce product non purchasable

I've been struggling with a snippet for WooCommerce that I'd like to add to my child's theme functions but I am reaching the limits of my PHP knowledge.
I have set a custom attribute called pdt_volume, that I will fill everytime I add a product. I would like to remove the Add_to_cart button when the sum of this attribute for all the items already in the cart have reach a max_volume limit.
For example, the Max_volume is 100, I have 4 products of pdt_volume = 20, therefore, the add_to_cart button for a product with a pdt_volume of 25 should be removed so that I cannot add it to the cart.
So I have come up with this snippet, with bits of code found here and there.
But the functions.php won't save it, and others variations of this code has succeeded to register in the functions.php but the website would then give a 500 error...
Please, does someone has any idea how to achieve this?
What am I doing wrong?
Thanks.
EDIT 1 : Alright, so I got this code to actually be registered in the functions.php by the editor without breaking, but on the site, I still get the internal server error. Like something is not computing or something.
EDIT 2 (29/01/2017)
I used the ACF plugin I had purchased a long time ago but didn't find any suitable purpose... so far.
Here is the working code I was able to come up with. This is not a code masterpiece and I won't get any award for this, but it seems to be working so far. At least, it allows me to get a TRUE/FALSE statement that I can use in a if condition to change the add_to_cart button to a Your_box_is_full button.
Indeed, I didn't need any global $woocommerce or $product !
function get_cart_volume() {
$cart_volume = 0;
foreach( WC()->cart->get_cart() as $cart_item ) {
$item_id = $cart_item['product_id'];
$item_volume = get_field('product_volume', $item_id);
$item_qty = $cart_item['quantity'];
$vol_by_qty = $item_volume * $item_qty;
$cart_volume += $vol_by_qty;
}
return $cart_volume;
}
function check_if_full($candidate) {
$max_volume = 100;
$candidate = $product->id;
$a_volume = get_field('product_volume', $candidate);
$b_volume = get_cart_volume();
$check_volume = $a_volume + $b_volume;
if ($check_volume > $max_volume)
return true;
else
return false;
}
//Just a function to see if it's working on the cart page fur debugging purpose
add_action( 'woocommerce_check_cart_items', 'current_volume');
function current_volume() {
if (is_cart() || is_checkout()) {
global $woocommerce;
$current_volume = get_cart_volume();
wc_add_notice( sprintf( '<strong>Volume is %s.</strong>', $current_volume ),
'error' );
}
}
As Helgatheviking says, you get already, with woocommerce_is_purchasable hook in your hooked function, the $product object as 2nd argument. So you don't need and you have to remove global $woocommerce, $product; to make it work without throwing an error.
Your code is going to be:
add_filter('woocommerce_is_purchasable', 'if_room_is_purchasable', 10, 2);
function if_room_is_purchasable ($is_purchasable, $product){
$cart_volume = 0;
$max_volume = 100;
foreach( WC()->cart->get_cart() as $cart_item ){
$item_id = $cart_item['product_id'];
$terms = get_the_terms( $item_id, 'pa_pdt_volume');
foreach($terms as $key => $term){
$cart_volume += $term->name; // May be better with $term->slug;
}
}
$candidate_volume = $cart_volume + $product->get_attribute( 'pa_pdt_volume' );
if ( $candidate_volume > $max_volume )
return false;
else
return true;
}
This should work now without error.

WooCommerce - Automatically add bonus product

I would like to make a script for automatically adding a bonus product to WooCommerce cart.
The bonus product would be added into the cart on adding a specific product(s). But I've never seen any improved and fully working code with features - such as automatically removing the bonus item or removing the primary product(s) from the cart.
In this solution I've come up with the below code which has the following features:
Options
Multiple required products
Automatic adding
Automatic removing (if there's no required product in the cart)
function bonus_product() {
if (is_admin()) return;
//## OPTIONS
$options = (object) array(
'bonus_product_id' => 1891, //bonus product to add
'required_products_id' => array(1873), //at least on of the specific product(s) needs to be represented in the cart
);
//function variables
$cart_items = WC()->cart->get_cart();
$bonus_product_found = false;
$required_product_found = false;
//check if the cart is not empty
if(sizeof($cart_items) > 0) {
//checking for required products. loop through the cart items
foreach ($cart_items as $key => $item) {
//bonus product already in the cart?
if($item['product_id'] == $options->bonus_product_id) {
$bonus_product_found = true;
}
//one of required products in the cart?
if(in_array($item['product_id'], $options->required_products_id)) {
$required_product_found = true;
}
}
//adding/removing bonus product
//add bonus product to the cart
if(!$bonus_product_found && $required_product_found) {
WC()->cart->add_to_cart($options->bonus_product_id);
}
//remove bonus product from the cart if none of required items is in the cart
if($bonus_product_found && !$required_product_found) {
$cart = WC()->instance()->cart;
$cart_id = $cart->generate_cart_id($options->bonus_product_id);
$cart_item_id = $cart->find_product_in_cart($cart_id);
$cart->set_quantity($cart_item_id, 0);
}
}
}
add_action( 'init', 'bonus_product' );
I have written an alternative version, based on Add To Cart and Remove From Cart action, which seems to be more appropriate.
$bonus_options = (object) array(
'bonus_product_id' => 1891,
'required_products_id' => array( 1873 )
);
// this function called whenever there is a product added to cart
function add_bonus_product( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
global $bonus_options;
// is the product is eligible for bonus product?
if( in_array( $product_id, $bonus_options->required_products_id) ) {
// add the bonus product to cart
WC()->cart->add_to_cart( $bonus_options->bonus_product_id, 1, 0, array(), array( "parent_product_line_item" => $cart_item_key ) );
// later if user removes the product from cart we can use the "parent_product_line_item" to remove the bonus product as well
}
}
add_action( 'woocommerce_add_to_cart', 'add_bonus_product', 10, 6 );
// this function will be called whenever there is a product removed from cart
function remove_bonus_product( $cart_item_key, $cart ) {
$cart_items = WC()->cart->get_cart();
foreach ( $cart_items as $key => $item ) {
if( $item["parent_product_line_item"] == $cart_item_key ) {
// ok this cart item is a bonus item to the product that being removed from the cart
// So remove this too
WC()->cart->remove_cart_item( $key );
}
}
}
add_action( 'woocommerce_cart_item_removed', 'remove_bonus_product', 10, 2 );

WooCommerce: Add product to cart with price override?

$replace_order = new WC_Cart();
$replace_order->empty_cart( true );
$replace_order->add_to_cart( "256", "1");
The above code add product 256 to the Cart 1 time. But the issue I'm having is that I want to be able to completely override the product price... as far as I can tell, the only thing I can do it apply a coupon to the Cart.
Is there a way to completely override the price to something totally custom?
Here is the code for overriding price of product in cart
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->price = $custom_price;
// for WooCommerce version 3+ use:
// $value['data']->set_price($custom_price);
}
}
Hope it will be useful...
You need to introduce an if statement for checking product id, in above code:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
$target_product_id = 598;
foreach ( $cart_object->cart_contents as $value ) {
if ( $value['product_id'] == $target_product_id ) {
$value['data']->price = $custom_price;
}
/*
// If your target product is a variation
if ( $value['variation_id'] == $target_product_id ) {
$value['data']->price = $custom_price;
}
*/
}
}
Add this code anywhere and make sure that this code is always executable.
After adding this code, when you'll call:
global $woocommerce;
$woocommerce->cart->add_to_cart(598);
Only this product will be added with overridden price, other products added to cart will be ignored for overriding prices.
Hope this will be helpful.
I have tried all above code samples and latest woocommerce 3.0 is not support any of the above example. Use below code and working perfectly for me.
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custom price
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$cart_item['data']->set_price($custom_price);
}
}
After release of woocommerce version 3.0.0 product price is update on add to cart using set_price($price) function. The example is given as below :
add_action( 'woocommerce_before_calculate_totals', 'mj_custom_price' );
function mj_custom_price( $cart_object ) {
$woo_ver = WC()->version;
$custom_price = 10;
foreach ( $cart_object->cart_contents as $key => $value )
{
if($woo_ver < "3.0.0" && $woo_ver < "2.7.0")
{
$value['data']->price = $custom_price;
}
else
{
$value['data']->set_price($custom_price);
}
}
}
Many Thanks
For the Wordpress and Woocommerce latest version,Please use like this
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
foreach ( $cart_object->cart_contents as $key => $value ) {
$custom_price = 5;
$value['data']->set_price($custom_price);
}
}
For eveeryone that got here from Google. The above is now deprecated as i found out updating to WooCommerce 3.0.1.
Instead of the above you now need to use set_price instead of price
Here is an example:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->set_price = $custom_price;
}
}
I hope this helps people in the future :)
This is how i did it, first i add my custom price to cart_item_data witch can save custom data to cart items, then i use woocommerce_before_calculate_totals, loop the cart and add the previously added price.
function add_donation_to_cart() {
$cart_item_data = array('price' => $_REQUEST['donate_amount']);
$woocommerce->cart->add_to_cart( 5395, 1, '', array(), $cart_item_data);
}
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart ) {
foreach ( $cart->cart_contents as $key => $value ) {
$value['data']->price = $value['price'];
}
}
With WooCommerce 2.5 I found this to be a 2-part process. The first step is to change the run-time display of pricing when added to the cart via the woocommerce_add_cart_item filter. The second part is to set the persistent session data which is read during checkout via the woocommerce_get_cart_item_from_session filter. This seems to be faster than hooking the calculate totals filters (such as woocommerce_before_calculate_totals) as they are run very frequently in WooCommerce.
More details here:
woocommerce change price while add to cart
To make it dynamic ( override price for each item in cart separately ), you need to save the override product price in session with cart item key as session key using woocommerce_add_to_cart hook.
by using these session values you can calculate correct Cart Total and make the altered price appear in the Order Item as well
You can use the following
add_filter( 'woocommerce_cart_item_price', 'kd_custom_price_message' );
function kd_custom_price_message( $price ) {
$textafter = ' USD';
return $price . $textafter;
}

Categories