I need to calculate the extra shipping cost the backorder products will have. I first thought of duplicate the shipping cost per product, but this is not accurate. So I thought I should withing my function run another function (same as woocommerce uses) to calculate the shipping cost for each and all backorder product in the cart.
Here is where I am so far.
// Add a extra shipping fee to each backordered product
add_action('woocommerce_cart_calculate_fees', 'add_backorder_shipping_fee', 20, 1);
function add_backorder_shipping_fee($cart)
{
if (is_admin() && !defined('DOING_AJAX'))
return;
$backorderNumber = 0;
// Loop through the cart items (added products).
foreach (WC()->cart->get_cart() as $cart_item) {
// Product Info
$product = $cart_item['data'];
// Quantity of product in cart (being purchased).
$buyingQuantity = $cart_item['quantity'];
if (!empty($product && $product->backorders_allowed())) {
// Calculate if product quantity in cart is more than stock, returns negative if so.
$isBuyingMoreThanStock = $product->stock_quantity - $buyingQuantity;
if ($isBuyingMoreThanStock < 0) {
//!ITEMS: Calculate the number of items are being backordered.
// $backorderNumber = $backorderNumber + ($isBuyingMoreThanStock * -1);
//!PRODUCTS: Calculate the number of products are being backordered.
$backorderNumber = $backorderNumber + 1;
}
}
}
// Get the shipping cost.
$totalShippingCost = WC()->cart->get_shipping_total() + WC()->cart->get_shipping_tax();
// Calculate and apply the above shipping cost to each backordered item.
$extraShippingFee = $backorderNumber * $totalShippingCost;
// Create the fee.
if ($backorderNumber > 0 && $totalShippingCost > 0) {
$cart->add_fee(__('Backorder Extra Shipping Fee (' . $backorderNumber . ')', 'woocommerce'), $extraShippingFee);
}
}
So instead of get the current shipping cost and multiple by the number of backordered product, I would like to calculate the extra fee just like woocommerce calculate the shipping cost. Another thing that comes to mind is if I could apply this to Fedex live rate API as well.
I suggest an alternative solution based on WooCommerce shipping fee calculate method.
// Calculate and sum all backorder product shipping fees
add_filter( 'woocommerce_shipping_packages', function ( $packages ) {
foreach ( $packages as $package_key => &$package ){
$backorders = array();
foreach ( $package['contents'] as $cartkey => $values ){
if ( $values['data']->is_on_backorder() ){
if ( empty($i) ) $i = 1; else $i++;
$backorders[ $i ][ $cartkey ] = $values; // Get backorders
} else {
$backorders[ 0 ][ $cartkey ] = $values; // Get NOT backorders
}
}
if ( count( $backorders ) <= 1 ) continue; // Break if no backorder
$tax_rates = WC_Tax::get_shipping_tax_rates();
$new_packages = array();
foreach ( $backorders as $i => $backorder ){
$new_packages[ $i ] = $package;
$new_packages[ $i ]['contents'] = $backorder;
$new_packages[ $i ]['contents_cost'] = array_sum( wp_list_pluck( $backorder, 'line_total' ) ); // calculate products total
$new_packages[ $i ] = WC()->shipping()->calculate_shipping_for_package( $new_packages[ $i ] ); // calculate shipping cost
}
foreach ( $package['rates'] as $ratekey => &$rate ){
$rate->cost = 0;
foreach ( $new_packages as $i => $new_package ){
if ( empty( $new_package['rates'][$ratekey] ) ) continue;
$rate->cost += $new_package['rates'][$ratekey]->cost; // sum shipping costs
}
$rate->taxes = WC_Tax::calc_tax( $rate->cost, $tax_rates, true ); // re-calculate taxes
$rate->label = 'Shipped in order'; // change shipping label if you want
}
}
return $packages;
});
The advantage of this method is that it also supports shipping calculation using shipping classes such as flat_rate and table_rate.
Related
I want to add a 30% discount on the cheapest item in the cart, except if it already has a discount.
Based on Cart discount for product that cost less in Woocommerce answer code, this is my code attempt:
add_action('woocommerce_cart_calculate_fees', 'discount_on_cheapest_cart_item', 20, 1 );
function discount_on_cheapest_cart_item( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Only for 2 items or more
if ( $cart->get_cart_contents_count() < 2 ) return;
// Initialising
$percentage = 50; // 10 %
$discount = 0;
$item_prices = array();
// Loop though each cart items and set prices in an array
foreach ( $cart->get_cart() as $cart_item ) {
$product_prices_excl_tax[] = wc_get_price_excluding_tax( $cart_item['data'] );
}
sort($product_prices_excl_tax);
if( ! $cart_item['data']->is_on_sale() ){
$discount = reset($product_prices_excl_tax) * $percentage / 100;
$cart->add_fee( "Discount on cheapest (".$percentage."%)", -$discount );
}
}
Is there a way to make it work so if the product with the lowest price is not on sale, then apply a 30%, if it is on sale, don't. But this applies only to the lowest, if any other product is on sale, we skip it.
The use of if( ! $cart_item['data']->is_on_sale() ) in your code attempt
will not be of any influence as this is used outside the foreach loop.
This anwer will apply a discount calculated on the basis of the product with the lowest price, this only if this product is not already on sale.
So you get:
function action_woocommerce_cart_calculate_fees( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Only for 2 items or more
if ( $cart->get_cart_contents_count() < 2 ) return;
// Setting
$percentage = 30; // 30 %
// Initialize
$discount = 0;
$product_prices_on_sale = array();
$product_prices_excl_tax = array();
// Loop though each cart items and set prices in an array
foreach ( $cart->get_cart() as $cart_item ) {
// When cart contains a on sale product
if ( $cart_item['data']->is_on_sale() ) {
// On sale, push to on sale array
$product_prices_on_sale[] = wc_get_price_excluding_tax( $cart_item['data'] );
}
// Push to excl tax array
$product_prices_excl_tax[] = wc_get_price_excluding_tax( $cart_item['data'] );
}
// Sort an array in ascending order
sort( $product_prices_on_sale );
sort( $product_prices_excl_tax );
// Set the internal pointer of an array to its first element
$p_o_s = reset( $product_prices_on_sale );
$p_e_t = reset( $product_prices_excl_tax );
// NOT equal
if ( $p_o_s != $p_e_t ) {
// Calculate discount
$discount = $p_e_t * $percentage / 100;
// Apply discount (negative fee)
$cart->add_fee( 'Discount on cheapest (' . $percentage . '%)', -$discount );
}
}
add_action( 'woocommerce_cart_calculate_fees', 'action_woocommerce_cart_calculate_fees', 10, 1 );
first of all I am not a pro developer and for a customer we have the following request.
Currently, in Woocommerce the shipping cost is calcutlated on basis of the total order amount ([fee percent="8"]) --> 8% of the order amount.
The customer wants now that if a coupon is applied the shipping cost should be calculated on the basis of the original amount NOT the new total amount with the coupon amount.
Any idea to resolve that smoothly ?
Thanks !
Julian
You need to use woocommerce_package_rates filter hook in which get total cart amount except coupon amount and changes shiiping cost for each shipping rates in loop. Let me know if you getting any issue.
add_filter( 'woocommerce_package_rates', 'custom_shipping_costs', 10, 2 );
function custom_shipping_costs( $rates, $package ) {
$carttotal = WC()->cart->get_cart_subtotal();
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 ){
$taxes[$key] = $carttotal * 0.08;
}
}
$rates[$rate_key]->taxes = $taxes;
}
Here are my solutions:
1.
add_filter( 'woocommerce_package_rates', 'custom_shipping_costs', 20, 2 );
function custom_shipping_costs( $rates, $package ) {
global $woocommerce;
if (!empty(WC()->cart->applied_coupons)){
$totalfees = WC()->cart->get_fees(); //$order->get_total_fees();
$producttotal = WC()->cart->subtotal; // $order->get_subtotal();
$total = (float)$totalfees + (float)$producttotal;
// New shipping cost (can be calculated)
$new_cost = $total * .08;
$tax_rate = array();
foreach( $rates as $rate_key => $rate ){
// Excluding free shipping methods
if( $rate->method_id != 'free_shipping'){
// Set rate cost
$rates[$rate_key]->cost = $new_cost;
// Set taxes rate cost (if enabled)
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 )
$taxes[$key] = $new_cost * $tax_rate;
}
$rates[$rate_key]->taxes = $taxes;
}
}
return $rates;
}
}
if(!empty( $order->get_used_coupons() )) {
$totalfees = $order->get_total_fees();
$producttotal = $order->get_subtotal();
$total = (float)$totalfees + (float)$producttotal;
// Get the customer country code
$country_code = $order->get_shipping_country();
// Set the array for tax calculations
$calculate_tax_for = array(
'country' => $country_code,
'state' => '', // Can be set (optional)
'postcode' => '', // Can be set (optional)
'city' => '', // Can be set (optional)
);
// Optionally, set a total shipping amount
$new_ship_price = $total * .08;
// Get a new instance of the WC_Order_Item_Shipping Object
$item = new WC_Order_Item_Shipping();
$item->set_method_title( "Flat rate" );
$item->set_method_id( "flat_rate:14" ); // set an existing Shipping method rate ID
$item->set_total( $new_ship_price ); // (optional)
$item->calculate_taxes($calculate_tax_for);
$order->add_item( $item );
$order->calculate_totals();
$order->update_status('on-hold');
// $order->save(); // If you don't update the order status
}
Resources 1 , 2, 3, 4
I am trying to discount the cheapest item in the cart if my coupon type is used:
add_filter('woocommerce_coupon_get_discount_amount', 'wc_cpn_disc', 10, 5);
function wc_cpn_disc($discount, $discounting_amount, $cart_item, $single, $coupon) {
// IF TYPE MATCHES PERFORM CUSTOM CALCULATION
if ($coupon->type == 'cheapest_free'){
global $woocommerce;
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
$product_price[] = get_option('woocommerce_tax_display_cart') == 'excl' ? $_product->get_price_excluding_tax() : $_product->get_price_including_tax(); /*Store all product price from cart items in Array */
}
$lowestprice = min($product_price);
$discount = number_format((float)$lowestprice/10,2,'.','');
}
return $discount;
}
The discount amount is very weird - no matter what I try, it never comes out to the value I expect. At first I thought it was a percentage discount, but I expect this to be a fixed amount. I have tried running my get lowest price bit of function elsewhere on the site and it returns 1.195 when the lowest value item is 11.95 - so I know that part works. But the discount on a total basket of 265.60 is 23.90 - I just don't get it!
I just want to get the lowest priced item in the cart, and discount that amount.
I managed to solve this with the help of Bossman and a couple of other threads. It turns out I needed to change my method - full code below.
add_filter('woocommerce_coupon_get_discount_amount', 'tfcc_cheapest_free', 10, 5);
function tfcc_cheapest_free($discount, $discounting_amount, $cart_item, $single, $coupon) {
// IF TYPE MATCHES PERFORM CUSTOM CALCULATION
if ($coupon->type == 'cheapest_free'){
$items_prices = [];
$items_count = 0;
// Loop through cart items
foreach( WC()->cart->get_cart() as $key => $item ){
// Get the cart item price (the product price)
if ( wc_prices_include_tax() ) {
$price = wc_get_price_including_tax( $item['data'] );
} else {
$price = wc_get_price_excluding_tax( $item['data'] );
}
if ( $price > 0 ){
$items_prices[$key] = $price;
$items_count += $item['quantity'];
}
}
// Only when there is more than one item in cart
if ( $items_count > 1 ) {
asort($items_prices); // Sorting prices from lowest to highest
$item_keys = array_keys($items_prices);
$item_key = reset($item_keys); // Get current cart item key
// Targeting only the current cart item that has the lowest price
if ( $cart_item['key'] == $item_key ) {
return reset($items_prices)/$cart_item['quantity']; // return the lowest item price as a discount
}
} else {
return 0;
}
}
}
I hope this might help someone else out who needs a similar feature in the future.
I'm trying to create a shortcode for a single product page that shows the average sale price of the current product. So the shortcode will be in a description of a product page.
This value should work both with simple and variable products.
I'm able to get average value for simple product but only when there is one item in order and I'm not able to solve this with variable products.
function avg_sales_price () {
global $product;
if ( is_a($product, 'WC_Product') ) {
$product_id = $product->get_id();
$orders = wc_get_orders( array(
'numberposts' => -1,
'post_type' => 'shop_order',
) );
foreach ( $orders as $order ) {
if ( count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item_id => $item )
$productid = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();
if ( $productid == $product_id) {
$product_price_in_orders += $item->get_total();
}
}
}
$count_orders = 0;
foreach ( $orders as $order ) {
$has_product = false;
foreach ( $order->get_items() as $item_values )
if ( $item_values['product_id'] == $product_id )
$has_product = true;
if ( $has_product )
$count_orders++;
}
$avg_sales_price = round( $product_price_in_orders / $count_orders );
echo '<div class="sales-price-number">
<span class="sales-price-text">Average Sale Price </span>
<span class="sales-price-value">' . esc_html( get_woocommerce_currency_symbol() . $avg_sales_price ). ' </span>
</div>';
}
}
Any ideas how to get this work?
Thanks.
To get orders based on current product you shouldn't have to query all orders every time. It takes up too many resources especially if the orders are many.
From Get all Orders IDs from a product ID in Woocommerce answer code you can use the function get_orders_ids_by_product_id() to get all orders from a specific product Id (where you will have to set the desired order statuses).
Then you can use this other function to calculate the average price of each product (simple or variation) based on the product id.
// calculate the average price based on the product id using the query within the "get_orders_ids_by_product_id()" function
function calculates_average_price_based_on_product_id( $product_id ) {
$orders = get_orders_ids_by_product_id( $product_id );
$count = count( $orders );
if ( $count <= 0 ) {
return; // set the return value in case there are no orders for this product
}
$prices = 0;
foreach ( $orders as $order_id ) {
$order = wc_get_order( $order_id );
if ( $order ) {
foreach ( $order->get_items() as $item ) {
$product = $item->get_product();
if ( $product_id == $product->get_id() ) {
$qty = $item->get_quantity();
$total = $item->get_total();
$prices += $total / $qty;
}
}
}
}
return $prices / $count;
}
HOW DOES IT WORK
If the product is variable:
Calculate the average price ONLY between the product variations that have been purchased
If no variation has been purchased, it calculates the average price between the net prices currently set
If the product is simple:
Calculate the average price based on the selling prices across all orders containing this product
If the product has never been purchased, it displays the currently set net price
SHORTCODE
Finally here is the shortcode which will show the average price based on the product of the current page. If the product is variable, the average price between all variations will be calculated and shown.
The [average_price_product] shortcode will only work on the product page.
// create a shortcode showing the average price of the product
add_shortcode( 'average_price_product', 'shortcode_average_price_product' );
function shortcode_average_price_product() {
global $product;
// initialize prices
$avg_price = 0;
$current_avg_price = 0;
// initializes the number of variations that have been purchased at least once
$count = 0;
// if the product is variable it calculates the average price of all the variations
if ( $product->is_type( 'variable' ) ) {
$variation_ids = $product->get_children();
// gets the total number of variations
$total_variations = count( $variation_ids );
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
// gets the average price of each single variation
$avg_price_variation = calculates_average_price_based_on_product_id( $variation_id );
// if the average price of the current product variation is greater than zero, it updates the price and count
if ( $avg_price_variation > 0 ) {
$avg_price += $avg_price_variation;
$count++;
}
// sum the current price of each single variation (in case no variation has ever been purchased)
$current_avg_price += $variation->get_price();
}
// if no variation has been purchased, I display the average price between the current net prices
if ( $avg_price == 0 ) {
$avg_price = $current_avg_price / $total_variations;
} else {
$avg_price /= $count;
}
// if the product is simple
} else {
// gets the average price of the product
$avg_price = calculates_average_price_based_on_product_id( $product->get_id() );
// if the simple product has never been ordered, I will display the current price
if ( $avg_price == 0 ) {
$avg_price = $product->get_price();
}
}
// creates the HTML content to display on the product page
$html = '<div class="sales-price-number"><span class="sales-price-text">Average Sale Price </span><span class="sales-price-value">' . wc_price( $avg_price ) . ' </span></div>';
return $html;
}
HOOK
As an alternative to the shortcode you can add the average price on the product page using the woocommerce_before_add_to_cart_form hook, with the following function:
// shows the average price on the product page
add_action( 'woocommerce_before_add_to_cart_form', 'shows_average_price_on_the_product_page', 10 );
function shows_average_price_on_the_product_page() {
global $product;
// initialize prices
$avg_price = 0;
$current_avg_price = 0;
// initializes the number of variations that have been purchased at least once
$count = 0;
// if the product is variable it calculates the average price of all the variations
if ( $product->is_type( 'variable' ) ) {
$variation_ids = $product->get_children();
// gets the total number of variations
$total_variations = count( $variation_ids );
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
// gets the average price of each single variation
$avg_price_variation = calculates_average_price_based_on_product_id( $variation_id );
// if the average price of the current product variation is greater than zero, it updates the price and count
if ( $avg_price_variation > 0 ) {
$avg_price += $avg_price_variation;
$count++;
}
// sum the current price of each single variation (in case no variation has ever been purchased)
$current_avg_price += $variation->get_price();
}
// if no variation has been purchased, I display the average price between the current net prices
if ( $avg_price == 0 ) {
$avg_price = $current_avg_price / $total_variations;
} else {
$avg_price /= $count;
}
// if the product is simple
} else {
// gets the average price of the product
$avg_price = calculates_average_price_based_on_product_id( $product->get_id() );
// if the simple product has never been ordered, I will display the current price
if ( $avg_price == 0 ) {
$avg_price = $product->get_price();
}
}
// creates the HTML content to display on the product page
$html = '<div class="sales-price-number"><span class="sales-price-text">Average Sale Price </span><span class="sales-price-value">' . wc_price( $avg_price ) . ' </span></div>';
echo $html;
}
The code has been tested and works. Add it to your active theme's functions.php.
Advice
Although the code above works correctly this is not the best way to get the average price of the product.
You could create and update a custom meta for each product whenever the status of each order changes, for example, to "wc-completed".
You will only need a counter of the number of orders for that product and the average price calculated based on the last completed order.
When a new order goes to the completed status you will update the respective fields.
I want to set shipping cost quantity wise on my woocommerce theme. I want to do this option :
For 1 to 5 products shiping cost will be 15%.
More than 5 products whipping cost will be $6.99 .
Can i add this shipping option without a plugin ?
You need to hook a function to woocommerce_calculate_totals action which is triggered right before calculating the final cart total. The woocommerce_calculate_totals action provides the WC_Cart instance, on which you can perform manipulation as per your requirement.
add_action('woocommerce_calculate_totals', 'modify_shipping_totals');
function modify_shipping_totals($cart) {
if($cart->get_cart_contents_count() < 6) {
$cart->shipping_total = ( 15/100 ) * $this->cart_contents_total;
// shipping cost will be 15% of cart content total
// you may also want to modify the shipping tax.
$cart->shipping_tax_total = 0;
} else {
$cart->shipping_total = 6.99;
$cart->shipping_tax_total = 0;
}
}
For further reference regarding changable variables refer to WC_Cart documentation.
// **Note**: This code is working only when you set Flat rate Settings
// cost value is 1
add_filter( 'woocommerce_package_rates', 'custom_package_rates', 10, 2 );
function custom_package_rates( $rates, $packages ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
$cart_count = WC()->cart->get_cart_contents_count();
$cart_total = WC()->cart->cart_contents_total;
foreach($rates as $rate_key => $rate_values ) {
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
if( $method_id == 'flat_rate' ){
if( $cart_count < 99 ){
$flat_rate_value = 4.95; //"Applay Flat rate less then 99 quatity"
$cart_10_percent = 0; // No percent discount
}
if( $cart_count > 99 ){
$flat_rate_value = 9.95; // "Applay Flat rate greater then 99 quatity"
$cart_10_percent = 0; // No percent discount
}
$rate_cost = $flat_rate_value > $cart_10_percent ? $flat_rate_value - $cart_10_percent : 0;
// Set the new calculated rate cost
$rates[$rate_id]->cost = number_format( $rates[$rate_id]->cost * $rate_cost, 2 );
}
}
return $rates;
}
The Pranav solution works if you call the add_action inside the init hook, like this:
function init_shop(){
add_action('woocommerce_calculate_totals', 'modify_shipping_totals', 10);
}
add_action( 'init', 'init_shop');