Inspired by this answer code, I've write the code below which works to add an error notice in WooCommerce checkout under a condition (they add a non-shippable product, (id'd by shipping class) to the cart and are outside of my local delivery zones (i.e. their postal code falls into the rest of the world zone) and only pickup is available (identified by only shipping method available).
add_action('woocommerce_after_checkout_validation', 'get_zone_info', 10 );
function get_zone_info( ) {
// Get cart shipping packages
$shipping_packages = WC()->cart->get_shipping_packages();
// Get the WC_Shipping_Zones instance object for the first package
$shipping_zone = wc_get_shipping_zone( reset( $shipping_packages ) );
$zone_id = $shipping_zone->get_id(); // Get the zone ID
$zone_name = $shipping_zone->get_zone_name(); // Get the zone name
$shipping_class_target = 87;
$in_cart = 0;
foreach ( WC()->cart->get_cart_contents() as $key => $values ) {
if ( $values[ 'data' ]->get_shipping_class_id() == $shipping_class_target ) {
$in_cart = 1;
break;
}
}
if ( $zone_name=='Locations not covered by your other zones' && $in_cart==1 || $zone_id==0 && $in_cart==1 ) {
//wc_print_notice( __( '<p>Count_ships: ' .$counter_ship. ' Cart id: ' . $in_cart . ' | Zone id: ' . $zone_id . ' | Zone name: ' . $zone_name . '</p>', 'woocommerce' ), 'success' );
wc_print_notice( __( '<b>ONLY PICKUP IS AVAILABLE</b><br>To have courier delivery, please goto your cart and remove products which cannot be shipped', 'woocommerce' ), 'success' );
} else {
//donothing
}
}
I have two Issues:
1- The notice check doesn't retrigger each time the address changes
2- I use the Multi-Step Checkout Pro for WooCommerce by Silkypress and want this notice to come up during the ORDER section...however the above notice doesn't work at all when I activate this plugin.
I've contacted them for support, but appreciate any help.
Alternatively, you can think differently, automatically disabling all shipping methods except local pickup (if there is at least one product in the cart with the shipping class id equal to the one specified).
Based on:
Hide shipping methods for specific shipping class in WooCommerce
Hide shipping methods for specific shipping classes in WooCommerce
Then you can use a custom function to check if there are products in the cart with a specific shipping class id:
// check if the products in the cart have a specific shipping class id
function check_product_in_cart_according_shipping_class_id() {
$shipping_class_target = 87;
$in_cart = false;
// check if in the cart there is at least one product with the shipping class id equal to "87"
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product = $cart_item['data'];
$shipping_class = $product->get_shipping_class_id();
if ( $shipping_class == $shipping_class_target ) {
$in_cart = true;
return $in_cart;
}
}
return $in_cart;
}
And with another custom function you get the list of products that cannot be shipped (and therefore those that have the shipping class id equal to 87):
// gets products that have a specific shipping class
function get_product_in_cart_according_shipping_class_id() {
$shipping_class_target = 87;
$product_list = '<ul>';
// check if in the cart there is at least one product with the shipping class id equal to "87"
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product = $cart_item['data'];
$shipping_class = $product->get_shipping_class_id();
if ( $shipping_class == $shipping_class_target ) {
$sku = $product->get_sku();
$name = $product->get_name();
$qty = $cart_item['quantity'];
$product_list .= '<li>' . $qty . ' x ' . $name . ' (' . $sku . ')</li>';
}
}
$product_list .= '</ul>';
return $product_list;
}
Then disable all shipping methods except local pickup:
// disable all shipping methods except local pickup based on product shipping class
add_filter( 'woocommerce_package_rates', 'disable_shipping_methods_based_on_product_shipping_class', 10, 2 );
function disable_shipping_methods_based_on_product_shipping_class( $rates, $package ) {
$in_cart = check_product_in_cart_according_shipping_class_id();
// if it is present, all shipping methods are disabled except local pickup
if ( $in_cart ) {
foreach( $rates as $rate_key => $rate ) {
if ( $rate->method_id != 'local_pickup' ) {
unset($rates[$rate_key]);
}
}
}
return $rates;
}
Finally, it adds the custom notice on the cart and checkout page.I created two functions because on the cart page the notice needs to update when a product is added or removed and using the woocommerce_before_calculate_totals hook would cause multiple notices in the checkout.
In the cart:
// add custom notice in cart
add_action( 'woocommerce_before_calculate_totals', 'add_custom_notice_in_cart' );
function add_custom_notice_in_cart() {
// only on the cart page
if ( ! is_cart() ) {
return;
}
$in_cart = check_product_in_cart_according_shipping_class_id();
if ( $in_cart ) {
$products = get_product_in_cart_according_shipping_class_id();
wc_clear_notices();
wc_add_notice( sprintf( __( 'Only <strong>Local Pickup</strong> shipping method is available. These products cannot be shipped:<br>%s', 'woocommerce' ), $products ), 'notice' );
}
}
In the checkout:
// add custom notice in checkout
add_action( 'woocommerce_checkout_before_customer_details', 'add_custom_notice_in_checkout' );
function add_custom_notice_in_checkout() {
$in_cart = check_product_in_cart_according_shipping_class_id();
if ( $in_cart ) {
$products = get_product_in_cart_according_shipping_class_id();
wc_clear_notices();
wc_add_notice( sprintf( __( 'Only <strong>Local Pickup</strong> shipping method is available. These products cannot be shipped:<br>%s', 'woocommerce' ), $products ), 'notice' );
}
}
The code has been tested and works. Add it to your active theme's functions.php.
Related
Hello I am trying to add an error notice when this function is met if a user adds more than 1 item from tv series catergory the shipping method 'flat_rate:3' is removed from the shipping options, it works but I can not get the error notice to display to the user when this happens can you help please
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package ) {
$categories = array('tv-series'); // Defined targeted product categories
$allowed_max_qty = 1; // Max allowed quantity for the shipping class
$shipping_rates_ids = array( // Shipping Method rates Ids To Hide
'flat_rate:3'
);
$related_total_qty = 0;
// Checking cart items for current package
foreach( $package['contents'] as $key => $cart_item ) {
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$related_total_qty += $cart_item['quantity'];
}
}
// When total allowed quantity is more than allowed (for items from defined shipping classes)
if ( $related_total_qty > $allowed_max_qty ) {
// Hide related defined shipping methods
foreach( $shipping_rates_ids as $shipping_rate_id ) {
if( isset($rates[$shipping_rate_id]) ) {
unset($rates[$shipping_rate_id]); // Remove Targeted Methods
}
}
}
return $rates;
{
if ( $allowed_max_qty > 1 ) {
wc_add_notice( '<strong>' . __( 'You are not allowed more than one TV Series on a Disc', 'woocommerce' ) . '</strong> ' . 'error' );
}
}
}
I use a code that adds a 20% discount when choosing "Local Pickup" in the cart and at checkout based on Do not give discounts for on sale products when selecting “local pickup” in WooCommerce
/**
* Discount for Local Pickup
*/
add_action( 'woocommerce_cart_calculate_fees', 'custom_discount_for_pickup_shipping_method', 10, 1 );
function custom_discount_for_pickup_shipping_method( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$percentage = 20; // Discount percentage
$chosen_shipping_method_id = WC()->session->get( 'chosen_shipping_methods' )[0];
$chosen_shipping_method = explode(':', $chosen_shipping_method_id)[0];
// Only for Local pickup chosen shipping method
if ( strpos( $chosen_shipping_method_id, 'local_pickup' ) !== false ) {
// Set variable
$new_subtotal = 0;
// Loop though each cart items and set prices in an array
foreach ( $cart->get_cart() as $cart_item ) {
// Get product
$product = wc_get_product( $cart_item['product_id'] );
// Product has no discount
if ( ! $product->is_on_sale() ) {
// line_subtotal
$line_subtotal = $cart_item['line_subtotal'];
// Add to new subtotal
$new_subtotal += $line_subtotal;
}
}
// Calculate the discount
$discount = $new_subtotal * $percentage / 100;
// Add the discount
$cart->add_fee( __('Discount') . ' (' . $percentage . '%)', -$discount );
}
}
Tell me how to make sure that the local-pickup discount is not taken if the product is from certain categories? I must specify the category name manually, for example, "steak", "sauce" and "bread".
And how to make a notice to customers "Pickup discount does not apply to products from the" Category Name "category"?
I will be glad for your help!
Try below code
/**
* Discount for Local Pickup
*/
add_action( 'woocommerce_cart_calculate_fees', 'custom_discount_for_pickup_shipping_method', 10, 1 );
function custom_discount_for_pickup_shipping_method( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$percentage = 20; // Discount percentage
$chosen_shipping_method_id = WC()->session->get( 'chosen_shipping_methods' )[0];
$chosen_shipping_method = explode(':', $chosen_shipping_method_id)[0];
// Only for Local pickup chosen shipping method
if ( strpos( $chosen_shipping_method_id, 'local_pickup' ) !== false) {
// Set variable
$new_subtotal = 0;
// Set discount excluded categories list
$arr_discount_excluded_category = ['Category 1', 'Test Category'];
// Set variable for matched excluded category from the cart list
$arr_discount_excluded_category_matched = [];
// Loop though each cart items and set prices in an array
foreach ( $cart->get_cart() as $cart_item ) {
// Get product
$product = wc_get_product( $cart_item['product_id'] );
// Get product category
$category_list = wp_get_post_terms($cart_item['product_id'],'product_cat',array('fields'=>'names'));
// Product has no discount
$arr_discount_excluded_category_matched_by_item = array_intersect($category_list, $arr_discount_excluded_category);
if ( ! $product->is_on_sale() && empty($arr_discount_excluded_category_matched_by_item)) {
// line_subtotal
$line_subtotal = $cart_item['line_subtotal'];
// Add to new subtotal
$new_subtotal += $line_subtotal;
}
else{
$arr_discount_excluded_category_matched = array_merge($arr_discount_excluded_category_matched, $arr_discount_excluded_category_matched_by_item);
}
}
// Calculate the discount
$discount = 0;
if($new_subtotal > 0){
$discount = $new_subtotal * $percentage / 100;
}
//Add notification
if(!empty($arr_discount_excluded_category_matched)){
$str_message = 'Pickup discount does not apply to products from the Category "' . implode('", "', array_unique($arr_discount_excluded_category_matched)) . '"';
wc_add_notice($str_message, 'notice');
}
// Add the discount
$cart->add_fee( __('Discount') . ' (' . $percentage . '%)', -$discount );
}
}
Based my update discount will not apply Category 1, Test Category when shipping method is local_pickup and if any of those category products found from the cart list, it will show notification to customer on the success page.
Thank you.
As i can see your code, you have already used below function to get all product added in cart
/* Get product */
$product = wc_get_product( $cart_item['product_id'] );
now you just need to check that your product belongs to any of that category you want to check
i think this way you can figure out
I currently have this code in functions.php in order to display a message on the checkout page saying the customer has backordered products in their cart:
add_action( 'woocommerce_review_order_before_payment', 'es_checkout_add_cart_notice' );
function es_checkout_add_cart_notice() {
$message = "You have a backorder product in your cart.";
if ( es_check_cart_has_backorder_product() )
wc_add_notice( $message, 'error' );
}
function es_check_cart_has_backorder_product() {
foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
$cart_product = wc_get_product( $values['data']->get_id() );
if( $cart_product->is_on_backorder() )
return true;
}
return false;
}
What do I need to do to also have this same message display on the CART page? (Before going to Checkout).
Thanks in advance.
Note 1: I changed the code so that it works with 1 hook on both pages as opposed to using 2 different hooks
Note 2: Notice the use of the notice_type
wc_add_notice( __( $message, 'woocommerce' ), 'notice' );
opposite 'error'.
Ultimately, this is not an error
Note 3: optionally you can remove the 'proceed_to_checkout' button (code line is in comment, this doesn't seem to apply here) (see note 2)
So you get:
function es_add_cart_notice() {
// Only on cart and check out pages
if( ! ( is_cart() || is_checkout() ) ) return;
// Set message
$message = "You have a backorder product in your cart.";
// Set variable
$found = false;
// Loop through all products in the Cart
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Product on backorder
if( $product->is_on_backorder() ) {
$found = true;
break;
}
}
// true
if ( $found ) {
wc_add_notice( __( $message, 'woocommerce' ), 'notice' );
// Removing the proceed button, until the condition is met
// optional
// remove_action( 'woocommerce_proceed_to_checkout','woocommerce_button_proceed_to_checkout', 20);
}
}
add_action( 'woocommerce_check_cart_items', 'es_add_cart_notice', 10, 0 );
In Woocommerce, I am trying to auto remove a gift product from cart if cart item is less than 300,000 dongs.
Here is my code:
add_action( 'template_redirect', 'remove_product_from_cart' );
function remove_product_from_cart() {
// Run only in the Cart or Checkout Page
global $woocommerce;
$current = WC()->cart->cart_contents_total;
$min_amount= 300000;
$prod_to_remove = 357;
if ( $current < $min_amount) {
if( is_cart() || is_checkout() ) {
// Cycle through each product in the cart
foreach( WC()->cart->cart_contents as $prod_in_cart ) {
// Get the Variation or Product ID
$prod_id = ( isset( $prod_in_cart['variation_id'] ) && $prod_in_cart['variation_id'] != 0 ) ? $prod_in_cart['variation_id'] : $prod_in_cart['product_id'];
// Check to see if IDs match
if( $prod_to_remove == $prod_id ) {
// Get it's unique ID within the Cart
$prod_unique_id = WC()->cart->generate_cart_id( $prod_id );
// Remove it from the cart by un-setting it
unset( WC()->cart->cart_contents[$prod_unique_id] );
wc_add_notice( __( 'Quà tặng của bạn đã bị xóa khỏi giỏ hàng vì ' ), 'notice' );
}
}
}
}}
But the problem is that it does not work with product variations.
Any help is appreciated.
Uptaded (to handle multiple items to be removed).
Try the following revisited code, that will work with product variations too in a better way:
add_action( 'woocommerce_check_cart_items', 'conditionally_remove_specific_cart_item' );
function conditionally_remove_specific_cart_item() {
## -- Settings -- ##
$min_required = 300000; // The minimal required cart subtotal
$items_to_remove = array( 446, 27 ); // Product Ids to be removed from cart
// Only if cart subtotal is less than 300000
if( WC()->cart->cart_contents_total >= $min_required )
return;
// Loop through cart items
foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if( array_intersect( $items_to_remove, array( $cart_item['variation_id'], $cart_item['product_id'] ) ) ) {
// Remove cart item
WC()->cart->remove_cart_item( $cart_item_key );
// Add a custom notice
wc_add_notice( sprintf(
__( "Your gift has been removed from the cart because its requires a minimal cart amount of %s", "woocommerce" ),
$min_required ), 'notice' );
}
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
In cart page:
In checkout page:
This is an extension of this question: Remove shipping Flat Rate method for particular Category in WooCommerce 2.6 and 3+
Used the Woo Smart Coupons plugin for a Gift Card product. This HAS to be set to Variation, as we have multiple tiers to select from. (this rules out virtual products) The Gift Card has it's own category for distinction. We have two shipping options set up: Flat Rate + Local Pickup. It's pretty silly to have shipping options for a gift card that gets sent to your Inbox, so I used the following snippet found in the link above:
add_filter( 'woocommerce_package_rates', 'conditional_hide_shipping_methods', 100, 2 );
function conditional_hide_shipping_methods( $rates, $package ){
// Define/replace here your correct category slug (!)
$product_category = 'coupons-gift-cards';
$prod_cat = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id'];
if ( has_term( $product_category, 'product_cat', $product_id ) ){
$prod_cat = true;
}
}
$rates_arr = array();
if ( $prod_cat ) {
foreach($rates as $key => $rate) {
if ('free_shipping' === $rate->method_id || 'local_pickup' === $rate->method_id || 'local_delivery' === $rate->method_id) {
$rates_arr[ $rate_id ] = $rate;
break;
}
}
}
return !empty( $rates_arr ) ? $rates_arr : $rates;
}
Works like a charm... until you add a product that ISN'T from that category. If someone decides that they want a Gift Card AND a normal product, then the regular shipping options need to be back in place.
EDIT: The checked answer works perfectly! If you want to change the Pickup Label for items like the above situation so they say something like "Download" instead of "Pickup", then add this line after the IF statement that checks which products are matching the categories
foreach( $rates as $rate_key => $rate ) {
//change local_pickup:1 to your shipping method
if ( 'local_pickup:1' == $rate_key){
//set the text for the label
$rates[$rate_key]->label = __( 'Download', 'woocommerce' );
}
}
Here is the correct way to make it work for a unique and exclusive product category, that will hide Flat rate exclusively if there is no others products remaining to this product category:
add_filter( 'woocommerce_package_rates', 'hide_shipping_flat_rate_conditionaly', 90, 2 );
function hide_shipping_flat_rate_conditionaly( $rates, $package ){
// HERE Define your product category (ID, slug or name or an array of values)
$term = 'coupons-gift-cards';
$others = $found = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id'];
if ( has_term( $term, 'product_cat', $cart_item['product_id'] ) )
$found = true;
else
$others = true;
}
if ( $found && ! $others ) {
foreach($rates as $rate_id => $rate) {
if ('flat_rate' === $rate->method_id )
unset($rates[ $rate_id ]);
}
}
return $rates;
}
Code goes on function.php file of your active child theme (or active theme).
This code is tested and fully functional (it will work if you have correctly set your shipping zones).
You might need to refresh shipping cached data: disable, save and enable, save related shipping methods for the current shipping zone, in Woocommerce shipping settings.