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

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

Related

Add or remove a free product based on WooCommerce cart total and month range

I am on a project, which needs to add to cart a free item on particular promotional month. Therefore, I need to make the product value 0.00 and add it automatically to cart in a particular promotional month. So far, I have added the product automatically when the add to cart total reaches certain amount
/*
* Automatically adding the product to the cart when cart total amount reach to $500.
*/
function aapc_add_product_to_cart() {
global $woocommerce;
$cart_total = 500;
if ( $woocommerce->cart->total >= $cart_total ) {
if ( ! is_admin() ) {
$free_product_id = 12989; // Product Id of the free product which will get added to cart
$found = false;
//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->get_id() == $free_product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $free_product_id );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $free_product_id );
}
}
}
}
add_action( 'template_redirect', 'aapc_add_product_to_cart' );
...
Try the following instead (that handle dynamic changes in cart page and also a month range).
Note: the total amount for the threshold can be only the cart items total.
The code:
function is_free_product_allowed_for_month() {
// Define allowed months in the array (values from 1 to 12)
$allowed_months = array('3', '7', '8');
return in_array( date('n'), $allowed_months );
}
add_action( 'woocommerce_before_calculate_totals', 'add_remove_free_product' );
function add_remove_free_product( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Check if we are in an allowed month
if ( ! is_free_product_allowed_for_month() )
return;
$free_product_id = 339; // ID of the free product
$threshold_amount = 200; // The threshold amount for cart subtotal
$cart_items_total = 0; // Initializing
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
// Check if the free product is in cart
if ( in_array( $free_product_id, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) ) {
$cart_item['data']->set_price(0); // Set price to Zero
$free_item_key = $cart_item_key;
}
// Get cart subtotal incl. tax from items (with discounts if any)
$cart_items_total += $cart_item['line_total'] + $cart_item['line_tax'];
}
// If Cart total is up to the defined amount and if the free products is not in cart, we add it.
if ( $cart_items_total >= $threshold_amount && ! isset($free_item_key) ) {
$cart->add_to_cart( $free_product_id );
}
// If cart total is below the defined amount and free product is in cart, we remove it.
elseif ( $cart_items_total < $threshold_amount && isset($free_item_key) ) {
$cart->remove_cart_item( $free_item_key );
}
}
// For minicart displayed free product price
add_filter( 'woocommerce_cart_item_price', 'free_product_cart_item_price', 10, 3 );
function free_product_cart_item_price( $price_html, $cart_item, $cart_item_key ) {
// Check if we are in an allowed month
if ( ! is_free_product_allowed_for_month() )
return $price_html;
$free_product_id = 339; // ID of the free product
if ( in_array( $free_product_id, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) ) {
return wc_price( 0 );
}
return $price_html;
}
Code goes in functions.php file of the active child theme (or active theme). It should work.
Related: Add or remove specific cart Item based on WooCommerce cart total

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

wordpress remove gift product on cart total change

using the code found on Internet, works perfectly, but it needs to be updated with extra function.
now: if total sum gets 100+ free gift is added. if user removes some products and total gets lower than 100, free gift still stays there
should be: if user removes products and total gets lower than 100, free gift is removed
function aapc_add_product_to_cart() {
global $woocommerce;
$cart_total = 100;
if ( $woocommerce->cart->total >= $cart_total ) {
if ( ! is_admin() ) {
$free_product_id = 6663; // Product Id of the free product which will get added to cart
$found = false;
//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->get_id() == $free_product_id )
$found = true;
}
// if product not found, add it
if ( ! $found )
WC()->cart->add_to_cart( $free_product_id );
} else {
// if no products in cart, add it
WC()->cart->add_to_cart( $free_product_id );
}
}
}
}
add_action( 'template_redirect', 'aapc_add_product_to_cart' );
this works
add_action( 'woocommerce_before_calculate_totals', 'add_or_remove_cart_items', 10, 1 );
function add_or_remove_cart_items( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// ONLY for logged users (and avoiding the hook repetition)
if ( ! is_user_logged_in() && did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
$threshold_amount = 99.9; // The threshold amount for cart total
$free_product_id = 6663; // ID of the free product
$cart_items_total = 0; // Initializing
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
// Check if the free product is in cart
if ( $cart_item['data']->get_id() == $free_product_id ) {
$free_item_key = $cart_item_key;
}
// Get cart subtotal incl. tax from items (with discounts if any)
$cart_items_total += $cart_item['line_total'] + $cart_item['line_tax'];
}
// If Cart total is up to the defined amount and if the free products is not in cart, we add it.
if ( $cart_items_total >= $threshold_amount && ! isset($free_item_key) ) {
$cart->add_to_cart( $free_product_id );
}
// If cart total is below the defined amount and free product is in cart, we remove it.
elseif ( $cart_items_total remove_cart_item( $free_item_key );
}
}

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.

Conditionally set cart item prices for specific products in WooCommerce 3

In WooCommerce I am using some code from this answer:
Set WooCommerce cart item price to zero if the product has already been bought
It works great with one product, but I would like to make it work with several products.
So the code below, for a specific product, change the cart item price to $0.00 if customer has already purchased and if not the price is set to $100 (for the first purchase):
add_action( 'woocommerce_before_calculate_totals', 'conditionally_change_cart_items_price', 10, 1 );
function conditionally_change_cart_items_price( $cart_object ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$targeted_product_id = 1107;
// Set Here your custom price (1st purshase)
$custom_price = 100; // First purshase for product ID 1092
// Detecting if customer has already bought The targeted product (1092)
if( is_user_logged_in() ){
$customer = wp_get_current_user();
$customer_id = $customer->ID; // customer ID
$customer_email = $customer->email; // customer email
if( wc_customer_bought_product( $customer_email, $customer_id, $targeted_product_id) )
$custom_price = 0; // Set to 0 for other purchases (product ID 1092)
}
foreach ( $cart_object->get_cart() as $cart_item ) {
// When targeted product is in cart we change the price
if ( $cart_item['product_id'] == $targeted_product_id ) {
// Woocommerce 3+ compatibility
if ( version_compare( WC_VERSION, '3.0', '<' ) )
$cart_item['data']->price = $custom_price;
else
$cart_item['data']->set_price( $custom_price );
}
}
}
How could I make it work for multiple defined products (instead of one actually)?
You have to create an array with all target product ids and then you should check if a customer bought this product already in the foreach loop:
add_action( 'woocommerce_before_calculate_totals', 'conditionally_change_cart_items_price', 10, 1 );
function conditionally_change_cart_items_price( $cart_object ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// add as many ids as you need
$targeted_product_ids = array( 1107, 1108, 1109 );
// Set Here your custom price (1st purshase)
$custom_price = 100; // First purshase for target product
if ( is_user_logged_in() ) {
$customer = wp_get_current_user();
$customer_id = $customer->ID; // customer ID
$customer_email = $customer->email; // customer email
foreach ( $cart_object->get_cart() as $cart_item ) {
// When targeted product is in cart we change the price
if ( in_array( $cart_item['product_id'], $targeted_product_ids ) ) {
// Detecting if customer has already bought The targeted product
if ( wc_customer_bought_product( $customer_email, $customer_id, $targeted_product_id ) )
// Set to 0 for other purchases
$custom_price = 0;
// Woocommerce 3+ compatibility
if ( version_compare( WC_VERSION, '3.0', '<' ) )
$cart_item['data']->price = $custom_price;
else
$cart_item['data']->set_price( $custom_price );
}
}
}
}
Based on: Set Woocommerce cart item price to zero if the product has already been bought
You will be able to make it work for multiple defined products IDs (in an array) this way:
// For WooCommerce version 3 and above (only)
add_action( 'woocommerce_before_calculate_totals', 'conditionally_change_cart_items_prices', 10, 1 );
function conditionally_change_cart_items_prices( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Set HERE the Products to check in this array
$targeted_product_ids = array( 37, 50, 89, 124, 327 );
$products_bought = array();
// Set Here your custom price (1st purshase)
$custom_price1 = 100; // First purshase
// Set Here your custom price (more purshases than first)
$custom_price2 = 0; // Next purshases
// Detecting if customer has already bought our targeted products
if( is_user_logged_in() ){
$customer = wp_get_current_user();
$customer_id = $customer->ID; // customer ID
$customer_email = $customer->email; // customer email
// Checking each product in the array
foreach( $targeted_product_ids as $product_id ){
if( wc_customer_bought_product( $customer_email, $customer_id, $product_id) ){
// We set all (targeted) purchased products in an array
$products_purchased[] = $product_id;
}
}
}
// Checking cart items and changing item prices if needed
foreach ( $cart->get_cart() as $cart_item ) {
// When a targeted products already purchased is in cart we set price 2
if ( in_array( $cart_item['product_id'], $products_purchased ) ) {
$cart_item['data']->set_price( $custom_price2 );
}
// When a targeted products is in cart and has not been purchased we set price 1
elseif ( in_array( $cart_item['product_id'], $targeted_product_ids ) ) {
$cart_item['data']->set_price( $custom_price1 );
}
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested and works in WooCommerce 3+

Categories