Lately, I am trying to tweak Magento 2 Cart Price Rule feature for our business requirements and I realized something. First of all, I need to say that we override Magento\SalesRule\Model\Rule\Condition\Product\Subselect::loadAttributeOptions() because in our location we must apply the discount on top of the tax included amount so we changed the 'base_row_total' with the 'base_row_total_incl_tax'. Here is the scenario;
I have 2 Cart Price Rule;(₺ = currency)
50% discount for items in the cart, with cart condition; amount > 250₺, priority 0
20% discount for items in the cart, with cart condition; amount > 200₺, priority 1
I have a cart with 1 item with a total of 300₺ and let's say that shipping is free. Our requirements say that; it should apply the first cart price rule and should not apply the second cart price rule because after applying the first cart price rule cart total is going down to 150₺ thus second cart price rule's condition is not met from now on.
If we go back to the reality Magento validates conditions by the product's attributes; Magento always looks for the same value, not the discounted fresh value.
Magento\SalesRule\Model\Rule\Condition\Product::validate($model) declares that;
$total += ($hasValidChild && $useChildrenTotal) ?
$childrenAttrTotal * $item->getQty() : $item->getData($attr);
// $attr value is "base_row_total_incl_tax" for our case, and it does not change after every rule process step
return $this->validateAttribute($total); // this func just makes a logical comparison with boolean return
So, what do you think about this topic? Has anyone thought about it, should I just change the $total with the latest discount applied cart amount(using registry) or does anyone have a better idea?
Related
The goal is to have two promotions:
Promotion without a code => gives a 3% discount if the selected Payment Method is "Invoice/Vorkasse".
Second promotion is with a code, absolute 10€ discount, with two rules:
a) total value of the cart is bigger than 49,99 €
b) at least one product in the cart is not discounted product (with the list price).
Additional explanation:
That means that the promotion should be valid if at least one product in the cart is not discounted product, and total value of the cart is bigger than 49,99€. In that case, a fixed absolute 10€ discount is added. Promotion should be invalid if all products in the cart are discounted products (with the list price) or if total value of the cart is less than 49,99 €.
What have I done so far:
I have created a promotion that works correctly (second promotion with a code) by itself, but if I log in as a customer with selected 'Invoice' as a payment method, the first promotion is applied directly, and from that point, validation seems to fail, and second promotion becomes valid if code is entered.
How to replicate the behavior:
Clean Shopware environment and add list price to one product
Create two rules in Rule Builder:
a) Invoice 3% => Applied payment method > is one of > Invoice
b) Min. 49.99 & without disc.:
- Total > is greater than > 49.99
- Item with price/list price percentage ratio > At least one > is empty
Create two new Promotions
a) Invoice 3%:
- General => active, no code
- Conditions => Sales Channels: Storefront, Shopping cart rules: Invoice 3%
- Discounts => Apply to cart, percentage, 3
b) 10 Discount
- General => Max. uses per customer: 1, active, fixed promotion code: test10
- Conditions => Sales Channels: Storefront, Shopping cart rules: Min. 49.99 & without disc
- Discounts => Apply to cart, Absolute, 10
Add discounted product to the cart (with list price)
Add code
Question:
I was unable to find where the validation could possibly fail if another promotion is present.
Can anyone help me and point me to where the bug could be?
I was able to reproduce the issue. I was able to narrow the cause down to the list price condition. The percentual promotion adds a discount line item with the negative amount. Currently the condition for the list price checks all line items, including discounts, for their list price. A discount, not being a product, has no list price. Hence why the condition is met and the second promotion can be applied. This will be fixed with the upcoming major release 6.5 where conditions obviously meant to only regard products, will no longer match discount line items.
In the meantime you can use a filter condition to check that there is at least one actual product in the cart, that has no list price. When you choose the condition for total quantity of products there's a filter icon on the right. Click it and a modal will open. Within the modal then add the condition for the list price.
I'm trying to take account of the package weight in the total weight of the cart so I can bill the right shipping cost.
I tried adding a filter on 'woocommerce_cart_contents_weight' like explained there
So I added the following code snippet
function add_package_weight_to_cart_contents_weight( $weight ) {
$weight = $weight * 1.3; // add 30%
return $weight;
}
add_filter('woocommerce_cart_contents_weight', 'add_package_weight_to_cart_contents_weight');
I've also added a snippet to print cart weight on cart page, and I can see that the weight filter isn't applied : The total weight of the cart doesn't change wether the snippet is active or not.
I also tried putting exactly 900g articles in the cart so that, with the box weight, it goes over 1kg and change price, but the shipping price is still the one for under 1kg.
Any idea on why it isn't applied and how I could fix it ?
Your code works and only affects WC_Cart method get_cart_contents_weight() usage.
Now for shipping methods, as cart items can be divided into packages, the weight is calculated for each shipping package and doesn't use WC_Cart method get_cart_contents_weight(). Instead it will make a calculation on the cart items for each package.
A possible way should be to use the filter hook woocommerce_package_rates to alter cost based on the package weight calculation. But this is going to be much more complicated as it seems that you are using 3rd party plugins.
I wanted to implement a buy 3 free 1 feature, so I wrote a script that detect whether customer has 3 same items in cart and automatically add 1 more same item to the cart. Then using another hook, I overwrite the price of the product to 0.
I googled the solution and used the same approach found on:
WooCommerce: Add product to cart with price override?
woocommerce add custom price while add to cart
Here is the code sample:
function setGiftPriceToZero($cart_object){
foreach($cart_object->cart_contents as $k=>$item):
if(isset($item['variation']['promo']) && ($item['variation']['promo']) == 'buy 3 free 1'):
$item['data']->price = 0;
endif;
endforeach;
}
add_action('woocommerce_before_calculate_totals', 'setGiftPriceToZero');
When Woocommerce calculate the subtotal for the cart, it always add in the original price of the product that is supposed to be free. For example, when I added 3 $100 item to cart, the cart subtotal ends up with $400 instead of $300.
I digged deeper into the Woocommerce code and found that in https://docs.woocommerce.com/wc-apidocs/source-class-WC_Cart.html#1139, $item['data']->get_price() is used which always return the original price for the item.
Is there anyway to fix this using hooks/apis instead of editing Woocommerce core file?
I have found the culprit of this error. It's caused by conflict from another plugin called Woocommerce Role Based Price. This plugin overwrite the cart item price at the end of the cart total calculation flow. This is why get_price() function always return the specified price for the item.
Now I only have to edit the plugin file so that it plays nicely with my logic.
I need to know if there is a way to create this in Magento:
First of all, all my products price are fixed: 9.99$.
So i want to do this: if the client bought three products, the third is free, so it pays 2 products. He can do this as many times as they want, so if you buy 6 products, there will be 2 product free, if he buys 9 products, 3 are free
Magneto supports buy x get y free promotions. Information about setting them up is available at http://www.magentocommerce.com/knowledge-base/entry/how-to-setup-buy-x-get-y-free
It's possible to do this using magento Shoping Cart Price rules.
Create a Shoping cart price rule by going to your admin panel and clicking on Promotions -> Shoping Cart Price rules.
Create a condition for your rule. There is a nice GUI to define the conditions so they will look like: If an item is FOUND in the cart with ALL of these conditions true
Define an Action, actions describe how prices are updated when the conditions of the rule are met. In your case there is an specific action called: Buy X get Y Free, which defines a quantity that the customer must purchase to receive a quantity for free. (The Discount Amount is Y.)
Complete the labels: lables appears on the order below the subtotal to identify the discount. You can enter a default label for all store views, or enter a different label for each view.
Apply the rule. Make sure you save and enable the rule, also you can define a time range when the rule is valid and number of usages.
For more details check here:
http://docs.magento.com/m1/ce/user_guide/marketing/price-rule-shopping-cart-buy-x-get-y-free.html
I'm looking to implement some e-commerce functionality that gives discounts when certain quantities are reached. The catch is, its not quantities of one sku, any number of other products in a category can trigger the quantity break when in total they reach the threshold.
So if I have a model class for a Cart_Product lets say, I would typically put the logic for getting the prices in that class as a method. But since other instances of that class in the current cart need to be considered, I'm not sure of the best way to proceed.
Do I call the "owner" Cart instance inside of the Cart_Product get_price method and then add the logic to check for the quantity break? Or is there a better design pattern to use at this step?
First of all, model is not a class or instance. Model is a layer. What you are talking about in your question actually are domain objects (assuming they are not also responsible for saving themselves, which would violate SRP.
As for applying the discount, it depends on whether each product in your cart has a separate discount of the discount is same for all the products:
if each product can have a separate discount, then the logic for that should reside in the Product domain object.
if all products get the same discount, then the discount should affect only the sum total, therefore - compute in the Cart instance.
The logic you have described is a cart-wide feature; since the cart is the logical owner of the products inside, you would implement it there:
class Cart
{
private $products; // Cart_Product[]
// ...
function calculateDiscount()
{
$totalQuantity = array_reduce($this->products, function($sum, $product) {
return $sum + $product->getQuantity();
}, 0);
if ($totalQuantity > 10) {
$this->cartDiscount = 25; // apply 25% discount on the cart
} else {
$this->cartDiscount = 0;
}
}
}
This introduces a separate entity for a global cart discount. If you don't want that, you would have to apply the discount to each individual item.
i just went through something very similar. really the only thing the cart should know about is the product id and the quantity. everything else should be for display purposes only. in other words the product object is always responsible for the price. the only reason that a price is stored in the cart is to help show it in the view. otherwise we assume that the price always has to be checked with any insert or update, to prevent fraud.
here is another scenario - you have a special on shipping, like buy $100 worth of qualifying goods and you get free shipping. there might be a separate shipping special on specific products. the only way to calculate is with all of the cart items.
so my solution - which i am not sure is optimal - is to pass the cart items to a shipping object - do the shipping calculations - optionally add messaging for specific products to display in the cart - and then return the cart items.
otherwise you are having to put shipping methods in the cart class which does not make any sense but maybe there is another way to do this.
here is another scenario - inventory control. someone orders 30 blue widgets but you only have 10 blue widgets. ok you can check for inventory when you insert item in cart. but what if they update the cart and then increase to 30? that means that we have to check inventory - for every item in the cart - every time the cart is updated. and if we are doing that then might as well get the price in case it has gone up or down.
so i take the cart items - and pass them to a product object - which checks inventory - and if necessary reduces the quantity of the items down to current inventory - optionally adds messaging explaining that stock is limited - then passes back to cart object.
finally - suggest that you have an object that owns the shopping session. and then thats where the totals would be. that way the cart is never in charge of totals - its just a container. one way is you just start an order and then store the different totals there.