For WooCommerce 3.2 we set out to tackle some long-standing issues with our coupon and cart classes to both improve test coverage, and make calculations more stable. For anyone who has looked under the hood at the cart and coupon classes in core you’ll know that they are very deeply coupled and coded in a way which makes writing unit tests very difficult.
To solve this, in 3.2 we’ve introduced a few new classes and refactored our cart.
WC_Cart_Totalsclass takes a cart object, performs calculations, and sets the totals once complete.
WC_Discountsclass takes a cart/order object and lets you apply coupon discounts programmatically, returning the discount amounts.
WC_Cart_Feesclass houses the cart fees API. Interaction with this is unchanged.
WC_Cart_Sessionclass houses the cart sessions code.
We’ve also changed where coupon validation and discount logic is found to make things more reusable and to make coupons less reliant on the global cart;
WC_Discounts handles validation (I.e. is the coupon valid for the given items), and also handles the discount amount calculations.
To maintain backwards compatibility, the old discount methods are left in place (deprecated) and any filters/actions from those methods have been moved to the new methods.
With the new classes, we can now run unit tests on each component, for example, we can pass some items to the
WC_Discounts class and see if it calculates the correct expected totals. The same applies to
WC_Cart_Totals—these are now testable independently from the global
This is great news for everyone; we can now test many more test cases, add new tests if calculations fail for a certain use case, and we can ensure test cases continue to pass making everything much less brittle.
New! Apply discounts outside of the cart
A huge advantage with the new system is that we can now easily apply discounts for non-carts. I’m talking about orders!
Users can now add/remove coupons in the admin backend without doing manual calculations like in older versions.
Adding and removing coupons still tracks usage counts, and handles all calculations for you. This was one of the larger feature requests on the ideas board. ✅
Fees are applied in similar fashion in 3.2; just enter the fee amount (percent or fixed) and it will apply.
We also know that some users are currently exploiting fees to apply negative amounts, so we’ve fixed the way in which taxes get applied in those cases. Negative fees will now apportion taxes between all other items, and will not make the cart total go below $0.
Coupon logic improvements
Rounding can be difficult for coupons, because in most cases, to keep taxes correct we need to split the discount across all items in the cart rather than simply remove an amount from the total.
When you split a discount across the cart, naturally, half cent values could occur which in turn can cause rounding issues once everything is summed up.
To help mitigate this we’ve tweaked the discount logic to only deal with cent values. Once split between items, any remainder is applied one line item at a time until exhausted. The final total will then equal the applied discount total. This is most important for fixed cart discounts.
If you’re interested, you’ll find the code here. Again, this is all covered in unit tests to ensure the logic makes sense!
Ready for testing
The above changes are now live in master and will be part of 3.2. Please give them a test and let us know your feedback!
If you do spot a backwards compatibility issue, please let us know immediately. 3.2 is a minor update so we must keep things backwards compatible at all costs