Automated Discounts & Fees

Add or Remove Money based on Cart Data in WooCommerce

I haven’t written a proper nerdy post in a while, and since it took me quite a while to figure this out in a way that worked for my use case, I decided to get this all written up, in case anyone else here takes enough pride in their coding, that they just refuse to pay 150 bucks a year for a thing they can write up in under two hours.

What am I even talking about? Discounts and fees, based on the Items added in your cart. I’m not talking about applying some Coupon Code based on the Cart Information, but a proper positive or negative fee, that shows up as it’s own line in the Cart. Here’s how you do it.

Before We Begin

This is a coding Tutorial, if you are not well versed using PHP please make sure to have a backup of your Website and do not try this on a live-website.

I always test-run on a local Setup using XAMPP, but use whatever test environment you have.

If you are working on a Theme that is not custom-built by you, I recommend doing this in a Child-Theme

We are working in the functions.php file today. I put a whole site down in my first week as an intern because I forgot a semicolon, so just bear that in mind, if you are rather new to this (I know most of my readers aren’t big coders, but you can do it, guys!)

What will we be doing?

I personally wrote this Code to look up how many items of my Brush category are in the cart and then automatically apply a percentage discount to the total of all the items (which might not necessarily be the total of the Cart).

This Code is however not restricted to Discounts, you can add Fees as well. For example if you need to charge an extra fee because the total is lower to some value etc. The possibilites are basically endless.

In this tutorial I’ll be programming the following:

  • if under 3 matching items are bought, apply a fee of $5
  • if 3 matching items are added, add a discount of 5%
  • if 4 or more matching items are added, add a discount of 10%

The Setup

I setup a page where I downloaded WordPress and Setup a Woocommerce Installation, Using the Storefront Theme and a Child-Theme

I created items of 2 categories, one is the non-matching category, which should have no effect on our Discounts and Fees, and one is our matching Category which we’ll be taking a closer look at. (I priced each at $100 dollars to make it easier)

I created 6 items in our matching Category and 1 of the non-matching just for demonstration purposes.

The Helper Functions

Again, we are working in the functions.php file only, so all of the Code we’re writing will be placed there.

To make this as flexible as possible, I started out by trying to come up with a function, where I can pass Test-Conditions with variable operators. I didn’t actually think it would be a big deal, but after some research I found that passing Operators seems to be a pain, and the only actual solid solution seems to be using a switch.

This Function basically takes a string-based condition of two values and the operator and returns a proper condition to be used inside an if. My examples don’t use all the Operators, I still include them all in case I change my mind or need them at a later point.

/*
 * Return Test Conditions
 * ----------------------
 * runs through string based condition statemens and returns actual condition 
 * to be used in an if() condition test
 */
function test_condition( $val1, $operator, $val2 ){
    switch ( $operator ) {
        case '<':
            return $val1 < $val2
            break;
        case '<=':
            return $val1 <= $val2;
            break;
        case '>':
            return $val1 > $val2;
            break;
        case '>=':
            return $val1 >= $val2;
            break;
        case '==':
            return $val1 == $val2;
            break;
        case '!=':
            return $val1 != $val2;
            break;
        default:
            return false;
    }
    return false;
}

Next we’ll need to be able to calculate the total of items in the matching category. Depending on how you are working with your fees and discounts, you may want to count the Items by Quantity or just count the amount of different items.

I’m using the Version where I just go with the number of different items but you do what suits your needs. You could combine these and make the quantity and item count a variable, but since I’m assuming most of you will need either one or the other, I suggest you just use one of them.

/**
 * Cart Category Count
 *
 * Get the Number of Items in the Cart matching a Product Category
 *
 * @param integer $cat_id Product Category ID
 *
 * @return Number of Items
 */
function cart_cat_item_count( $cat_id ) {
    $cat_item_count = 0;
    foreach( WC()->cart->get_cart() as $cart_item )
        if( has_term( $cat_id, 'product_cat', $cart_item['product_id']))
            $cat_item_count += 1;
    return  $cat_item_count;
}

/**
 * Cart Category Quantity Count
 *
 * Get the total Quantity of Items in the Cart matching a Product Category
 *
 * @param integer $cat_id Product Category ID
 *
 * @return Quantity of Items
 */
function cart_cat_quantity_count( $cat_id ) {
    $cat_quantity_count = 0;

    foreach( WC()->cart->get_cart() as $cart_item )
        if( has_term( $cat_id, 'product_cat', $cart_item['product_id'] ) )
            $cat_quantity_count += $cart_item['quantity'];

    return  $cat_quantity_count;
}

If we are working with percentage based Discounts that just apply to our matching items, we’ll need an additional function that calculates the total of all of our items.

/**
 * Cart Category Items Total
 *
 * Get the total value of all Cart Items in matching category
 *
 * @param integer $cat_id Product Category ID
 *
 * @return Total Value of Category Items
 */
function cat_cart_total( $cat_id ) {
    $cat_total = 0;
    
    foreach( WC()->cart->get_cart() as $cart_item )
        if( has_term( $cat_id, 'product_cat', $cart_item['product_id'] ) )
            $cat_total += $cart_item['data']->get_price();

    return  $cat_total;
}

The Actual Fee and Discount Part

Okay, now we are ready to have some fun!

The idea behind this function is, to create an array for every type of discount, where we would be able to enter Data and then iterate through all of those items and do the whole math and all.

/**
 * Custom Discount and Fees
 *
 */
function custom_discount_and_fees() {
  global $woocommerce;

    global $woocommerce;
    $discounts_and_fees = array(
        // Items Arrays in here
    );

    foreach ( $discounts_and_fees as $c ){
        if ( test_condition( cart_cat_item_count($c['cat_id']), $c['operator'], $c['apply_count'] ) ) {
            if( $c['percentage'] ){
                $total = cat_cart_total( $c['cat_id'] );
                $charge = $total * ( $c['percentage'] / 100 );
            } else{
                $charge = $c['value'];
            }
            if( $c['type'] == 'discount'){
                $charge = $charge * -1;
            }
            $woocommerce->cart->add_fee( $c['name'], $charge, true, '' );
        }
    }
}

Let’s have a bit of a closer look at the last part of that, the foreach loop. We have 3 conditions in our case which only need to be applied, if the item count of or the quantity count match a certain criteria, I decided to call this the apply count.

Then we have value based Fees and Discounts and Percentage based. If we want to use percentage, we need to get the total of the items and multiply it with our percentage to get the right fraction.

Since we do have Fees (adding value to the cart) and Discounts (removing value from the cart, thus creating a negative fee) we might have to multiply our entire charge with negative one to turn it into a discount. So for every Discount or Fee, we need to provided that information in an array.

Which brings me back to the Conditions and how we’d add them to make the function work.

  • if under 3 matching items are bought, apply a fee of $5
  • if 3 matching items are added, add a discount of 5%
  • if 4 or more matching items are added, add a discount of 10%
/**
 * Custom Discount and Fees
 *
 */
function custom_discount_and_fees() {
  global $woocommerce;

    global $woocommerce;
    $discounts_and_fees = array(
        array(
            'cat_id' => 15,
            'operator' => '<',
            'apply_count' => 3,
            'name' => 'Custom Fee ($5)',
            'type' => 'fee',
            'value' => 5,
        ),
        array(
            'cat_id' => 15,
            'operator' => '==',
            'apply_count' => 3,
            'name' => '3 Item Discount (5%)',
            'type' => 'discount',
            'percentage' => 5,
        ),
        array(
            'cat_id' => 15,
            'operator' => '>=',
            'apply_count' => 4,
            'name' => '4+ Items Discount (10%)',
            'type' => 'discount',
            'percentage' => 10,
        ),
    );

    foreach ( $discounts_and_fees as $c ){
        if ( test_condition( cart_cat_item_count($c['cat_id']), $c['operator'], $c['apply_count'] ) ) {
            if( $c['percentage'] ){
                $total = cat_cart_total( $c['cat_id'] );
                $charge = $total * ( $c['percentage'] / 100 );
            } else{
                $charge = $c['value'];
            }
            if( $c['type'] == 'discount'){
                $charge = $charge * -1;
            }
            $woocommerce->cart->add_fee( $c['name'], $charge, true, '' );
        }
    }
}

And then all that’s left is hook it into Woocommerce and we’re done.

// Add custom Discounts
add_action( 'woocommerce_cart_calculate_fees','custom_discount_and_fees' );

And then you’ll be seeing your Discounts based on the Items in the Cart

I hope this was helpful and if you have any questions left, feel free to ask!