For an e-commerce business to flourish, it is quite necessary to inculcate effective marketing strategies that lay a strong influence on the customers.
Having said this, Magento 2 Special Promotions and discounts are one of the most renowned marketing strategies.
As you know Magento 2 provides us with four types of cart rules.
In this blog, We will learn how can we add a custom cart rule (Buy X Get next Y with percent discount)
Let’s start step by step.
Note:- You must have installed your custom Module on which you are going to implement this.
Step 1: Create file Webkul\CartRule\etc\adminhtml\di.xml
<?xml version="1.0"?> <!-- /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\SalesRule\Model\Rule\Metadata\ValueProvider"> <plugin name="salesrule-plugin" type="Webkul\CartRule\Plugin\Rule\Metadata\ValueProvider" sortOrder="1" /> </type> </config>
Step 2: We need to create the ValueProvider plugin file which add a custom cart rule option in the cart rules dropdown. Webkul\CartRule\Plugin\Rule\Metadata\ValueProvider.php
<?php /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ namespace Webkul\CartRule\Plugin\Rule\Metadata; class ValueProvider { public function afterGetMetadataValues( \Magento\SalesRule\Model\Rule\Metadata\ValueProvider $subject, $result ) { $applyOptions = [ 'label' => __('Custom'), 'value' => [ [ 'label' => 'Buy X Get next Y with M% discount', 'value' => 'buy_x_get_next_y_with_percent', ] ], ]; array_push($result['actions']['children']['simple_action']['arguments']['data']['config']['options'], $applyOptions); return $result; } }
After adding the above code to your custom module. Deploy the code and see the result.
Step 3: Create Webkul/CartRule/view/adminhtml/ui_component/sales_rule_form.xml file to add custom fields in the Cart price rule form.
<?xml version="1.0" encoding="UTF-8"?> <!-- /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> <fieldset name="actions" sortOrder="30"> <field name="custom_step_nqty" formElement="input"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="source" xsi:type="string">sales_rule</item> <item name="sortOrder" xsi:type="number">4</item> </item> </argument> <settings> <validation> <rule name="required-entry" xsi:type="boolean">true</rule> <rule name="validate-number" xsi:type="boolean">true</rule> <rule name="validate-zero-or-greater" xsi:type="boolean">true</rule> </validation> <dataType>text</dataType> <label translate="true">Discount Qty (Buy Y)</label> <dataScope>custom_step_nqty</dataScope> </settings> </field> <field name="promo_skus" formElement="textarea"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="source" xsi:type="string">sales_rule</item> <item name="notice" xsi:type="string" translate="true">Enter Y products separated by comma. Enter All SKU(s) if product is Customisable.</item> </item> </argument> <settings> <dataType>number</dataType> <label translate="true">Promo SKU</label> <dataScope>promo_skus</dataScope> </settings> </field> </fieldset> </form>
You will see the result in the cart price rule :
Discount Qty (Buy Y): Set quantity for Y products eligible for discount.
Promo SKU: This field defines the Y products, Enter Y products SKU separated by comma. Enter All SKU(s) if the product is Customisable.
Note:- Non-Promo products SKU are defined as X products.
Step 4: Create a layout file Webkul/CartRule/view/adminhtml/layout/sales_rule_promo_quote_edit.xml to add a template file.
<?xml version="1.0"?> <!-- /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="head.components"> <block class="Magento\Framework\View\Element\Js\Components" name="sales_custom_rule_form_page_head_components" template="Webkul_CartRule::promo/customrulejs.phtml"/> </referenceBlock> </body> </page>
Step 5: Create a template Webkul/view/adminhtml/templates/promo/customrulejs.phtml file for managing the custom field’s appearance.
<?php /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ ?> <script> require([ 'jquery', "uiRegistry" ], function($, registry) { $(document).ready(function() { $('body').on('click', ".fieldset-wrapper-title", function() { $('body select[name=simple_action]').trigger('change'); }); $('body').on('change', 'select[name=simple_action]', function() { $('body input[name=discount_amount]').parent().parent() .find('label span').text("<?= /* @noEscape */ __('Discount Amount'); ?>"); if ($(this).val() == 'buy_x_get_next_y_with_percent') { let discountAmount = Math.min(100, $('body input[name=discount_amount]').val()); discountAmount = discountAmount ? discountAmount : 1; let customRuleElementNode = $('body select[name=simple_action] option[value="buy_x_get_next_y_with_percent"]'); let customRuleLabel = customRuleElementNode.text().replace('M%', `${discountAmount}%`); customRuleElementNode.text(customRuleLabel); $('body textarea[name=promo_skus]').parent().parent().show(); $('body input[name=discount_amount]').parent().parent().find('label span') .text("<?= /* @noEscape */ __('Discount Amount (in %)'); ?>"); $('body input[name=custom_step_nqty]').parent().parent().show(); } else { $('body input[name=custom_step_nqty]').parent().parent().hide(); $('body textarea[name=promo_skus]').parent().parent().hide(); } }); }); }); </script>
Step 6: Configure declarative schema Webkul/CartRule/etc/db_schema.xml file to save custom field data of the Cart price rule form.
<?xml version="1.0"?> <!-- /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="salesrule" resource="default"> <column xsi:type="text" name="custom_step_nqty" nullable="true" comment="N Qty"/> <column xsi:type="text" name="promo_skus" nullable="true" comment="Promotion SKUs"/> </table> </schema>
Step 7: For Calculation of the custom cart rule, create Webkul\CartRule\etc\di.xml file and pass the class as an argument in class Magento\SalesRule\Model\Rule\Action\Discount\Calculator
<?xml version="1.0"?> <!-- /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory"> <arguments> <argument name="discountRules" xsi:type="array"> <item name="buy_x_get_next_y_with_percent" xsi:type="string">\Webkul\CartRule\Model\Rule\Action\Discount\BuyXGetNextYWithPercent</item> </argument> </arguments> </type> </config>
Step 8: Create class Webkul\CartRule\Model\Rule\Action\Discount\BuyXGetNextYWithPercent.php file which is passed as an argument in the above file.
<?php /** * Webkul Software. * * @category Webkul * @package Webkul_CartRule * @author Webkul * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ namespace Webkul\CartRule\Model\Rule\Action\Discount; use Magento\SalesRule\Model\Rule\Action\Discount\AbstractDiscount; class BuyXGetNextYWithPercent extends AbstractDiscount { /** * Calculate Buy X Get next Y with M% discount amount * * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param float $qty * @return \Magento\SalesRule\Model\Rule\Action\Discount\Data */ public function calculate($rule, $item, $qty) { $rulePercent = min(100, $rule->getDiscountAmount()); $discountData = $this->_calculate($rule, $item, $qty, $rulePercent); return $discountData; } /** * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param float $qty * @param float $rulePercent * @return Data */ protected function _calculate($rule, $item, $qty, $rulePercent) { /** @var \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData */ $discountData = $this->discountFactory->create(); /** * Calculation logic here */ $discountStep = $rule->getDiscountStep(); $maxDiscountQty = $rule->getDiscountQty(); $currentDiscountAmount = $rulePercent; $qty = $item->getQty(); $cartSKUs = []; $promoSkus = $rule->getpromoSkus(); $totalQuantity = $item->getQuote()->getItemsQty(); $yProducts = $rule->getData('custom_step_nqty'); $productPriceSKUWise = []; $productQty = []; foreach ($item->getQuote()->getAllVisibleItems() as $currentItem) { if (!$this->validator->canApplyRules($currentItem)) { continue; } $currentItemSku = $currentItem->getSku(); if ($currentItem->getProductType() == 'bundle') { $currentItemSku = $this->getBundleProductSku($currentItem->getId(), $currentItemSku, $item); } $productQty[$currentItemSku] = $currentItem->getQty(); $cartSKUs[] = $currentItemSku; $productPriceSKUWise[$currentItemSku]['price'] = $this->validator->getItemPrice($currentItem); $productPriceSKUWise[$currentItemSku]['base_price'] = $this->validator->getItemBasePrice($currentItem); } $discountAmount = $this->getAmountSkuWise( $currentDiscountAmount, $qty, $maxDiscountQty, $discountStep, $totalQuantity, $yProducts, $productQty, $item, $cartSKUs, $promoSkus, $productPriceSKUWise ); /** Set the discount price in Price **/ $discountData->setAmount($discountAmount['discount']); $discountData->setBaseAmount($discountAmount['base_discount']); $discountData->setOriginalAmount($discountAmount['discount']); $discountData->setBaseOriginalAmount($discountAmount['base_discount']); return $discountData; } /** * Bundle product SKu * * @param int $itemId * @param string $currentItemSku * @param \Magento\Quote\Model\Quote\Item $item * @return string */ public function getBundleProductSku($itemId, $currentItemSku, $item) { foreach ($item->getQuote()->getAllItems() as $currentItem) { if ($currentItem->getParentItemId() == $itemId) { $currentItemSku = str_replace("-" . $currentItem->getSku(), "", $currentItemSku); } } return $currentItemSku; } /** * Get discount amount as per promotional SKU * * @param integer $currentDiscountAmount * @param integer $qty * @param integer $maxDiscountQty * @param integer $discountStep * @param integer $totalQuantity * @param int $yProducts * @param array $productQty * @param \Magento\Quote\Model\Quote\Item $item * @param array $cartSKUs * @param string $promoSkus * @param array $productPriceSKUWise * @return array */ private function getAmountSkuWise( $currentDiscountAmount, $qty, $maxDiscountQty, $discountStep, $totalQuantity, $yProducts, $productQty, $item, $cartSKUs, $promoSkus, $productPriceSKUWise ) { $discountAmount = ['discount' => 0, 'base_discount' => 0]; if (empty($yProducts) || $yProducts == 0) { $yProducts = 1; } $maxDiscountQty = $maxDiscountQty ? $maxDiscountQty : 1; $currentItemSku = $item->getSku(); if ($item->getProductType() == 'bundle') { $currentItemSku = $this->getBundleProductSku($item->getId(), $currentItemSku, $item); } $skus = explode(',', trim($promoSkus) ?? ''); if (!array_diff($cartSKUs, $skus)) { return $discountAmount; } if (in_array($currentItemSku, $cartSKUs) && in_array($currentItemSku, $skus)) { foreach ($skus as $sku) { if (array_key_exists($sku, $productQty)) { $productQtyitem = $productQty[$sku]; if ($item->getProduct()->getTypeId() == 'bundle') { $promoSKUPrice = $this->getBundleProductPrice($sku, $item); } else { $promoSKUPrice = $this->getProductPrice( $sku, $productPriceSKUWise ); } $qty = $totalQuantity - $productQtyitem; if (in_array($sku, $cartSKUs) && $discountStep <= $qty && $yProducts <= $productQtyitem) { if ($currentItemSku == $sku) { $nDiscountQty = min($qty, $productQtyitem, $maxDiscountQty); $discountAmount['discount'] = $discountAmount['discount'] + ($promoSKUPrice['price'] * $currentDiscountAmount / 100) * $nDiscountQty; $discountAmount['base_discount'] = $discountAmount['base_discount'] + ($promoSKUPrice['base_price'] * $currentDiscountAmount / 100) * $nDiscountQty; } } } } } return $discountAmount; } /** * Bundle Product Price * * @param string $sku * @param \Magento\Quote\Model\Quote\Item $item * @return array */ public function getBundleProductPrice($sku, $item) { $price = 0; foreach ($item->getQuote()->getAllItems() as $currentItem) { if ( $currentItem->getProductType() == 'bundle' && $currentItem->getProduct()->getData('sku') == $sku ) { $price = $this->validator->getItemPrice($currentItem);; $basePrice = $this->validator->getItemBasePrice($currentItem); } } return ['price' => $price, 'base_price' => $basePrice]; } /** * Get Product Price * * @param string $sku * @param array $productPriceSKUWise * @return array */ public function getProductPrice($sku, $productPriceSKUWise) { $totalSkuPrice = 0; if (isset($productPriceSKUWise[$sku])) { $totalSkuPrice = $productPriceSKUWise[$sku]; } return $totalSkuPrice; } }
The discount will be applicable only if the product, for which the admin defines the Promo SKU is present in the cart as shown in the image below.
SKU 24-MB01 is mentioned in the promo SKU so the cart considered this product as a Y product and 24-MB05 is a non-promo SKU so the cart considered this product as an X product.
You can also go through the best Magento 2 extensions catalog list.
Hope this will be helpful. Thanks 🙂
Be the first to comment.