1

I have created the following functions to override the product price and make a max discount for WooCommerce. However, I can't seem to figure out why the woocommerce_coupon_get_discount_amount filter is applying to all coupons.

The way I am trying to get it to function is if the coupon is a percent coupon, it will cap the total discount amount for that coupon. If the coupon is a fixed_cart type and it has the custom field I added _price_per_product_amt filled in, then it will change the price of the product.

Both of these functions work if I have only one coupon in the cart. If I have the fixed_cart_coupon in the cart, it will change the price and set the coupon amount to $0 (the coupon is set to have $0 for its amount). However, if I add a percent coupon as well, the function works for the percent coupon, but then it also adds a discount amount to the fixed_cart_coupon. It should only be modifying the discount amount for percent coupons in the cart.

The second should be zero, since that coupon is set to $0 and modifies the price of products in the cart

// filter to change discount if over max coupon amount
function filter_woocommerce_coupon_get_discount_amount( $discount, $discounting_amount, $cart_item, $single, $instance ) {

    $cartCoupons = WC()->cart->get_applied_coupons();
    
    foreach ($cartCoupons as $key => $appliedCoupon) {
        $coupon = new WC_Coupon($appliedCoupon);
        $couponType = get_post_meta( $coupon->get_id(), 'discount_type', true );
        if ($couponType == 'percent') {
            $maxCouponAmount = get_post_meta( $coupon->get_id(), '_max_discount', true );
            $excludedProducts = explode(",", get_post_meta( $coupon->get_id(), 'exclude_product_ids', true ));
            $cartLines = count(WC()->cart->get_cart());
            $cartLineItems = WC()->cart->get_cart();

            foreach ($cartLineItems as $cartItem){
                $cartProductID[] = $cartItem['product_id'];

                if (!empty($excludedProducts)) {
                    $cartLinesWithoutExcluded = array_intersect($cartProductID,$excludedProducts);
                } else {
                    $cartLinesWithoutExcluded = $cartProductID;
                }
                $cartLinesWithoutExcluded = count($cartLinesWithoutExcluded);
                $totalCartItems = $cartLines - $cartLinesWithoutExcluded;
                $discount = $maxCouponAmount / $totalCartItems;
            }
        } else {
            $discount = 0.00;
        }

        return $discount;

    }

}

// apply the coupon whether it is max discount or a product price adjustment
function apply_max_amount_or_product_price_adjustment(){

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

    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
    return;

    if ( !is_admin() && !wp_is_json_request() ) {
        global $wp, $woocommerce;
        $cartCoupons = WC()->cart->get_applied_coupons();
        foreach ($cartCoupons as $key => $appliedCoupon) {
            $coupon = new WC_Coupon($appliedCoupon);
            $maxCouponAmount = get_post_meta( $coupon->get_id(), '_max_discount', true );
            $excludedProducts = explode(",", get_post_meta( $coupon->get_id(), 'exclude_product_ids', true ));
            $couponType = get_post_meta( $coupon->get_id(), 'discount_type', true );
            // $fixedProductPrice = get_post_meta( $coupon->get_id(), '_adjust_price', true );
            $couponAmount = WC()->cart->get_coupon_discount_amount( $appliedCoupon );

            if (!empty($maxCouponAmount) && $couponType == 'percent' && ($couponAmount > $maxCouponAmount || $couponAmount == $maxCouponAmount)) {
                add_filter( 'woocommerce_coupon_get_discount_amount', 'filter_woocommerce_coupon_get_discount_amount', 10, 5 );
            }

            if ($couponType == 'fixed_cart'){
                $cart = WC()->cart->get_cart();
                $couponProducts = explode(',',get_post_meta( $coupon->get_id(), 'product_ids', true ));
                $fixedPricePerProduct = get_post_meta( $coupon->get_id(), '_price_per_product_amt', true );

                foreach( $cart as $cart_item ) {
                    if (in_array($cart_item['data']->get_parent_id(), $couponProducts)) {
                        $cart_item['data']->set_price( $fixedPricePerProduct );
                    }
                }
            }
        }
    }
}
add_action('woocommerce_before_calculate_totals', 'apply_max_amount_or_product_price_adjustment', 10, 1);

1 Answers1

0

I can't seem to figure out why the woocommerce_coupon_get_discount_amount filter is applying to all coupons.

That's because when you add your filter 'filter_woocommerce_coupon_get_discount_amount' from the function 'apply_max_amount_or_product_price_adjustment', it is added to all coupons. This filter is fired later so your condition to apply it or not will not work (since it had been added). You should remove the part adding the filter and set your filter outside this function.

When you add any filter, even inside a condition, since it is added, it is added for every time until it is removed by remove_filter( ... ). In your case, you can't remove_filter in 'apply_max_amount_or_product_price_adjustment' because it is to early and will remove it for all coupons event "percent" type.

However, if I add a percent coupon as well, the function works for the percent coupon, but then it also adds a discount amount to the fixed_cart_coupon

That's because you are looping all the coupons in your filter 'filter_woocommerce_coupon_get_discount_amount' and always returning value from the first coupon. This filter fires every time there is $coupon->get_discount_amount(), so it will fire on each coupon applied on your cart in checkout. You should remove the loop and check only the current coupon. For better understanding you should rename argument '$instance' to '$coupon' which is the current WC_Coupon object.

You can have a look to the WC_Coupon class and search filter 'woocommerce_coupon_get_discount_amount' ( ctrl + f to find it in the page ) here : https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/class-wc-coupon.php

Also, be carefull in your loop :

foreach ($cartLineItems as $cartItem){
    $cartProductID[] = $cartItem['product_id'];

    if (!empty($excludedProducts)) {
        $cartLinesWithoutExcluded = array_intersect($cartProductID,$excludedProducts);
    } else {
        $cartLinesWithoutExcluded = $cartProductID;
    }

    $cartLinesWithoutExcluded = count($cartLinesWithoutExcluded);
    $totalCartItems = $cartLines - $cartLinesWithoutExcluded;
    $discount = $maxCouponAmount / $totalCartItems; 
}
// here $discount is the one calculated in last loop like all previous loops didn't exists. (same thing for $cartLinesWithoutExcluded and $totalCarItems)

because it will always return $discount for the last cart item only.

Try this code (not tested) :

add_filter( 'woocommerce_coupon_get_discount_amount', 'filter_woocommerce_coupon_get_discount_amount', 10, 5 ); // add filter for all coupons
// filter to change discount if over max coupon amount
function filter_woocommerce_coupon_get_discount_amount( $discount, $discounting_amount, $cart_item, $single, $coupon ) { 
        // remove loop on applied coupons because you are managing coupon that is currently applied to the cart   
        if ( $coupon->is_type( 'percent' ) ) {
            $maxCouponAmount = get_post_meta( $coupon->get_id(), '_max_discount', true );

            // TRY TO REMOVE THIS FOR TESTING
            // OR TRY TO return $maxCouponAmount to check this value
            if ( empty( $maxCouponAmount ) ) { // do not change coupon discount if no max_discount
                return $discount;
            }
            // ---

            $excludedProducts = explode(",", get_post_meta( $coupon->get_id(), 'exclude_product_ids', true ));
            $cartLines = count(WC()->cart->get_cart());
            $cartLineItems = WC()->cart->get_cart();

            // Changes in your loop
            $cartLinesWithoutExcluded = 0; // *** UPDATE HERE

            foreach ($cartLineItems as $cartItem){
                $cartProductID[] = $cartItem['product_id'];
                if (!empty($excludedProducts)) {
                    $cartLinesWithoutExcluded += count ( array_intersect($cartProductID,$excludedProducts) );
                } else {
                    $cartLinesWithoutExcluded += count ( $cartProductID );
                }
            }
            $totalCartItems = $cartLines - $cartLinesWithoutExcluded;
            $discount = $totalCartItems > 0 ? $maxCouponAmount / $totalCartItems : 0; // Never divide by 0 !
            // end of changes in your loop
        } elseif ( $coupon->is_type( 'fixed_cart' ) ) {
            $discount = 0.00;
        }

        return $discount;

}

// apply the coupon whether it is max discount or a product price adjustment
function apply_max_amount_or_product_price_adjustment(){

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

    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
    return;

    if ( !is_admin() && !wp_is_json_request() ) {
        global $wp, $woocommerce;
        $cartCoupons = WC()->cart->get_applied_coupons();
        foreach ($cartCoupons as $key => $appliedCoupon) {
            $coupon = new WC_Coupon($appliedCoupon);
            $maxCouponAmount = get_post_meta( $coupon->get_id(), '_max_discount', true );
            $excludedProducts = explode(",", get_post_meta( $coupon->get_id(), 'exclude_product_ids', true ));
            $couponType = get_post_meta( $coupon->get_id(), 'discount_type', true );
            // $fixedProductPrice = get_post_meta( $coupon->get_id(), '_adjust_price', true );
            $couponAmount = WC()->cart->get_coupon_discount_amount( $appliedCoupon );

            // removed adding filter here

            if ($couponType == 'fixed_cart'){
                $cart = WC()->cart->get_cart();
                $couponProducts = explode(',',get_post_meta( $coupon->get_id(), 'product_ids', true ));
                $fixedPricePerProduct = get_post_meta( $coupon->get_id(), '_price_per_product_amt', true );

                foreach( $cart as $cart_item ) {
                    if (in_array($cart_item['data']->get_parent_id(), $couponProducts)) {
                        $cart_item['data']->set_price( $fixedPricePerProduct );
                    }
                }
            }
        }
    }
}
add_action('woocommerce_before_calculate_totals', 'apply_max_amount_or_product_price_adjustment', 10, 1);

Please test and edit code if necessary. Sorry if my explainations seems messy, I'm writing at middle of the night.

Jo Kolov
  • 86
  • 5