Woocommerce price update, reverts during checkout - php

I have the following code, it loops trougth woocommerce cart and selects the most expensive item and deducts it from the total of the cart updating the total price in the process.
add_filter( 'woocommerce_cart_total', 'wc_modify_cart_price' );
add_filter( 'woocommerce_order_amount_total', 'wc_modify_cart_price' );
function wc_modify_cart_price( $price ) {
$cart_ct = WC()->cart->get_cart_contents_count();
$tax = WC()->cart->get_taxes();
if($cart_ct > 1 ){
$product_prices = [];
$fee = 0;
$prices = array();
// Loop Through cart items - Collect product prices
foreach ( WC()->cart->get_cart() as $cart_item ) {
$_product = wc_get_product( $cart_item['product_id'] );
$terms = get_the_terms( $cart_item['product_id'], 'product_cat' );
#var_dump($terms[0]->name);
if($terms[0]->name == "pizza"){
$prices[] = $cart_item['data']->get_price();
}
}
$sort_price = max($prices);
$max_price = $sort_price;
$cart_total = WC()->cart->cart_contents_total;
$addition = $cart_total - $max_price;
WC()->cart->total = $addition;
$addition = $cart_total - $max_price + $tax[2];
}else{
echo $tax;
$addition = WC()->cart->cart_contents_total + $tax[2];
}
return $addition;
}
add_filter( 'woocommerce_calculated_total', 'wc_modify_cart_price', 10, 2 );
My issue is when i continue and go to checkout the total price order reverts back and i can't find out why, maybe im just calculating and not actually setting said price? how can i not only calculate but actually set that price as the order price?

I would use a different approach here because the numbers won't add up. If you just change the final amount you'll have something like this:
Pizza 1 $50
Pizza 2 $30
subtotal: $80
Total: $30
There are $50 missing without any explanation.
You can use the hook woocommerce_before_calculate_totals to change the cart items.
For example, this code will change the price of the most expensive item to 0. But there is one problem with this. The customer can increase the quantity of these items and you will give away more than you wanted.
add_action( 'woocommerce_before_calculate_totals', 'set_most_expensive_product_to_zero' );
function set_most_expensive_product_to_zero( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
// Set the initial maximum price to 0
$max_price = 0;
// Set the initial product ID to 0
$max_price_product_id = 0;
// Iterate through each item in the cart
foreach ( $cart->get_cart() as $cart_item ) {
// Get the product price
$product_price = $cart_item['data']->get_price();
// Check if the product price is greater than the current maximum price
if ( $product_price > $max_price ) {
// Update the maximum price
$max_price = $product_price;
// Update the product ID of the most expensive product
$max_price_product_id = $cart_item['product_id'];
}
}
// Iterate through each item in the cart again
foreach ( $cart->get_cart() as $cart_item ) {
// Check if the product ID matches the ID of the most expensive product
if ( $cart_item['product_id'] == $max_price_product_id ) {
// Set the price of the most expensive product to 0
$cart_item['data']->set_price( 0 );
}
}
}
Another approach, the one I would use, is creating a one time coupon code with the amount of the free pizza.
Let's say a customer buys 2 pizzas for $20 each and one $16 pizza. You create a $20 coupon and you are done. All the numbers will match and customers will see their discount on the cart/checkout pages.
Here is the code to do just that:
add_action( 'woocommerce_before_calculate_totals', 'generate_coupon_for_most_expensive_product' );
function generate_coupon_for_most_expensive_product( $cart ) {
if(WC()->cart->get_cart_contents_count() < 2){
remove_coupon_from_cart('pizza_');
return;
}
if ( has_coupon_code_with_prefix( 'pizza_' ) ) {
return;
}
// Set the initial maximum price to 0
$max_price = 0;
// Set the initial product ID to 0
$max_price_product_id = 0;
// Iterate through each item in the cart
foreach ( $cart->get_cart() as $cart_item ) {
// Get the product price
$product_price = $cart_item['data']->get_price();
// Check if the product price is greater than the current maximum price
if ( $product_price > $max_price ) {
// Update the maximum price
$max_price = $product_price;
// Update the product ID of the most expensive product
$max_price_product_id = $cart_item['product_id'];
}
}
// Check if the maximum price is greater than 0
if ( $max_price > 0 ) {
// Generate a coupon code
$coupon_code = uniqid( 'pizza_' );
$coupon = new WC_Coupon();
$coupon->set_code($coupon_code);
$coupon->set_amount( $max_price );
$coupon->set_individual_use(true);
$coupon->set_usage_limit(1);
$coupon->set_product_ids(array($max_price_product_id));
$coupon->save();
// Apply the coupon to the cart
$cart->add_discount( $coupon_code );
}
}
function has_coupon_code_with_prefix( $prefix ) {
// Get the applied coupons
$applied_coupons = WC()->cart->get_applied_coupons();
// Check if there are any applied coupons
if ( ! empty( $applied_coupons ) ) {
// Iterate through the applied coupons
foreach ( $applied_coupons as $coupon_code ) {
// Check if the coupon code starts with the specified prefix
if ( strpos( $coupon_code, $prefix ) === 0 ) {
// There is a coupon code applied that starts with the specified prefix
return true;
}
}
}
// No coupon code was found that starts with the specified prefix
return false;
}
function remove_coupon_from_cart($prefix) {
global $woocommerce;
// Get the cart object
$cart = $woocommerce->cart;
// Get the coupon codes applied to the cart
$coupon_codes = $cart->get_coupons();
// Loop through the coupon codes and remove any that start with the specified prefix
foreach ($coupon_codes as $code => $coupon) {
if (strpos($code, $prefix) === 0) {
$cart->remove_coupon($code);
// Save the updated cart
$cart->calculate_totals();
}
}
}

Related

WooCommerce cart discount on second item with coupon code

I am trying to implement a rule to make discounts on products depending on the quantity of products and with a coupon code, taking into account the following points:
With the application of a code the second item has a discount of 40%, the discount must be applied to the item of lesser value, if 4 products are purchased, the discount must be applied to the 2 items of lesser value and so on.
Try this code but it only applies to the second item and doesn't take into account if there are more than 4 items
add_filter( 'woocommerce_coupon_get_discount_amount',
'filter_wc_coupon_get_discount_amount', 10, 5 );
function filter_wc_coupon_get_discount_amount( $discount_amount,
$discounting_amount, $cart_item, $single, $coupon ) {
// Define below your existing coupon code
$coupon_code = 'coupon_code';
// Only for a defined coupon code
if( strtolower( $coupon_code ) !== $coupon->get_code() )
return $discount_amount;
$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 $discount_amount; // return discount
}
} else {
return 0;
}
}

How to add fees based on product categories and quantity in WooCommerce

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 );

Add or remove automatically a free product in Woocommerce cart

I'm trying to create code that automatically adds an item to the customer's cart once they reach a particular price point in the cart. AND I'm trying to exclude that from happening if they are only ordering virtual products, as the "free gift" is only meant for products that are being shipped out. The code I'm using is adding the free gift at the right dollar amount, but it's not excluding any virtual product. Can anyone id what I'm doing wrong?
Here's the code:
/**
* Add another product depending on the cart total
*/
add_action( 'template_redirect', 'add_product_to_cart' );
function add_product_to_cart() {
if ( ! is_admin() ) {
global $woocommerce;
$product_id = 85942; //replace with your product id
$found = false;
$cart_total = 15; //replace with your cart total needed to add above item
if( $woocommerce->cart->total >= $cart_total ) {
//check if product already in cart
if ( sizeof( $woocommerce->cart->get_cart() ) > 0 ) {
$isVirtualOnly = false;
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values) {
$_product = $values[‘data’];
if ($_product != null)
if ($_product->get_type() != $_virtual)
$isVirtualOnly = false;
}
if ($isVirtualOnly != true) {
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->get_id() == $product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
$woocommerce->cart->add_to_cart( $product_id );
}
} else {
// if no products in cart, add it
$woocommerce->cart->add_to_cart( $product_id );
}
}
}
}
/**
* END Add another product depending on the cart total
*/
Updated: The following will auto add to cart a free product:
if there is at least a shippable item in cart,
if the free product is not in cart yet,
and if the cart subtotal is not below a specific amount.
Or will remove the fee product from cart:
if the cart subtotal is not below a specific amount,
and if there is only virtual products.
The code:
add_action( 'woocommerce_before_calculate_totals', 'add_free_product_to_cart' );
function add_free_product_to_cart( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$free_product_id = 38; // <= Set the free product id to add
$min_subtotal = 80; // <= Set the minimum cart subtotal required
$has_shippable = $free_key = false; // Initializing
$cart_subtotal = 0;
// Loop through cart items (first loop)
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
// Check if free product is in cart
if ( $free_product_id == $cart_item['product_id'] ) {
$free_key = $cart_item_key;
$free_qty = $cart_item['quantity'];
$cart_item['data']->set_price(0); // Optional: Set free product price to zero
}
// Check for non virtual products
if ( $cart_item['data']->is_virtual() !== true ) {
$has_shippable = true;
}
// Calculate items subtotal: Add discounted Line total with taxes
$cart_subtotal += $cart_item['line_total'] + $cart_item['line_tax'];
}
// Add Free product
if ( $cart_subtotal >= $min_subtotal && $has_shippable && $free_key === false ) {
$cart->add_to_cart( $free_product_id, 1 );
}
// Remove free product
elseif ( ( $cart_subtotal < $min_subtotal ) && $free_key !== false ) {
$cart->remove_cart_item( $free_key );
}
// Adjust free product quantity to 1
elseif ( $free_key !== false && $free_qty > 1 ) {
$cart->set_quantity( $free_key, 1 );
}
}
// Optional: Display free product price to zero on minicart
add_filter( 'woocommerce_cart_item_price', 'change_minicart_free_gifted_item_price', 10, 3 );
function change_minicart_free_gifted_item_price( $price_html, $cart_item, $cart_item_key ) {
$free_product_id = 38;
if( $cart_item['product_id'] == $free_product_id ) {
return wc_price( 0 );
}
return $price_html;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Related:
Free Give-Away product for specific cart subtotal in WooCommerce
Add free gifted product for a minimal cart amount in WooCommerce

Apply a discount based on ACF field to cart total in WooCommerce

I'm trying to create a discount programmatically for orders on my store. My users have a variable discount rate e.g. 10%. I want to apply their discount to the order before checkout, however, some items in the store are not applicable for a discount.
These items have a toggle set so I can check which products do or do not allow the discount to be applied.
Currently, I'm looping through the order to check which items are applicable and using a 'fixed_cart'coupon to add the discount.
This works, however, the in the admin the coupon applies to all line items even the items which should be skipped. Making it impossible to work how much to refund to customers when a refund is required.
Checking the cart items
$tradeDiscountTotal = 0;
foreach( WC()->cart->get_cart() as $cart_item ) {
if(!get_field('trade_discount_exempt', $cart_item['product_id'])) {
$tradeDiscountTotal += $cart_item['line_subtotal'];
}
}
Applying the total discount for this order
$coupon->set_discount_type('fixed_cart');
$coupon->set_amount($tradeDiscountTotal);
return $coupon;
How can I create a bespoke discount for each order and ensure the products that are discounted are represented correctly in the admin area?
UPDATE
A solution is to create a coupon manually with a percentage discount with for example 10%.
Then we add the following codes based on the couponcode
First part to ensure that a discount of 0 is applied to products that meet the condition
Second part to ensure that the coupon is automatically added to the shopping cart
So we get:
Discount amount on trade_discount_exempt = 0
function filter_woocommerce_coupon_get_discount_amount( $discount, $price_to_discount , $cart_item, $single, $coupon ) {
// Product ID in cart
$product_id = $cart_item['product_id'];
// If 'trade_discount_exempt'
if ( get_field( 'trade_discount_exempt', $product_id ) ) {
$discount = 0;
}
return $discount;
}
add_filter( 'woocommerce_coupon_get_discount_amount', 'filter_woocommerce_coupon_get_discount_amount', 10, 5 );
Apply coupon programmatically if NOT trade_discount_exempt in cart
function action_woocommerce_before_calculate_totals( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Only cart
if( ! is_cart() )
return;
// Coupon code
$coupon_code = 'testcoupon';
// Format
$coupon_code = wc_format_coupon_code( $coupon_code );
// Iterating though each cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Product ID in cart
$product_id = $cart_item['product_id'];
// NOT 'trade_discount_exempt'
if ( ! get_field( 'trade_discount_exempt', $product_id ) ) {
// Applied coupons
$applied_coupons = $cart->get_applied_coupons();
// Is applied
$is_applied = in_array( $coupon_code, $applied_coupons );
// NOT applied
if ( ! $is_applied ) {
// Apply
$cart->apply_coupon( $coupon_code );
break;
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'action_woocommerce_before_calculate_totals', 10, 1 );
Optional: Here are 2 other possible solutions:
1. Use the woocommerce_calculated_total filter hook, apply a discount globally on cart
function filter_woocommerce_calculated_total( $total, $cart ) {
// Discount (percentage)
$percentage = 10; // 10 %
// Set variable, used in loop
$new_total = 0;
// Iterating though each cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Product ID in cart
$product_id = $cart_item['product_id'];
// NOT 'trade_discount_exempt'
if( ! get_field( 'trade_discount_exempt', $product_id ) ) {
// Product price
$price = $cart_item['data']->get_price();
// Caclulate new price
$new_price = $price * ( 1 - ( $percentage / 100 ) );
// Add new price to new total
$new_total += $new_price;
}
}
// New total greater than
if ( $new_total > 0 ) {
$total = $new_total;
}
return $total;
}
add_filter( 'woocommerce_calculated_total', 'filter_woocommerce_calculated_total', 10, 2 );
2. Use the woocommerce_before_calculate_totals action hook, grant the discount on the product price
function action_woocommerce_before_calculate_totals( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Discount (percentage)
$percentage = 10; // 10 %
// Iterating though each cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Product ID in cart
$product_id = $cart_item['product_id'];
// NOT 'trade_discount_exempt'
if( ! get_field( 'trade_discount_exempt', $product_id ) ) {
// Product price
$price = $cart_item['data']->get_price();
// Caclulate new price
$new_price = $price * ( 1 - ( $percentage / 100 ) );
// Set price
$cart_item['data']->set_price( $new_price );
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'action_woocommerce_before_calculate_totals', 10, 1 );

Automatically recalculate quantity when clicking on update cart in Woocommerce

So by using the docs on the WooCommerce site I've been able to add a new product to the basket automatically which works great.
I calculate the number of products by taking the current quantity in the cart and multiplying it by a percentage modifier, this also works.
My problem comes when I update the cart with a new quantity as the bonus product quantity doesn't get updated.
add_action( 'template_redirect', 'add_product_to_cart' );
function add_product_to_cart() {
if ( ! is_admin() ) {
$product_id = 265;
$found = false;
// Get the current cart quantity
foreach ( WC()->cart->get_cart() as $cart_item ) {
$quantity = $cart_item['quantity'];
$percentage = .25;
$bonus = $quantity * $percentage;
}
//check if product already in cart
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->id == $product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $product_id, $bonus );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $product_id, $bonus );
}
}
}
Does anyone have an idea on how to get this to automatically recalculate when update cart is clicked?
Preliminary remarks:
Your code doesn't work when cart is empty as $bonus variable is not defined and you get an error…
Also as you are setting a variable float number for quantity: So if the quantity is not an integer, customer can't change and update the cart items quantities.
This is kind of complex, your bonus quantity need to be updated in another hooked function when:
A product is added to cart
Customer update quantities in cart page
Customer remove items from cart
Also, there is small errors like $_product->id that should instead $_product->get_id() in WC 3+
So your revisited code with additional hooked functions will be:
add_action( 'template_redirect', 'auto_add_product_to_cart', 50 );
function auto_add_product_to_cart() {
if ( ! is_admin() ) {
$cart = WC()->cart; // <== Cart object
$product_id = 37; // <== Specific product to be auto added
$bonus_rate = .25; // <== Bonus rate
$found = false;
// Check if product already in cart
if ( ! $cart->is_empty() ) {
$contents_count = $cart->get_cart_contents_count(); // The cart items count
$bonus_qty = $contents_count * $bonus_rate; // Bonus quantity calculation
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
if ( $cart_item['data']->get_id() == $product_id ){
$found = true;
break; // Stop the loop
}
}
// Product is not found in the loop, we add it
if( ! $found )
$cart->add_to_cart( $product_id, $bonus_qty );
} else {
// There is no items in cart, we add it
$cart->add_to_cart( $product_id, $bonus_rate );
}
}
}
// Calculate, set and update bonus quantity
add_action( 'woocommerce_before_calculate_totals', 'conditional_bonus_quantity_calculation', 20, 1 );
function conditional_bonus_quantity_calculation( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
$bonus_rate = .25; // <== Bonus rate
$product_id = 37; // <== Specific product to be auto added
$contents_count = $cart->get_cart_contents_count(); // Total cart items count
// Loop through cart items to check our specific product
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// If specific product is in cart
if ( $cart_item['data']->get_id() == $product_id ){
$calc_qty = $cart_item['quantity'] < 1 ? 1 : $cart_item['quantity'];
// Bonus quantity calculation
if( $contents_count > 1 )
$bonus_qty = round( $contents_count - $calc_qty ) * $bonus_rate;
else
$bonus_qty = $bonus_rate;
// Update item quantity
$cart->set_quantity( $cart_item_key, $bonus_qty, false );
}
}
}
// Allowing float numbers quantity (step by .25) for a specific product
add_filter( 'woocommerce_quantity_input_args', 'custom_quantity_input_args', 20, 2 );
function custom_quantity_input_args( $args, $product ) {
// Only for your specific product ID on cart page
if( $product->get_id() != 37 && is_cart() ) return $args;
//$args['input_value'] = 0.25; // Default starting value (Optional)
$args['min_value'] = 0.25;
$args['step'] = 0.25;
$args['pattern'] = '[0-9.]*';
$args['inputmode'] = 'numeric';
return $args;
}
// Allowing float numbers in Stock management
remove_filter('woocommerce_stock_amount', 'intval');
add_filter('woocommerce_stock_amount', 'floatval');
Code goes in function.php file of the active child theme (or active theme).
Tested and works.

Categories