Changing WooCommerce cart price after applied coupon code

10,320

Solution 1

This is not really possible … Why? … Because (the logic):

  1. You have the product price
  2. Then the coupon discount is applied to that price (afterwards)
    ==> if you change the product price, the coupon is will be applied to that changed price

What you can do instead:

  1. You don't change product price
  2. if entered the coupon is applied and …
  3. If "option2" product is added to cart:
  4. Apply a custom discount (a negative fee) based on the product price added after using WC_cart add_fee() method…

For this last case you will have to fine tune your additional discount.
If the coupon has not been applied or it's removed there is no additional discount.

Your custom function will be hooked in woocommerce_cart_calculate_fees action hook instead:

add_action( 'woocommerce_cart_calculate_fees', 'option2_additional_discount', 10, 1 );
function option2_additional_discount( $cart_obj ) {

    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    $discount = 0;
    $applied_coupons = $cart_obj->get_applied_coupons();

    foreach ( $cart_obj->get_cart() as $item_values ) {
        if( 'option2' == $item_values['custom_options'] && !empty($applied_coupons) ){
            $product_id = $item_values['product_id'];
            $percentage = get_post_meta( $product_id , 'percentage', true );
            $quantity = $item_values['quantity'];
            $product_reg_price = $item_values['data']->regular_price;
            $line_total = $item_values['line_total'];
            $line_subtotal = $item_values['line_subtotal'];
            $percentage = 90;

            ## ----- CALCULATIONS (To Fine tune) ----- ##

            $item_discounted_price = ($percentage / 100) *  $product_reg_price * $item_values['quantity'];
            // Or Besed on line item subtotal
            $discounted_price = ($percentage / 100) * $line_subtotal;

            $discount += $product_reg_price - $item_discounted_price;
        }
    }
    if($discount != 0)
        $cart_obj->add_fee( __( 'Option2 discount', 'woocommerce' ) , - $discount );
}

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.

Solution 2

Adding a negative fee using the WC_Cart->add_fee() method wasn't working for me. When i check the WC Cart class, it even states you are not allowed to use a negative ammount.

See the docs.

I did the following:

  • create a placeholder coupon with a 'secure' code, e.g. custom_discount_fjgndfl28. Set a discount ammount of 0, so when somebody (somehow) uses this coupon outside your program the discount is still 0.
  • Use filter woocommerce_get_shop_coupon_data, and set all the coupon data you want for that coupon/session.
  • Hook into woocommerce_before_calculate_totals and set your custom coupon to the cart.
  • At this point the Cart should calculate everything correctly. And when it becomes an order, it also has the correct discount ammount.
  • Note: the coupon code is also used as a label in some templates. Use filter woocommerce_cart_totals_coupon_label to change it.

Example functions:

/**
 * NOTE: All the hooks and filters below have to be called from your own
 * does_it_need_custom_discount() function. I used the 'wp' hook for mine.
 * Do not copy/paste this to your functions.php.
**/

add_filter('woocommerce_get_shop_coupon_data', 'addVirtualCoupon', 10, 2);
function addVirtualCoupon($unknown_param, $curr_coupon_code) {

    if($curr_coupon_code == 'custom_discount_fjgndfl28') {

      // possible types are: 'fixed_cart', 'percent', 'fixed_product' or 'percent_product.
      $discount_type = 'fixed_cart'; 

      // how you calculate the ammount and where you get the data from is totally up to you.
      $amount = $get_or_calculate_the_coupon_ammount;

      if(!$discount_type || !$amount) return false;

        $coupon = array(
            'id' => 9999999999 . rand(2,9),
            'amount' => $amount,
            'individual_use' => false,
            'product_ids' => array(),
            'exclude_product_ids' => array(),
            'usage_limit' => '',
            'usage_limit_per_user' => '',
            'limit_usage_to_x_items' => '',
            'usage_count' => '',
            'expiry_date' => '',
            'apply_before_tax' => 'yes',
            'free_shipping' => false,
            'product_categories' => array(),
            'exclude_product_categories' => array(),
            'exclude_sale_items' => false,
            'minimum_amount' => '',
            'maximum_amount' => '',
            'customer_email' => '',
            'discount_type' => $discount_type,
        );

        return $coupon;
    }
}

add_action('woocommerce_before_calculate_totals', 'applyFakeCoupons');
function applyFakeCoupons() {
  global $woocommerce;
  // $woocommerce->cart->remove_coupons(); remove existing coupons if needed.
  $woocommerce->cart->applied_coupons[] = $this->coupon_code; 
}

add_filter( 'woocommerce_cart_totals_coupon_label', 'cart_totals_coupon_label', 100, 2 );
function cart_totals_coupon_label($label, $coupon) {

    if($coupon) {
      $code = $coupon->get_code();
      if($code == 'custom_discount_fjgndfl28') {
        return 'Your custom coupon label';
      }
    }

    return $label;
}

Please Note: i copied these functions out of a class that handles much more, it's only to help you get going.

Share:
10,320

Related videos on Youtube

Archana
Author by

Archana

Updated on June 04, 2022

Comments

  • Archana
    Archana almost 2 years

    I have created a product on WooCommerce, and added two options on product detail page using the hook woocommerce_before_add_to_cart_button. Now when customers add product to cart from product detail page they have two options their. They can choose one option from these two options.

    Then I have stored the user selected value in cart meta using the woocommerce hook woocommerce_add_cart_item_data.

    I am using the code from this answer: Save product custom field radio button value in cart and display it on Cart page

    This is my code:

    // single Product Page options  
    add_action("woocommerce_before_add_to_cart_button", "options_on_single_product");
    function options_on_single_product(){
        $dp_product_id = get_the_ID(); 
        $product_url = get_permalink($dp_product_id);
    
        ?>
            <input type="radio" name="custom_options" checked="checked" value="option1"> option1<br />
            <input type="radio" name="custom_options" value="option2"> option2
        <?php
    }
    
    
    //Store the custom field
    add_filter( 'woocommerce_add_cart_item_data', 'save_custom_data_with_add_to_cart', 10, 2 );
    function save_custom_data_with_add_to_cart( $cart_item_meta, $product_id ) {
        global $woocommerce;
        $cart_item_meta['custom_options'] = $_POST['custom_options'];
        return $cart_item_meta; 
    }
    

    And this is what I have tried:

    add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 10, 1);
    function add_custom_price( $cart_obj ) {
    
        if ( is_admin() && ! defined( 'DOING_AJAX' ) )
            return;
    
        foreach ( $cart_obj->get_cart() as $key => $value ) {
            $product_id = $value['product_id'];
            $custom_options = $value['custom_options'];
            $coupon_code = $value['coupon_code'];
            if($custom_options == 'option2')
            {
                if($coupon_code !='')
                {
                    global $woocommerce;
                    if ( WC()->cart->has_discount( $coupon_code ) ) return;
                    (WC()->cart->add_discount( $coupon_code ))
    
                //code for second discount
                }
                else{
                    $percentage = get_post_meta( $product_id , 'percentage', true );
                    //print_r($value);
                    $old_price = $value['data']->regular_price;
                    $new_price = ($percentage / 100) * $old_price;
                    $value['data']->set_price( $new_price );
                }
            } 
        }
    }
    

    Now what I am trying to get with that last snippet is:

    • If Option1 is selected by the customer then woocommerce regular process is run.
    • If Option2 is selected then firstly coupon code applied to cart (if code entered by the customer) and then the price is divide by some percentage (stored in product meta) is applied afterward.

    But it’s not working as expected because the changed product price is maid before and coupon discount is applied after on this changed price.

    What I would like is that the coupon discount will be applied first on the product regular price and then after change this price with my custom product discount.

    Is this possible? How can I achieve that?

    Thanks.

  • Darko
    Darko about 4 years
    Hello, can you please explain "All the hooks and filters below have to be called from your own does_it_need_custom_discount() function", thanks?
  • Bjorn
    Bjorn about 4 years
    I always create a virtual_coupon class in my app. When a discount is needed, I call the class and set the discount info in a public $discounts = array();. The virtual_coupon action and filter hooks only run if $this->discounts are set. I realize this is not very in-depth information, but it really depends on what you are trying to accomplish and how your app/plugin is structured.