There are some cases when you might need to add a fee to the cart totals. Sometimes only if a certain condition is met, for example depending on the cart totals or shipping location. With WooCommerce adding a fee is pretty easy – much easier than adding a discount in fact. In this post we’ll learn how to add a custom fee to WooCommerce.

WooCommerce has a built-in function in the cart object for adding fees. All you need is to hook onto the right action, and with the provided cart object call a function to add a fee. WooCommerce will automatically display fees in the cart and checkout totals. You decide the fee’s label and amount.

It’s important to note that the fee amount cannot be negative – thus giving the customer a discount. This “hack” used to work before in older WooCommerce versions, but not anymore.

Let’s get into the details of the code! The only hook you need to be concerned about is woocommerce_cart_calculate_fees. Inside this hook you can get ahold of the cart object by calling WC()->cart. With that object you can call add_fee() which accepts four parameters (you probably only need the two first); add_fee($fee_name, $amount_in_float, $taxable_boolean, $tax_class_string). The first two are self-explanatory. The third parameter is a boolean that decides if the fee is taxable or not. And the fourth is for providing a tax class for the fee (if the third parameter is true). But it can be left blank for standard tax class.

Adding a fixed fee

Adding a custom fee is done in its simplest form like this:

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}
	WC()->cart->add_fee(__('A small fee', 'txtdomain'), 5);
});

The first step is preventing adding a fee if we’re in admin or if Ajax is in progress. Next we call add_fee() onto the cart object. This will add a fee named “A small fee” with the value of 5. The value will be in whatever currency you have in your store. If the store’s currency is set to dollars, it becomes $5. Adjust the name and amount to your needs.

Adding a fee as a percentage of the cart totals

If you want the fee amount to be a percentage of the cart totals, you just need to calculate it differently. Remember we always have access to the cart object by calling WC()->cart and from that we can fetch the totals.

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}
	
	$percentage = 0.05;  // Percentage (5%) in float
	$percentage_fee = (WC()->cart->get_cart_contents_total() + WC()->cart->get_shipping_total()) * $percentage;

	WC()->cart->add_fee(__('A small fee', 'txtdomain'), $percentage_fee);
});

The above code will add a fee that amounts to 5% of the cart totals, which is the cart items total (get_cart_contents_total()) plus the cart’s shipping cost (get_shipping_total()). Adjust the rate and which totals to include or exclude into what you need.

So this is pretty nice, but in some cases we might want to customize when to add a fee. Maybe you want to add a fee only for a certain shipping method, payment method, or based on the cart’s total. The process is the same, we just add the fee only if our conditions are met.

Adding a fee depending on the cart totals

Say you wanted to only add a fee if the cart totals were under a certain amount. Assume you want to add a fee for handling small amounts. You could do it like so:

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}

	$cart_total = WC()->cart->get_cart_contents_total();  // This is excluding shipping
	if ($cart_total < 500) {
		WC()->cart->add_fee(__('Fee for small transactions', 'txtdomain'), 50);
	}
});

Use WC()->cart->get_cart_contents_total() to get the float number of the cart total, excluding shipping. (If you want to include shipping as well, look above in adding percentage fee for how to get shipping totals). Then do your comparisons.

You could also flip it around and decide to add a fee if the cart was over a certain limit.

Adding a fee depending on shipping location

You can add a fee depending on the shipping location as well, for example by country. Similarly to getting cart object, we can use WC()->customer for the customer object. Keep in mind that when the customer is not logged in and not yet arrived to the checkout and filled in his or her address, the values of the customer object will be empty or the defaults set in your store.

You’ll need to know the country code – here is a nice country code reference. Here is an example of adding a fee if the customer is from Norway.

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}

	$shipping_country = WC()->customer->get_shipping_country();	
	if ($shipping_country == 'NO') {
		WC()->cart->add_fee(__('Fee for shipping to Norway', 'txtdomain'), 50);
	}
});

Adding a fee depending on chosen shipping method

Getting shipping method is a little trickier since it depends on the user’s session and doesn’t come in a “nice understandable format”. You can get ahold of the current chosen shipping method with WC()->session->get('chosen_shipping_methods'). Note that it says “methods” and not “method”, so this returns an array. Normally the array contains one element, where each element consists of strings of shipping ID, a colon, and an ID after it. I recommend using PHP string methods to check if the string contains the ID – for example strpos() – and not if it’s equal.

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}

	$chosen_shipping_method = WC()->session->get('chosen_shipping_methods');

	if (strpos($chosen_shipping_method[0], 'flat_rate') !== false) {
		WC()->cart->add_fee(__('Fee for flat rate shipping', 'txtdomain'), 50);
	}
});

Adding a fee depending on chosen payment method

If you wanted to add a fee depending on which payment gateway was chosen, you can get ahold of the current chosen payment gateway with WC()->session->get('chosen_payment_method') and do a comparison to it’s name (e.g. for Paypal it would return 'paypal'). However you might notice that when the customer is switching between payment gateways in checkout the fee is not dynamically added or removed. So you will need to add some additional Javascript to ensure that WooCommerce updates the cart so that your fee works consistently.

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}

	$chosen_payment_method = WC()->session->get('chosen_payment_method');
	if ($chosen_payment_method == 'paypal') {
		WC()->cart->add_fee(__('Paypal Fee', 'txtdomain'), 50);
	}
});

add_action('woocommerce_review_order_before_payment', function() {
    ?><script type="text/javascript">
        (function($){
            $('form.checkout').on('change', 'input[name^="payment_method"]', function() {
                $('body').trigger('update_checkout');
            });
        })(jQuery);
    </script><?php
});

For adding the Javascript we hook onto woocommerce_review_order_before_payment which occurs right before the payment box in checkout and output inline script. All the code does is triggering WooCommerce’s hook update_checkout whenever the payment method choice is changed. This ensures that all necessary PHP methods and hooks are run (including the fee hook) each time the cart is updated.

Adding a fee depending on which products are in cart

I’ll mentioned it before, but I’ll mention it again: You have full access to the cart object with WC()->cart, so you could search the cart contents for a specific product ID and add a fee if it exists in the cart.

Using WC()->cart->get_cart_contents() returns an array of all products in cart. You can either loop through this with a simple foreach loop and check each product to your desired condition, or if you simply want to check for IDs you can extract all product IDs in one line like so:

$products_in_cart = WC()->cart->get_cart_contents();
$product_ids_in_cart = array_column(array_values($products_in_cart), 'product_id');
// Now you can use e.g. in_array() to check for certain product IDs.

The example below shows how you can add a fee depending on the cart contains a product of a certain ID:

add_action('woocommerce_cart_calculate_fees', function() {
	if (is_admin() && !defined('DOING_AJAX')) {
		return;
	}

	$really_expensive_product_id = 12;  // product ID that triggers a fee

	$products_in_cart = WC()->cart->get_cart_contents();
	$product_ids_in_cart = array_column(array_values($products_in_cart), 'product_id');
	if (in_array($really_expensive_product_id, $product_ids_in_cart)) {
		WC()->cart->add_fee(__('Fee for really expensive product', 'txtdomain'), 50);
	}
});

You can also use similar operations for extracting quantities if you need to add a fee when a customer adds a really large quantity of something.

In conclusion

This post has shown you how to add a custom fee, and through five examples how to add a fee depending on certain conditions. These conditions were all I have encountered and could think of, and they should at the very least give hints on how to get ahold of information to further customize your conditions. Let me know if they worked for you, or if you have found another condition I did not think of!