I have some code in a function that displays a message to the end user when the total added value of a custom field exceeds a certain value. Basically its a message telling the customer that they have ordered too much furniture to fit into a 68m3 container.
I also have code which is suppose to disable the add to cart button on products - this doesn't seem to work.
So is there a visible issue in the code below to disable the add to cart button when my custom field total value exceeds 68 and is there a way to totally disable the submit order so it's not possible to submit the order until the value of the custom field total is below 68?
The code to display the message is as follows and works fine.
// display message when cart volume exceeds 68mcube
add_action('woocommerce_before_calculate_totals', 'display_custom_notice',
50, 1);
function display_custom_notice( $cart ) {
if ( is_admin() && !defined('DOING_AJAX') )
return;
$total_volume = 0;
// Loop through cart items and calculate total volume
foreach( WC()->cart->get_cart() as $cart_item ){
$product_volume = (float) get_post_meta( $cart_item['product_id'],
'_item_volume', true );
$total_volume += $product_volume * $cart_item['quantity'];
}
if( $total_volume > 68 && $total_volume != 0 ){
// Display a custom notice
wc_add_notice( __("Note: Your order total volume has reached a full 40ft
container - 68 m3 - Please submit order with volume no greater than 68m3",
"woocommerce"), 'notice' );
}
}
The function below are not appearing to work
// function to disable add to cart when volume exceeds 68m3
function get_total_volume(){
$total_volume = 0;
// Loop through cart items and calculate total volume
foreach( WC()->cart->get_cart() as $cart_item ){
$product_volume = (float) get_post_meta( $cart_item['product_id'],
'_item_volume', true );
$total_volume += $product_volume * $cart_item['quantity'];
}
return $total_volume;
}
// Replacing the button add to cart by a link to the product in Shop and
archives pages
add_filter( 'woocommerce_loop_add_to_cart_link',
'replace_loop_add_to_cart_button', 10, 2 );
function replace_loop_add_to_cart_button( $button, $product ) {
if( get_total_volume() > 68 ){
$button_text = __( "View product", "woocommerce" );
$button = '<a class="button" href="' . $product->get_permalink() . '">'
. $button_text . '</a>';
}
return $button;
}
add_action( 'woocommerce_single_product_summary',
'remove_add_to_cart_button', 1 );
function remove_add_to_cart_button() {
// Only when total volume is up to 68
if( get_total_volume() <= 68 ) return;
global $product;
// For variable product types (keeping attribute select fields)
if( $product->is_type( 'variable' ) ) {
remove_action( 'woocommerce_single_variation',
'woocommerce_single_variation_add_to_cart_button', 20 );
add_action( 'woocommerce_single_product_summary',
'innactive_add_to_cart_button', 20 );
}
// For all other product types
else {
remove_action( 'woocommerce_single_product_summary',
'woocommerce_template_single_add_to_cart', 30 );
add_action( 'woocommerce_single_product_summary',
'innactive_add_to_cart_button', 30 );
}
}
To replace "Place Order" button in checkout page when total cart volume exceed 68 m3 we will need to use your utility function get_total_volume() in the following code:
// Utility function to disable add to cart when volume exceeds 68m3
function get_total_volume(){
$total_volume = 0;
// Loop through cart items and calculate total volume
foreach( WC()->cart->get_cart() as $cart_item ){
$product_volume = (float) get_post_meta( $cart_item['product_id'], '_item_volume', true );
$total_volume += $product_volume * $cart_item['quantity'];
}
return $total_volume;
}
// Replacing the Place order button when total volume exceed 68 m3
add_filter( 'woocommerce_order_button_html', 'replace_order_button_html', 10, 2 );
function replace_order_button_html( $order_button ) {
// Only when total volume is up to 68
if( get_total_volume() <= 68 ) return $order_button;
$order_button_text = __( "Max volume reached", "woocommerce" );
$style = ' style="color:#fff;cursor:not-allowed;background-color:#999;"';
return '<a class="button alt"'.$style.' name="woocommerce_checkout_place_order" id="place_order" >' . esc_html( $order_button_text ) . '</a>';
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
When total volume exceed 68 m3, you will get an inactive custom button instead:
Check that the utility function get_total_volume() is defined only once, to avoid errors…
Now, to disable add to cart buttons, there is some missing things in your code like the function inactive_add_to_cart_button() and other little things… Try this:
// display message when cart volume exceeds 68mcube
add_action('woocommerce_before_calculate_totals', 'display_custom_notice', 50, 1);
function display_custom_notice( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
$total_volume = 0;
// Loop through cart items and calculate total volume
foreach( WC()->cart->get_cart() as $cart_item ){
$product_volume = (float) get_post_meta( $cart_item['product_id'], '_item_volume', true );
$total_volume += $product_volume * $cart_item['quantity'];
}
if( $total_volume > 68 && $total_volume != 0 ){
// Display a custom notice
wc_clear_notices();
wc_add_notice( __("Note: Your order total volume has reached a full 40ft container - 68 m3 - Please submit order with volume no greater than 68m3", "woocommerce"), 'notice' );
}
}
// Utility function to disable add to cart when volume exceeds 68m3
function get_total_volume(){
$total_volume = 0;
// Loop through cart items and calculate total volume
foreach( WC()->cart->get_cart() as $cart_item ){
$product_volume = (float) get_post_meta( $cart_item['product_id'], '_item_volume', true );
$total_volume += $product_volume * $cart_item['quantity'];
}
return $total_volume;
}
// Replacing the button add to cart by a link to the product in Shop and archives pages
add_filter( 'woocommerce_loop_add_to_cart_link', 'replace_loop_add_to_cart_button', 10, 2 );
function replace_loop_add_to_cart_button( $button, $product ) {
// Only when total volume is up to 68
if( get_total_volume() <= 68 ) return $button;
$small_text = '<br><em style="font-size:85%;">(' . __( "Max volume reached", "woocommerce" ) . ')</em>';
$button_text = __( "View product", "woocommerce" ) . $small_text;
return '<a class="button" href="' . $product->get_permalink() . '">' . $button_text . '</a>';
}
// Replacing the button add to cart by an inactive button on single product pages
add_action( 'woocommerce_single_product_summary', 'remove_add_to_cart_button', 1 );
function remove_add_to_cart_button() {
// Only when total volume is up to 68
if( get_total_volume() <= 68 ) return;
global $product;
// For variable product types (keeping attribute select fields)
if( $product->is_type( 'variable' ) ) {
remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
add_action( 'woocommerce_single_product_summary', 'inactive_add_to_cart_button', 20 );
}
// For all other product types
else {
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30 );
add_action( 'woocommerce_single_product_summary', 'inactive_add_to_cart_button', 30 );
}
}
// Utility function: displays a custom innactive add to cart button replacement
function inactive_add_to_cart_button(){
global $product;
$style = 'style="color:#fff;cursor:not-allowed;background-color:#999;"';
echo '<a class="button" '.$style.'>' . __ ( 'Max volume reached', 'woocommerce' ) . '</a>';
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Check that the utility function get_total_volume() is defined only once, to avoid errors…
When the total cart volume is up to 68m3 you will get:
1) On shop and archives pages:
2) On single product pages:
Related
In Woocommerce, I used jQuery to calculate a custom price on a single product pages, and now need to pass this value to the cart.
The desired behavior is to pass the new price retrieved from the hidden field to the cart item price.
Here is my actual code:
// Hidden input field in single product page
add_action( 'woocommerce_before_add_to_cart_button', 'custom_hidden_product_field', 11, 0 );
function custom_hidden_product_field() {
echo '<input type="hidden" id="hidden_field" name="custom_price" class="custom_price" value="">';
}
// The code to pass this data to the cart:
add_action( 'woocommerce_add_cart_item_data', 'save_custom_fields_data_to_cart', 10, 2 );
function save_custom_fields_data_to_cart( $cart_item_data, $product_id ) {
if( ! empty( $_REQUEST['custom_price'] ) ) {
// Set the custom data in the cart item
$cart_item_data['custom_data']['custom_price'] = $_REQUEST['custom_price'];
$data = array( 'custom_price' => $_REQUEST['custom_price'] );
// below statement make sure every add to cart action as unique line item
$cart_item_data['custom_data']['unique_key'] = md5( microtime().rand() );
WC()->session->set( 'custom_data', $data );
}
return $cart_item_data;
}
And check both $data and $cart_item_data to see that they both return the custom_price data that is calculated on the page.
However, I go to view cart, and the value of the line item is still 0.
I set a var equal to the WC()->session->set( 'custom_data', $data ); and then var_dump to check it, but this returns NULL which might just be what it returns, I'm not entirely sure because I've never used it.
I should also add that I have the regular_price in the product backend set to 0. When I erase this (and leave it blank) I get back the error:
Warning: A non-numeric value encountered in
C:\xampp\htdocs\my-transfer-source\wp-content\plugins\woocommerce\includes\class-wc-discounts.php on line 85
I'm wondering if I've missed something here, and if someone could lend some light onto this? Thanks
Update 2021 - Handling custom price item in mini cart
First for testing purpose we add a price in the hidden input field as you don't give the code that calculate the price:
// Add a hidden input field (With a value of 20 for testing purpose)
add_action( 'woocommerce_before_add_to_cart_button', 'custom_hidden_product_field', 11 );
function custom_hidden_product_field() {
echo '<input type="hidden" id="hidden_field" name="custom_price" class="custom_price" value="20">'; // Price is 20 for testing
}
Then you will use the following to change the cart item price (WC_Session is not needed):
// Save custom calculated price as custom cart item data
add_filter( 'woocommerce_add_cart_item_data', 'save_custom_fields_data_to_cart', 10, 2 );
function save_custom_fields_data_to_cart( $cart_item_data, $product_id ) {
if( isset( $_POST['custom_price'] ) && ! empty( $_POST['custom_price'] ) ) {
// Set the custom data in the cart item
$cart_item_data['custom_price'] = (float) sanitize_text_field( $_POST['custom_price'] );
// Make each item as a unique separated cart item
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
// For mini cart
add_action( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 2 );
function filter_cart_item_price( $price, $cart_item ) {
if ( isset($cart_item['custom_price']) ) {
$args = array( 'price' => floatval( $cart_item['custom_price'] ) );
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;
}
// Updating cart item price
add_action( 'woocommerce_before_calculate_totals', 'change_cart_item_price', 30, 1 );
function change_cart_item_price( $cart ) {
if ( ( is_admin() && ! defined( 'DOING_AJAX' ) ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
// Set the new price
if( isset($cart_item['custom_price']) ){
$cart_item['data']->set_price($cart_item['custom_price']);
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
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'm building a website where i'll sell some fine art prints and i would like to set an automated discount when a customer buy the first print of a serie. Each of my prints are limited to 15 pieces and i would like to set a 30% discount for the first sell (#1/15) of each series. Then, for the next prints (#2 to #15), price goes back to normal.
Edit :
I did progress on my problem ! I did some research and tried a lot of different things and i found how to set the custom price i wanted on my product page (regular price + discount price) with the rule to only apply it if the related item available stock is 15.
regular price + discount price
And here's the code is used :
// Generating the product "regular price"
add_filter( 'woocommerce_product_get_regular_price', 'dynamic_regular_price', 10, 2 );
add_filter( 'woocommerce_product_variation_get_regular_price', 'dynamic_regular_price', 10, 2 );
function dynamic_regular_price( $regular_price, $product ) {
$stock_qte = $product->get_stock_quantity();
if( empty($regular_price) || $regular_price == 0 )
return $product->get_price();
else
return $regular_price;
}
// Generating the product "sale price"
add_filter( 'woocommerce_product_get_sale_price', 'dynamic_sale_price', 10, 2 );
add_filter( 'woocommerce_product_variation_get_sale_price', 'dynamic_sale_price', 10, 2 );
function dynamic_sale_price( $sale_price, $product ) {
$stock_qte = $product->get_stock_quantity();
if( $stock_qte == '15')
return $product-> get_regular_price() * 0.7;
else
//return $product->get_regular_price();
return $regular_price;
};
// Displayed formatted regular price + sale price
add_filter( 'woocommerce_get_price_html', 'dynamic_sale_price_html', 20, 2 );
function dynamic_sale_price_html( $price_html, $product ) {
$stock_qte = $product->get_stock_quantity();
if( $stock_qte == '15')
$price_html = wc_format_sale_price( wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) ), wc_get_price_to_display( $product, array( 'price' => $product->get_sale_price() ) ) ) . $product->get_price_suffix();
else
$price_html = wc_price(wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) ). $product->get_price_suffix());
return $price_html;
}
It's almost working but the code related to the cart values updates is not fully working. The cart sub-total is good and take count of the discount but the individual price of each product is not updated and shown as without discount. It's the same on the cart page.
cart (from mini cart)
cart (cart page)
add_action( 'woocommerce_before_calculate_totals', 'alter_price_cart', 9999 );
function alter_price_cart( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) return;
// IF CUSTOMER NOT LOGGED IN, DONT APPLY DISCOUNT
//if ( ! wc_current_user_has_role( 'customer' ) ) return;
// LOOP THROUGH CART ITEMS & APPLY 30% DISCOUNT
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$price = $product->get_price();
$cart_item['data']->set_price( $price * 0.7 );
}
}
I'll also have to add the stock availability logic for the mini-cart / cart / checkout page but i'll do that in a second time.
I must have made some mistakes in the loop throught the cart but i cannot catch the issue :( Any idea about how to hook the price of each product and change the displayed value ?
Have a nice day,
Quentin
I have three versions of this code in my site to product 3 buttons ( 1x quantity, 3x quantity and 10x quantity). My products are variable. The code didn't work until I removed this line if( ! $product->is_type('simple') ) return; . With this line removed, should it be replaced with something else for variable products to ensure the code is complete properly?
add_action( 'woocommerce_after_add_to_cart_button', 'additional_simple_add_to_cart_10', 20 );
function additional_simple_add_to_cart_10() {
global $product;
// Only for simple product type
if( ! $product->is_type('simple') ) return; // Removed this line
$href = '?add-to-cart=' . esc_attr( $product->get_id() ) . '&quantity=10';
$class = 'ingle_add_to_cart_button-10 button alt';
$style = 'display: inline-block; margin-top: 12px;';
$button_text = __( "10 Tickets", "woocommerce" );
// Output
echo '<br><a rel="no-follow" href="'.$href.'" class="'.$class.'" style="'.$style.'">'.$button_text.'</a>';
}
// Redirect to checkout after add to basket
add_filter( 'woocommerce_add_to_cart_redirect', 'misha_skip_cart_redirect_checkout' );
function misha_skip_cart_redirect_checkout( $url ) {
return wc_get_checkout_url();
}
Trying to add the order subtotal in woocommerce my-account/orders table but I can't seem to get it to display. Currently it adds the column and the label but its not displaying the orders sub total. I am currently using the code below :
add_filter( 'woocommerce_account_orders_columns',
'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-total', 'woocommerce' );
return $columns;
}
add_action( 'woocommerce_my_account_my_orders_column_custom-column',
'add_account_orders_column_rows' );
function add_account_orders_column_rows( $order ) {
// Example with a custom field
if ( $value = $order->get_meta( 'item_subtotal_tax_excl' ) ) {
echo esc_html( $value );
}
}
Subtotal like in cart doesn't exist in WooCommerce Orders meta data, so you need to get it and calculate it from order items:
add_filter( 'woocommerce_account_orders_columns', 'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-total', 'woocommerce' );
return $columns;
}
add_action( 'woocommerce_my_account_my_orders_column_custom-column', 'display_account_orders_column_rows_value' );
function display_account_orders_column_rows_value( $order ) {
$subtotal = 0; // Initializing
// Loop through order items (line items)
foreach ( $order->get_items() as $item ) {
// Sum item subtotal excluding taxes and not discounted
$subtotal += $item->get_subtotal();
}
echo $subtotal;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related:
Get Order items and WC_Order_Item_Product in WooCommerce 3
How to get WooCommerce order details
Get the metadata of an order item in woocommerce 3
Edit: the following code worked for me: (answer provided by helgatheviking on woocommerce slack)
//show sub total in order page
add_filter( 'woocommerce_account_orders_columns',
'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-Total',
'woocommerce' );
return $columns;
}
add_action(
'woocommerce_my_account_my_orders_column_item_subtotal_tax_excl',
'add_account_orders_column_rows' );
function add_account_orders_column_rows( $order ) {
// Example with a custom field
if ( $value = $order->get_subtotal() ) {
echo esc_html( $value );
}
}