WooCommerce - Automatically add bonus product - php

I would like to make a script for automatically adding a bonus product to WooCommerce cart.
The bonus product would be added into the cart on adding a specific product(s). But I've never seen any improved and fully working code with features - such as automatically removing the bonus item or removing the primary product(s) from the cart.
In this solution I've come up with the below code which has the following features:
Options
Multiple required products
Automatic adding
Automatic removing (if there's no required product in the cart)
function bonus_product() {
if (is_admin()) return;
//## OPTIONS
$options = (object) array(
'bonus_product_id' => 1891, //bonus product to add
'required_products_id' => array(1873), //at least on of the specific product(s) needs to be represented in the cart
);
//function variables
$cart_items = WC()->cart->get_cart();
$bonus_product_found = false;
$required_product_found = false;
//check if the cart is not empty
if(sizeof($cart_items) > 0) {
//checking for required products. loop through the cart items
foreach ($cart_items as $key => $item) {
//bonus product already in the cart?
if($item['product_id'] == $options->bonus_product_id) {
$bonus_product_found = true;
}
//one of required products in the cart?
if(in_array($item['product_id'], $options->required_products_id)) {
$required_product_found = true;
}
}
//adding/removing bonus product
//add bonus product to the cart
if(!$bonus_product_found && $required_product_found) {
WC()->cart->add_to_cart($options->bonus_product_id);
}
//remove bonus product from the cart if none of required items is in the cart
if($bonus_product_found && !$required_product_found) {
$cart = WC()->instance()->cart;
$cart_id = $cart->generate_cart_id($options->bonus_product_id);
$cart_item_id = $cart->find_product_in_cart($cart_id);
$cart->set_quantity($cart_item_id, 0);
}
}
}
add_action( 'init', 'bonus_product' );

I have written an alternative version, based on Add To Cart and Remove From Cart action, which seems to be more appropriate.
$bonus_options = (object) array(
'bonus_product_id' => 1891,
'required_products_id' => array( 1873 )
);
// this function called whenever there is a product added to cart
function add_bonus_product( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
global $bonus_options;
// is the product is eligible for bonus product?
if( in_array( $product_id, $bonus_options->required_products_id) ) {
// add the bonus product to cart
WC()->cart->add_to_cart( $bonus_options->bonus_product_id, 1, 0, array(), array( "parent_product_line_item" => $cart_item_key ) );
// later if user removes the product from cart we can use the "parent_product_line_item" to remove the bonus product as well
}
}
add_action( 'woocommerce_add_to_cart', 'add_bonus_product', 10, 6 );
// this function will be called whenever there is a product removed from cart
function remove_bonus_product( $cart_item_key, $cart ) {
$cart_items = WC()->cart->get_cart();
foreach ( $cart_items as $key => $item ) {
if( $item["parent_product_line_item"] == $cart_item_key ) {
// ok this cart item is a bonus item to the product that being removed from the cart
// So remove this too
WC()->cart->remove_cart_item( $key );
}
}
}
add_action( 'woocommerce_cart_item_removed', 'remove_bonus_product', 10, 2 );

Related

Hide auto added gift product from WooCommerce catalog

I have the following scenario that I can not solve, could you guide me, either with a plugin or from code?
I have the product A and the product B, which should not appear in the store nor can it be added individually as it is a gift.
If someone adds product A to the cart, then the system should add product B. It is like a BOGO, but the problem that I cannot solve is that of the free product that must be invisible and not allow to be purchased individually.
Based on Auto add to cart a Gift product variation programmatically in WooCommerce? answer code, here is the complete way to auto add a hidden gift product when a specific product is added to cart:
// Auto add a hidden gift product to cart based on sprcific product
add_action( 'woocommerce_before_calculate_totals', 'wc_auto_add_gift_to_cart' );
function wc_auto_add_gift_to_cart( $cart ) {
if (is_admin() && !defined('DOING_AJAX'))
return;
$required_product_id = 37; // The required product Id for a gift
$product_gift_id = 53; // The gift product Id
$has_required = $gift_key = false; // Initializing
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Check if required product is in cart
if( in_array( $required_product_id, array($cart_item['product_id'], $cart_item['variation_id']) ) ) {
$has_required = true;
}
// Check if gifted product is already in cart
if( in_array($product_gift_id, array($cart_item['product_id'], $cart_item['variation_id']) ) ) {
$gift_key = $cart_item_key;
}
}
// If gift is in cart, but not the required product: Remove gift from cart
if ( ! $has_required && $gift_key ) {
$cart->remove_cart_item( $gift_key );
}
// If gift is not in cart and the required product is in cart: Add gift to cart
elseif ( $has_required && ! $gift_key ) {
$cart->add_to_cart( $product_gift_id );
}
}
// Hide gift product from catalog (shop and archive pages)
add_action( 'woocommerce_product_query', 'hide_gift_product_from_catalog' );
function hide_gift_product_from_catalog( $q ) {
// Not in admin
if ( ! is_admin() ) {
$product_gift_id = 53;
$q->set('post__not_in', array($product_gift_id) );
}
}
// Redirect gift product single page to shop
add_action( 'template_redirect', 'redirect_gift_single_product_to_shop' );
function redirect_gift_single_product_to_shop() {
$product_gift_id = 53;
//
if ( get_the_id() == $product_gift_id ) {
wp_redirect( wc_get_page_permalink( 'shop' ) );
exit;
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related: Auto add to cart a Gift product variation programmatically in WooCommerce?

WooCommerce: If specific product is the only item in cart, clear cart

If users purchase a product on my site, I give them the option of adding a discounted product to their cart at checkout. However, users are able to remove the original product from cart and the discounted product still remains within the cart.
Is it possible to have it such that if the discounted product is the only one in the cart, then the cart should be empty? I haven't found a way to do this.
What I have tried
Currently, I use the following code from bekarice found on Github:
/**
* Renders a notice and prevents checkout if the cart
* only contains products in a specific category
*/
function sv_wc_prevent_checkout_for_category() {
// set the slug of the category for which we disallow checkout
$category = 'clothing';
// get the product category
$product_cat = get_term_by( 'slug', $category, 'product_cat' );
// sanity check to prevent fatals if the term doesn't exist
if ( is_wp_error( $product_cat ) ) {
return;
}
$category_name = '' . $product_cat->name . '';
// check if this category is the only thing in the cart
if ( sv_wc_is_category_alone_in_cart( $category ) ) {
// render a notice to explain why checkout is blocked
wc_add_notice( sprintf( 'Hi there! Looks like your cart only contains products from the %1$s category – you must purchase a product from another category to check out.', $category_name ), 'error' );
}
}
add_action( 'woocommerce_check_cart_items', 'sv_wc_prevent_checkout_for_category' );
/**
* Checks if a cart contains exclusively products in a given category
*
* #param string $category the slug of the product category
* #return bool - true if the cart only contains the given category
*/
function sv_wc_is_category_alone_in_cart( $category ) {
// check each cart item for our category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// if a product is not in our category, bail out since we know the category is not alone
if ( ! has_term( $category, 'product_cat', $cart_item['data']->id ) ) {
return false;
}
}
// if we're here, all items in the cart are in our category
return true;
}
But I don't think it as good a method. Basically, it doesn't allow users to checkout if cart only contains items from a certain category.
You can use the following code that will check on adding to cart your specific discounted product that cart is not empty and that it's not alone in cart, removing it from cart:
// Add to cart validation for the discounted product
add_filter( 'woocommerce_add_to_cart_validation', 'check_specific_discounted_product', 10, 3 );
function check_specific_discounted_product( $passed, $product_id, $quantity ) {
// Settings
$discounted_product_id = 53;
if( WC()->cart->is_empty() && $discounted_product_id == $product_id ) {
wc_add_notice( __("This product can't be purchased alone."), 'notice' );
return false;
}
return $passed;
}
// Removing the discounted product if it's alone in cart
add_action( 'woocommerce_before_calculate_totals', 'conditionally_remove_a_discounted_product' );
function conditionally_remove_a_discounted_product( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Settings
$discounted_product_id = 53;
// Initializing variables
$discounted_item_key = false;
// Loop through cart items (first loop)
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
// When free productis is cart
if ( in_array( $discounted_product_id, array($cart_item['product_id'], $cart_item['variation_id']) ) ) {
$discounted_item_key = $cart_item_key;
}
// if any other product is in cart: EXIT
else {
return;
}
}
// When the discounted product is alone in cart, remove it
if( $discounted_item_key ) {
// display notice on removal (optional)
wc_clear_notices();
wc_add_notice( __("The discounted product can't be purchased alone and has been removed."), 'notice' );
$cart->remove_cart_item( $discounted_item_key ); // Remove
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Auto add product to cart except for some WooCommerce product categories

I am using Auto add a product for cart item from specific product categories in WooCommerce answer code to auto add a free product to the cart. The code works great if the product is in a specific category but I need to add the product if it is NOT in a specific category.
I am able to add the free product if it is not in the specific category with this edit:
if( **!** has_term( $required_categories, 'product_cat', $item['product_id'] ) ) {
$matched_category = true;
}
But this does not remove the free product when the parent product is removed.
Any help would be appreciated!
Updated: Here are the changes that are required to "auto add a product in cart except for specific defined product categories (not removing the auto added product if mixed categories are in cart):
add_action( 'woocommerce_before_calculate_totals', 'auto_add_item_except_for_product_category', 10, 1 );
function auto_add_item_except_for_product_category( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Settings
$except_terms = array('t-shirts'); // Required product category(ies)
$auto_added_id = 70; // Specific product to be added automatically
$except_found = false;
$others_found = false;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Check for product category
if( has_term( $except_terms, 'product_cat', $cart_item['product_id'] ) ) {
$except_found = true;
} else {
$others_found = true;
}
// Check if specific product is already auto added
if( $cart_item['data']->get_id() == $auto_added_id ) {
$auto_added_item_key = $cart_item_key; // keep cart item key
}
}
// If auto added product is in cart with at least an item from a the defined product category only
if ( isset($auto_added_item_key) && $except_found && ! $others_found ) {
$cart->remove_cart_item( $auto_added_item_key ); // Remove specific product
}
// If there is at least an item from others product categories and the specific product is not in cart
elseif ( ! isset($auto_added_item_key) && ! $except_found ) {
$cart->add_to_cart( $auto_added_id ); // Add specific product
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Based on: Auto add a product for cart item from specific product categories in WooCommerce
Might be able to hook into the Woo's remove item from cart hook:
function remove_free_item() {
if ( is_admin() ) {
return;
}
$product_id = 'ID_OF_FREE_ITEM';
$product_cart_id = WC()->cart->generate_cart_id( $product_id );
$cart_item_key = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $cart_item_key ) {
WC()->cart->remove_cart_item( $cart_item_key );
}
}
add_action( 'woocommerce_cart_item_removed', 'after_remove_product_from_cart', 10, 2 );
function after_remove_product_from_cart($removed_cart_item_key, $cart) {
// removed item
$line_item = $cart->removed_cart_contents[ $removed_cart_item_key ];
// removed item product id
$product_id = $line_item[ 'product_id' ];
// might need to wrap this in some check depending on your case
remove_free_item();
}

Woocommerce cart quantity change stop changing the final price

By default when woocommerce cart quantity is changed it updates the item price by multiplying it by the quantity. I would like to change the default behaviour and ensure that when in the cart when the quantity is changed it doesnt change the default price passed when add to cart is clicked
I have added this action hook but i cannot figure out how to stop price change when quantity is changed in a cart.
add_action( 'woocommerce_after_cart_item_quantity_update', 'on_quantity_changed_in_cart', 20, 4 );
function on_quantity_changed_in_cart( $cart_item_key, $quantity, $old_quantity, $cart){
if( ! is_cart() ) return; // Only on cart page
//here stop price from been changed by default
}
Looking to "Disable Woocommerce cart line item quantity price calculation" answer thread, I found out I need to override an action hook to replace the final cost per line:
add_filter('woocommerce_cart_product_subtotal', [$this, 'filter_woocommerce_cart_product_subtotal'], 10, 4);
public function filter_woocommerce_cart_product_subtotal($product_subtotal, $product, $quantity, $cart){
$sub_value = 0;
foreach ($cart->cart_contents as $hash => $value) {
if ($value["product_id"] === wc_get_product($product)->get_id()) {
$sub_value = $value["wcform-custom_price"];
//wcform-custom_price is passed from when adding to the cart.
}
};
return wc_price($sub_value);
}
Now on the totals as well I overriden the following
add_action( 'woocommerce_calculate_totals', [$this,'custom_item_price'],20,1);
add_filter( 'woocommerce_calculated_total', [$this,"calculateFinalTotal"], 20, 2 );
public function custom_item_price( $wc_cart ) {
$cart_contents_total = 0;
foreach ( $wc_cart->get_cart() as $cart_item_key => $cart_item ){
$cart_contents_total += $cart_item["wcform-custom_price"];
}
$wc_cart->subtotal = $cart_contents_total;
}
public function calculateFinalTotal($total,$cart){
$cart_contents_total = 0;
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
$cart_contents_total += $cart_item["wcform-custom_price"];
}
return $cart_contents_total;;///print_r($cart->get_cart());
}

WooCommerce: Add product to cart with price override?

$replace_order = new WC_Cart();
$replace_order->empty_cart( true );
$replace_order->add_to_cart( "256", "1");
The above code add product 256 to the Cart 1 time. But the issue I'm having is that I want to be able to completely override the product price... as far as I can tell, the only thing I can do it apply a coupon to the Cart.
Is there a way to completely override the price to something totally custom?
Here is the code for overriding price of product in cart
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->price = $custom_price;
// for WooCommerce version 3+ use:
// $value['data']->set_price($custom_price);
}
}
Hope it will be useful...
You need to introduce an if statement for checking product id, in above code:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
$target_product_id = 598;
foreach ( $cart_object->cart_contents as $value ) {
if ( $value['product_id'] == $target_product_id ) {
$value['data']->price = $custom_price;
}
/*
// If your target product is a variation
if ( $value['variation_id'] == $target_product_id ) {
$value['data']->price = $custom_price;
}
*/
}
}
Add this code anywhere and make sure that this code is always executable.
After adding this code, when you'll call:
global $woocommerce;
$woocommerce->cart->add_to_cart(598);
Only this product will be added with overridden price, other products added to cart will be ignored for overriding prices.
Hope this will be helpful.
I have tried all above code samples and latest woocommerce 3.0 is not support any of the above example. Use below code and working perfectly for me.
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custom price
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$cart_item['data']->set_price($custom_price);
}
}
After release of woocommerce version 3.0.0 product price is update on add to cart using set_price($price) function. The example is given as below :
add_action( 'woocommerce_before_calculate_totals', 'mj_custom_price' );
function mj_custom_price( $cart_object ) {
$woo_ver = WC()->version;
$custom_price = 10;
foreach ( $cart_object->cart_contents as $key => $value )
{
if($woo_ver < "3.0.0" && $woo_ver < "2.7.0")
{
$value['data']->price = $custom_price;
}
else
{
$value['data']->set_price($custom_price);
}
}
}
Many Thanks
For the Wordpress and Woocommerce latest version,Please use like this
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
foreach ( $cart_object->cart_contents as $key => $value ) {
$custom_price = 5;
$value['data']->set_price($custom_price);
}
}
For eveeryone that got here from Google. The above is now deprecated as i found out updating to WooCommerce 3.0.1.
Instead of the above you now need to use set_price instead of price
Here is an example:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->set_price = $custom_price;
}
}
I hope this helps people in the future :)
This is how i did it, first i add my custom price to cart_item_data witch can save custom data to cart items, then i use woocommerce_before_calculate_totals, loop the cart and add the previously added price.
function add_donation_to_cart() {
$cart_item_data = array('price' => $_REQUEST['donate_amount']);
$woocommerce->cart->add_to_cart( 5395, 1, '', array(), $cart_item_data);
}
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart ) {
foreach ( $cart->cart_contents as $key => $value ) {
$value['data']->price = $value['price'];
}
}
With WooCommerce 2.5 I found this to be a 2-part process. The first step is to change the run-time display of pricing when added to the cart via the woocommerce_add_cart_item filter. The second part is to set the persistent session data which is read during checkout via the woocommerce_get_cart_item_from_session filter. This seems to be faster than hooking the calculate totals filters (such as woocommerce_before_calculate_totals) as they are run very frequently in WooCommerce.
More details here:
woocommerce change price while add to cart
To make it dynamic ( override price for each item in cart separately ), you need to save the override product price in session with cart item key as session key using woocommerce_add_to_cart hook.
by using these session values you can calculate correct Cart Total and make the altered price appear in the Order Item as well
You can use the following
add_filter( 'woocommerce_cart_item_price', 'kd_custom_price_message' );
function kd_custom_price_message( $price ) {
$textafter = ' USD';
return $price . $textafter;
}

Categories