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' );
}
}
}
Related
I currently charge a flat fee for shipping using the default WooCommerce shipping setup. I want to offer free shipping for the entire order if the user purchases "x" products from a single category. I have some code that I put together but I need a bit of help to get it working.
// Free shipping if you purchase 12 or more from selected category
function wcs_my_free_shipping( $is_available ) {
global $woocommerce;
// HERE set your product categories in the array (can be IDs, slugs or names)
$categories = array('t-shirts');
// Initializing
$found = false;
$count = 0;
// 1st Loop: get category items count
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// get cart contents
$cart_items = $woocommerce->cart->get_cart();
// loop through the items looking for one in the eligible array
foreach ( $cart_items as $key => $item ) {
if( in_array( $item['product_id'], $eligible ) ) {
return true;
}
}
if ( $count > 11 ) {
// Apply free shipping
$shipping = 0;
}
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'wcs_my_free_shipping', 20 );
The use of 2 loops is not necessary
Take into account the product quantity per product
comment with explanation added in the code
function filter_woocommerce_shipping_free_shipping_is_available( $is_available, $package, $shipping_method ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 12;
/* END settings */
// Counter
$count = 0;
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// Condition
if ( $count >= $minimum ) {
$notice = __( 'free shipping', 'woocommerce' );
$is_available = true;
} else {
$notice = __( 'NO free shipping', 'woocommerce' );
$is_available = false;
}
// Display notice
if ( isset( $notice ) ) {
wc_add_notice( $notice, 'notice' );
}
// Return
return $is_available;
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'filter_woocommerce_shipping_free_shipping_is_available', 10, 3 );
Additional question: hide other shipping methods when free shipping is available
function filter_woocommerce_package_rates( $rates, $package ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 11;
/* END settings */
// Counter
$count = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Check for category
if ( has_term( $categories, 'product_cat', $product_id ) ) {
$count += $line_item['quantity'];
}
}
// Condition
if ( $count > $minimum ) {
// Set
$free = array();
// Loop
foreach ( $rates as $rate_id => $rate ) {
// Rate method id = free shipping
if ( $rate->method_id === 'free_shipping' ) {
$free[ $rate_id ] = $rate;
break;
}
}
}
return ! empty( $free ) ? $free : $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Based on:
How to disable shipping method based on item count and a certain category
WooCommerce - Hide other shipping methods when FREE SHIPPING is available
I have some product in WooCommerce and two categories are:
Name: ORG Test
Slug: org-test
Name: ORG Prod
Slug: org-prod
I want to calculate shipping fee per quantity ($15 per quantity) if the product matches org-prod category:
My code attempt:
add_action('woocommerce_cart_calculate_fees', 'add_fees_on_ids');
function add_fees_on_ids() {
$total_act_fee = 0;
$business_plan_exist = false;
if (is_admin() && !defined('DOING_AJAX')) {return;}
foreach( WC()->cart->get_cart() as $cart_item ) {
$product = $cart_item['data'];
$quantity = $cart_item['quantity'];
$categories = wc_get_product_category_list( $product->get_id() );
if (strstr($categories, 'org-prod')) {
$business_plan_exist = true;
$total_act_fee = $total_act_fee + 15;
}
if ($business_plan_exist) {
WC()->cart->add_fee(__('Shipping Fees '), $total_act_fee);
}
}
}
But this does not give the desired result. The fee is applied but the total is wrong? Can you assist in figuring out why not?
Your code contains some mistakes and/or could be optimized:
When calculating the totals, you do not take into account the quantity
Your if condition for adding the fee, is in your foreach loop, so it is overwritten every loop
The use of WC()->cart is not necessary, as $cart is already passed to the callback function
Use has_term() versus wc_get_product_category_list() and strstr()
So you get:
function action_woocommerce_cart_calculate_fees( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Specific categories: the term name/term_id/slug. Several could be added, separated by a comma
$categories = array( 'org-prod', 'categorie-1', 83 );
// Initialize
$total_act_fee = 0;
$amount = 15;
// Gets cart contents
foreach ( $cart->get_cart_contents() as $cart_item ) {
// Has certain category
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
// Get quantity
$quantity = $cart_item['quantity'];
// Addition to the total
$total_act_fee += $amount * $quantity;
}
}
// Greater than
if ( $total_act_fee > 0 ) {
// Add fee
$cart->add_fee( __( 'Shipping Fees', 'woocommerce' ), $total_act_fee, true );
}
}
add_action( 'woocommerce_cart_calculate_fees', 'action_woocommerce_cart_calculate_fees', 10, 1 );
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.
I currently charge a flat fee for shipping using the default WooCommerce shipping setup. I want to offer free shipping for the entire order if the user purchases "x" products from a single category. I have some code that I put together but I need a bit of help to get it working.
// Free shipping if you purchase 12 or more from selected category
function wcs_my_free_shipping( $is_available ) {
global $woocommerce;
// HERE set your product categories in the array (can be IDs, slugs or names)
$categories = array('t-shirts');
// Initializing
$found = false;
$count = 0;
// 1st Loop: get category items count
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// get cart contents
$cart_items = $woocommerce->cart->get_cart();
// loop through the items looking for one in the eligible array
foreach ( $cart_items as $key => $item ) {
if( in_array( $item['product_id'], $eligible ) ) {
return true;
}
}
if ( $count > 11 ) {
// Apply free shipping
$shipping = 0;
}
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'wcs_my_free_shipping', 20 );
The use of 2 loops is not necessary
Take into account the product quantity per product
comment with explanation added in the code
function filter_woocommerce_shipping_free_shipping_is_available( $is_available, $package, $shipping_method ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 12;
/* END settings */
// Counter
$count = 0;
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// Condition
if ( $count >= $minimum ) {
$notice = __( 'free shipping', 'woocommerce' );
$is_available = true;
} else {
$notice = __( 'NO free shipping', 'woocommerce' );
$is_available = false;
}
// Display notice
if ( isset( $notice ) ) {
wc_add_notice( $notice, 'notice' );
}
// Return
return $is_available;
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'filter_woocommerce_shipping_free_shipping_is_available', 10, 3 );
Additional question: hide other shipping methods when free shipping is available
function filter_woocommerce_package_rates( $rates, $package ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 11;
/* END settings */
// Counter
$count = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Check for category
if ( has_term( $categories, 'product_cat', $product_id ) ) {
$count += $line_item['quantity'];
}
}
// Condition
if ( $count > $minimum ) {
// Set
$free = array();
// Loop
foreach ( $rates as $rate_id => $rate ) {
// Rate method id = free shipping
if ( $rate->method_id === 'free_shipping' ) {
$free[ $rate_id ] = $rate;
break;
}
}
}
return ! empty( $free ) ? $free : $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Based on:
How to disable shipping method based on item count and a certain category
WooCommerce - Hide other shipping methods when FREE SHIPPING is available
I want to add a fee in my woocommerce store for orders under a specific amount (value, not quantity).
I am using "WooCommerce dynamic minimum order amount based fee" answer code that works perfectly.
Now I am trying to change this functionand to apply it only to items from a specific product category. Basically, I have to sum the value of all the products in cart that matches the specific category and use this value for fee calculation.
How can I reach this goal?
The following revisited code will handle specific defined product categories. You will have to define:
Your specific product categories in the 1st function.
The minimal order amount in the 2nd and 3rd functions.
The code:
// Get the cart items subtotal amount from specific product categories
function get_specific_cart_items_total( $cart ) {
$categories = array('t-shirts'); // <=== Define your product categories
$items_amount = 0; // Initializing
// Loop through cart items
foreach( $cart->get_cart() as $item ) {
if( has_term( $categories, 'product_cat', $item['product_id'] ) ) {
$items_amount += $item['line_subtotal'];
}
}
return $items_amount;
}
// Add a custom fee (displaying a notice in cart page)
add_action( 'woocommerce_cart_calculate_fees', 'add_custom_surcharge', 10, 1 );
function add_custom_surcharge( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_cart_calculate_fees' ) >= 2 )
return;
$min_price = 200; // The minimal cart amount
$current_price = get_specific_cart_items_total( $cart );
$custom_fee = $min_price - $current_price;
if ( $custom_fee > 0 ) {
$cart->add_fee( __('Minimum Order Adjustment'), $custom_fee, true );
// NOTICE ONLY IN CART PAGE
if( is_cart() ){
wc_print_notice( get_custom_fee_message( $min_price, $current_price ), 'error' );
}
}
}
// Displaying the notice on checkout page
add_action( 'woocommerce_before_checkout_form', 'custom_surcharge_message', 10 );
function custom_surcharge_message( ) {
$min_price = 200; // The minimal cart amount
$cart = WC()->cart;
$current_price = get_specific_cart_items_total( $cart );
$custom_fee = $min_price - $current_price;
// NOTICE ONLY IN CHECKOUT PAGE
if ( $custom_fee > 0 ) {
wc_print_notice( get_custom_fee_message( $min_price, $current_price ), 'error' );
}
}
// The fee notice message
function get_custom_fee_message( $min_price, $current_price ) {
return sprintf(
__('We have a minimum %s per order from specific categories. As your current order is only %s, an additional fee will be applied.', 'woocommerce'),
wc_price( $min_price ),
wc_price( $current_price )
);
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Option: To handle parent product categories too, add the following additional function:
// Custom conditional function that handle parent product categories too
function has_product_categories( $categories, $product_id = 0 ) {
$parent_term_ids = $categories_ids = array(); // Initializing
$taxonomy = 'product_cat';
$product_id = $product_id == 0 ? get_the_id() : $product_id;
if( is_string( $categories ) ) {
$categories = (array) $categories; // Convert string to array
}
// Convert categories term names and slugs to categories term ids
foreach ( $categories as $category ){
$result = (array) term_exists( $category, $taxonomy );
if ( ! empty( $result ) ) {
$categories_ids[] = reset($result);
}
}
// Loop through the current product category terms to get only parent main category term
foreach( get_the_terms( $product_id, $taxonomy ) as $term ){
if( $term->parent > 0 ){
$parent_term_ids[] = $term->parent; // Set the parent product category
$parent_term_ids[] = $term->term_id; // (and the child)
} else {
$parent_term_ids[] = $term->term_id; // It is the Main category term and we set it.
}
}
return array_intersect( $categories_ids, array_unique($parent_term_ids) ) ? true : false;
}
And replace in the first function this line:
if( has_term( $categories, 'product_cat', $item['product_id'] ) ) {
by this:
if( has_product_categories( $categories, $item['product_id'] ) ) {