Woocommerce: Custom price based on user input - Mini cart conflict - wordpress

I follow the solution of this post to get the custom price from the input field with a cookie and it works properly except mini-cart.
My products are added to the cart with AJAX and the mini-cart doesn't load the new value of the cookie. It seems to be cached to the previous value of the input field until you reach to the cart page and after hard reload.
For example, I visit the page where I enter the custom price. The first time I put 20 euros, I press the add to cart button, my product is added to the cart via AJAX and it works well.
If I remove this product with the custom price and try to add it again with a different price at this time, the mini-cart keeps the previous price(20 euros).
So, the question is if there is a way to keep the mini-cart updated with the last inserted price?

Gratuitous self-promotion, but my Name Your Price should automatically work with the mini-cart.
But I think your question is actually asking why the item isn't being considered as unique... and therefore added a second time. The answer is that a different price will render a unique $cart_id see source. With a unique ID the item is not found in the cart, and so it is added again.
To force a 'sold individually' item with different prices to be truly sold individually, you need to change the way the cart ID is generated, by filtering woocommerce_cart_id. Here is how I do it to work with my Name Your Price plugin. You would need to adapt it to your own code.
<?php
/**
* Plugin Name: WooCommerce Name Your Price Sold Individually
* Plugin URI: https://gist.github.com/helgatheviking/a8802255167751a5dd746f83cdfc8716
* Description: Double check enforcement of "Sold Individually" for NYP items
* Version: 1.1.0
* WC requires at least: 2.6.3
* Author: Kathy Darling
* Author URI: http://kathyisawesome.com/
*
* Copyright: © 2016 Kathy Darling
* License: GNU General Public License v3.0
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
*/
function wc_nyp_force_sold_individually( $cart_id, $product_id, $variation_id, $variation, $cart_item_data ) {
// Get the product
$product = wc_get_product( $variation_id ? $variation_id : $product_id );
if ( $product->is_sold_individually() && WC_Name_Your_Price_Helpers::is_nyp($product) ){
$id_parts = array( $product_id );
if ( $variation_id && 0 != $variation_id ) {
$id_parts[] = $variation_id;
}
if ( is_array( $variation ) && ! empty( $variation ) ) {
$variation_key = '';
foreach ( $variation as $key => $value ) {
$variation_key .= trim( $key ) . trim( $value );
}
$id_parts[] = $variation_key;
}
$cart_id = md5( implode( '_', $id_parts ) );
}
return $cart_id;
}
add_filter( 'woocommerce_cart_id', 'wc_nyp_force_sold_individually', 10, 5 );

Related

WooCommerce function that disables an admin user to reduce the product stock quantity

I would like to add a function that is triggered every time that the stock quantity of a product will be changed in the admin product page, such that this function will not allow any reduce of the stock value - but only increase.
This is to prevent an admin user to reduce the stock quantity of the products.
Of course, this function should not be triggered if a product will be in an order, since then of course I would like the stock quantity to be reduced.
I tried the following function in the functions.php but unfortunately did not work.
Since I'm new to woocommerce and php, any ideas that could provide a solid solution to the problem?
// get old and new product stock quantity
function get_old_and_new_product_quantity_stock( $sql, $product_id_with_stock, $new_stock, $operation ) {
$product = wc_get_product( $product_id_with_stock );
$old_stock_quantity = $product->get_stock_quantity();
$new_stock_quantity = $new_stock;
echo $old_stock_quantity, $new_stock_quantity;
if ($new_stock_quantity < $old_stock_quantity) {
$new_stock = $old_stock_quantity;
$new_stock_quantity = $old_stock_quantity;
}
return $sql;
}
add_filter( 'woocommerce_update_product_stock_query', 'get_old_and_new_product_quantity_stock', 10, 4 );
You can use the update_post_meta action hook to check if the new value is less than the previous value and display error message.
This will work for quick edit and for product edit page. But the wp_die on product page will look bad so use the javascript to prevent submitting on product edit page (there was another question about it yesterday)
Be sure to test this snippet and create some orders that will reduce the stock automatically. I added is_admin() check but please do a good test.
add_action( 'update_post_meta', 'prevent_reducing_stock_metadata', 10, 4 );
function prevent_reducing_stock_metadata( $meta_id, $post_id, $meta_key, $meta_value ) {
// Check if the meta key is _stock and the new value is less than the previous value
if ( '_stock' == $meta_key && $meta_value < get_post_meta( $post_id, '_stock', true ) ) {
// Check if this is an update from the WordPress admin area
if ( is_admin() ) {
wp_die( __( 'Error: You cannot reduce the stock level for this product.' ), 'error' );
}
}
}

Price in the shopping cart [duplicate]

I am trying to change product price in cart using the following function:
add_action( 'woocommerce_before_shipping_calculator', 'add_custom_price'
);
function add_custom_price( $cart_object ) {
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->price = 400;
}
}
It was working correctly in WooCommerce version 2.6.x but not working anymore in version 3.0+
How can I make it work in WooCommerce Version 3.0+?
Thanks.
Update 2021 (Handling mini cart custom item price)
With WooCommerce version 3.0+ you need:
To use woocommerce_before_calculate_totals hook instead.
To use WC_Cart get_cart() method instead
To use WC_product set_price() method instead
Here is the code:
// Set custom cart item price
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 1000, 1);
function add_custom_price( $cart ) {
// This is necessary for WC 3.0+
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Avoiding hook repetition (when using price calculations for example | optional)
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
$cart_item['data']->set_price( 40 );
}
}
And for mini cart (update):
// Mini cart: Display custom price
add_filter( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 3 );
function filter_cart_item_price( $price_html, $cart_item, $cart_item_key ) {
if( isset( $cart_item['custom_price'] ) ) {
$args = array( 'price' => 40 );
if ( WC()->cart->display_prices_including_tax() ) {
$product_price = wc_get_price_including_tax( $cart_item['data'], $args );
} else {
$product_price = wc_get_price_excluding_tax( $cart_item['data'], $args );
}
return wc_price( $product_price );
}
return $price_html;
}
Code goes in functions.php file of your active child theme (or active theme).
This code is tested and works (still works on WooCommerce 5.1.x).
Note: you can increase the hook priority from 20 to 1000 (or even 2000) when using some few specific plugins or others customizations.
Related:
Set cart item price from a hidden input field custom price in Woocommerce 3
Change cart item prices based on custom cart item data in Woocommerce
Set a specific product price conditionally on Woocommerce single product page & cart
Add a select field that will change price in Woocommerce simple products
With WooCommerce version 3.2.6, #LoicTheAztec's answer works for me if I increase the priority to 1000.
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 1000, 1);
I tried priority values of 10,99 and 999 but the price and total in my cart did not change (even though I was able to confirm with get_price() that set_price() had actually set the price of the item.
I have a custom hook that adds a fee to my cart and I'm using a 3rd party plugin that adds product attributes. I suspect that these WooCommerce "add-ons" introduce delays that require me to delay my custom action.

Assign a tag programatically for stock status when product is saved in WooCommerce backend

I have created 3 tags 1.
Backorder (id 111)
In stock (id 112),
Out of stock (id 113)
I would like these tags to be auto-assigned based on availability.
If I am able to get this working, I'll be using the woocommerce_admin_process_product_object, woocommerce_product_quick_edit_save, woocommerce_product_set_stock, woocommerce_variation_set_stock to capture the possible scenarios for stock status changes.
Below is what I have tried but it gives me a fatal error. If I do not use the if condition, then the tags get assigned but it does not meet my objective. Any advice?
add_action( 'woocommerce_admin_process_product_object', 'mycode_woocommerce_backorder_tag', 10, 2 );
function mycode_woocommerce_backorder_tag ($wc_get_product, $product) {
if ($product->managing_stock() && $product->is_on_backorder(1)) {
$wc_get_product->set_tag_ids(array(111));
$wc_get_product->save();
//wp_set_object_terms ($post_id, 'onbackorder', 'product_tag');
}
}
woocommerce_admin_process_product_object hook has only 1 argument, which is $product
$product->save(); is also not necessary, because this happens automatically
Copied from admin/meta-boxes/class-wc-meta-box-product-data.php
/**
* Set props before save.
*
* #since 3.0.0
*/
do_action( 'woocommerce_admin_process_product_object', $product );
$product->save();
So to answer your question, you get:
// When product is saved in WooCommerce backend
function action_woocommerce_admin_process_product_object( $product ) {
// managing_stock() - returns whether or not the product is stock managed.
// on_backorder() – check if a product is on backorder.
if ( $product->managing_stock() && $product->is_on_backorder(1) ) {
// Product set tag ids
$product->set_tag_ids( array( 111 ) );
}
}
add_action( 'woocommerce_admin_process_product_object', 'action_woocommerce_admin_process_product_object', 10, 1 );

Create a Woocommerce product sold in units of gram

I would like to create a product in WooCommerce that is sold in units of gram.
The customer would enter the number of grams they want (in an input field) on the product page, and the price would be computed on the fly and added to the cart.
My question is: is this possible, and if so, can someone give me just a "big picture" idea of how I would implement it?
I don't need line-by-line code, just hoping someone with more knowledge of the structure of Woo can guide me on how to best attack the problem.
I already have parts of it worked out:
I can decide that the price entered for the product is the price per
100 grams, so that is how the seller will enter the price.
Then I can
write a little bit of Javascript to compute the price on the fly and
display it on the page as the user types the amount they want. No
problem.
But... I think every discrete product in Woo needs to have its own price.. So for example, if a customer wants 123g of a product, it seems like I might have to create a variation on the fly for that specific price/amount, and then add that to the cart. Which (judging by this) looks non-trivial and a little hacky. Is there a better way to do this?
WooCommerce has an option to show the weights as grams.
The following code will display the KG weights as grams on the WooCommerce templates :
// Convert the product weight
function ag_woocommerce_product_get_weight( $weight ) {
// Only convert if we have a weight
if ($weight) {
// The weight is in KGS, and we want grams, to multiple by 1000
$weight = $weight * 1000;
}
return $weight;
};
// add the filter
add_filter( 'woocommerce_product_get_weight', 'ag_woocommerce_product_get_weight', 10, 1 );
Hope this might help. Cheers!
There is a free plugin for WooCommerce that allows you to input a unit of measure (UOM) for each product:
https://wordpress.org/plugins/woocommerce-unit-of-measure/
I found this plugin that does pretty much exactly what I need-- https://woocommerce.com/products/measurement-price-calculator/
It's easier and quicker to give you that real example, than explain step by step… You will see which hooks are used for all steps or tasks.
You dont need variable products or generate a variation on the fly.
You just need to set on each simple product the price for one gram (or any other base). Now in this code, you can target those products with:
an array of product Ids
or by product categories (or even product tags).
Your concern is about the way to pass the data in the cart, to update the final price for each product and display the chosen grams amount in cart, checkout and in the order.
So in each product you will only set the price by gram… (or you can also make changes in the code and set the product price for 100 grs or even any other base).
The code:
// Add a product custom field "grams_quantity" that will update the displayed price
add_action('woocommerce_before_add_to_cart_button', 'special_product_by_grams', 25);
function special_product_by_grams(){
global $product;
// HERE Define the special product IDs sold by grams
$targeted_product_ids = array(37);
// or HERE Define a product categories (ids, slugs or names)
$categories = array('sold-by-gram');
// Only for products sold by gram
$product_id = $product->get_id();
if ( ! ( in_array( $product_id, $targeted_product_ids ) || has_term( $categories, 'product_cat', $product_id ) ) ) return;
?>
<div class="grams-field">
<label for="grams_quantity"><?php _e('Grams: ','woocoomerce'); ?><span></span><br>
<input type="number" step="1" name="grams_quantity" class="grams_quantity" id="grams_quantity" value="1">
</label>
</div><br>
<script type="text/javascript">
(function($){
// variables initialization
var priceByGram = <?php echo wc_get_price_to_display( $product ); ?>,
currencySymbol = $(".woocommerce-Price-currencySymbol").html(),
updatedPrice;
// On live event: imput number fields
$('input#grams_quantity').on( "click blur", function(){
updatedPrice = ($(this).val() * priceByGram).toFixed(2);
$(".woocommerce-Price-amount.amount").html('<span class="woocommerce-Price-amount amount">'+updatedPrice+' '+currencySymbol+'</span>');
console.log("event"); // <== To be removed
});
})(jQuery);
</script>
<?php
}
// Save the "grams_quantity" custom product field data in Cart item
add_filter( 'woocommerce_add_cart_item_data', 'save_in_cart_the_custom_product_field', 10, 2 );
function save_in_cart_the_custom_product_field( $cart_item_data, $product_id ) {
if( isset( $_POST['grams_quantity'] ) ) {
$cart_item_data[ 'grams_quantity' ] = $_POST['grams_quantity'];
// When add to cart action make an unique line item
$cart_item_data['unique_key'] = md5( microtime().rand() );
WC()->session->set( 'custom_data', $_POST['grams_quantity'] );
}
return $cart_item_data;
}
// Update product price by grams in cart and checkout
add_filter( 'woocommerce_before_calculate_totals', 'update_prices_by_gram', 10, 1 );
function update_prices_by_gram( $cart_object ) {
// HERE Define the special product IDs sold by grams
$targeted_product_ids = array(37);
// or HERE Define a product categories (ids, slugs or names)
$categories = array('sold-by-gram');
foreach ( $cart_object->get_cart() as $cart_item ) {
// Only for products sold by gram
$product_id = $cart_item['product_id'];
if ( in_array( $product_id, $targeted_product_ids ) || has_term( $categories, 'product_cat', $product_id ) ){
// Get an instance of the WC_Product object and the
$product = $cart_item['data'];
$grams = $cart_item['grams_quantity'];
// Method is_on_sale() manage everything (dates…)
$product->set_price( $product->get_price() * $grams);
}
}
}
// Render "grams_quantity" the custom product field in cart and checkout
add_filter( 'woocommerce_get_item_data', 'render_product_custom_field_meta_on_cart_and_checkout', 10, 2 );
function render_product_custom_field_meta_on_cart_and_checkout( $cart_data, $cart_item ) {
$custom_items = array();
if( !empty( $cart_data ) )
$custom_items = $cart_data;
if( isset( $cart_item['grams_quantity'] ) )
$custom_items[] = array(
'name' => __( 'Grams', 'woocommerce' ),
'value' => sanitize_text_field( $cart_item['grams_quantity'] ),
'display' => sanitize_text_field( $cart_item['grams_quantity'] ),
);
return $custom_items;
}
// Save "grams_quantity" to the order items meta data
add_action('woocommerce_add_order_item_meta','add_product_custom_fiel_to_order_item_meta', 1, 3 );
function add_product_custom_fiel_to_order_item_meta( $item_id, $item_values, $item_key ) {
if( isset( $item_values['grams_quantity'] ) )
wc_update_order_item_meta( $item_id, 'Grams', sanitize_text_field( $item_values['grams_quantity'] ) );
}
Code goes in function.php file of your active child theme (or active theme) or in any plugin file.
Tested and works.

woocommerce exclude state free shipping (australian states)

I'm trying to implement woocommerces exclude states from free shipping (Australian Site) snippet but am running into this error:
'Fatal error: Call to a member function get_shipping_state() on a non-object in
/home/bpcsport/public_html/wp-content/plugins/wa-exclude/wa-exclude.php
on line 30'
My client needs to exclude Western Australia from the free-shipping deal they offer through woocommerce. I get the error either way if I put it in my themes function.php or via plug-in format.
I've also tried the following method to no avail.
How can I disable free shipping for particular states?
This is the snippet from woocommerce that is in the plug-in
/**
* Hide ALL shipping options when free shipping is available and customer is NOT in certain states
* Hide Free Shipping if customer IS in those states
*
* Change $excluded_states = array( 'AK','HI','GU','PR' ); to include all the states that DO NOT have free shipping
*/
add_filter( 'woocommerce_available_shipping_methods', 'hide_all_shipping_when_free_is_available' , 10, 1 );
/**
* Hide ALL Shipping option when free shipping is available
*
* #param array $available_methods
*/
function hide_all_shipping_when_free_is_available( $available_methods ) {
$excluded_states = array( 'WA' );
if( isset( $available_methods['free_shipping'] ) AND !in_array( $woocommerce->customer->get_shipping_state(), $excluded_states ) ) :
// Get Free Shipping array into a new array
$freeshipping = array();
$freeshipping = $available_methods['free_shipping'];
// Empty the $available_methods array
unset( $available_methods );
// Add Free Shipping back into $avaialble_methods
$available_methods = array();
$available_methods[] = $freeshipping;
endif;
if( isset( $available_methods['free_shipping'] ) AND in_array( $woocommerce->customer->get_shipping_state(), $excluded_states ) ) {
// remove free shipping option
unset( $available_methods['free_shipping'] );
}
return $available_methods;
}
?>
I've made an plugin which allow advanced user configurations. Altough it includes only US states, you can configure country and postal code conditions.
I don't know if this meets you requirements, but might worth checking out: http://wordpress.org/plugins/woocommerce-advanced-free-shipping/

Resources