Create a Cart Price Rule and generate Coupon Codes programmatically

Magento offers a marketing feature called Cart Price Rules, which allows for the application of discounts to items in the shopping cart based on a set of conditions. The discount can be applied automatically as soon as the conditions are met, or when the customer enters a valid coupon code. Coupon codes can be generated for each cart price rule via the admin panel in Marketing -> Cart Price Rulessection. However, sometimes we need to automate this process. Let’s find out how to create a cart price rule and generate coupon codes programmatically in Magento 2.

First of all, we create a service class intended to generate coupon codes using the default coupon generator (\Magento\SalesRule\Model\CouponGenerator). This service can be reused in the future if we want to generate coupon codes by scheduling and running Magento 2 CRON job. Or we can even additionally create a new CLI command which allows to generate a number of codes for a specified cart price rule by simply calling the \Atwix\CartCouponSample\Service\GenerateCouponCodesService::execute method.

<?php
/* File: app/code/Atwix/CartCouponSample/Service/GenerateCouponCodesService.php */
namespace Atwix\CartCouponSample\Service;

use Magento\SalesRule\Model\CouponGenerator;

/**
 * Class GenerateCouponListService
 */
class GenerateCouponCodesService
{
    /**
     * Coupon Generator
     *
     * @var CouponGenerator
     */
    protected $couponGenerator;

    /**
     * GenerateCouponCodesService constructor
     *
     * @param CouponGenerator $couponGenerator
     */
    public function __construct(CouponGenerator $couponGenerator)
    {
        $this->couponGenerator = $couponGenerator;
    }

    /**
     * Generate coupon list for specified cart price rule
     *
     * @param int|null $qty
     * @param int|null $ruleId
     * @param array $params
     *
     * @return void
     */
    public function execute(int $qty, int $ruleId, array $params = []): void
    {
        if (!$qty || !$ruleId) {
            return;
        }

        $params['rule_id'] = $ruleId;
        $params['qty'] = $qty;

        $this->couponGenerator->generateCodes($params);
    }
}

Then, we implement a setup service class for creating a cart price rule and assigning the coupon codes to the newly created rule. We transferred this logic to a dedicated setup service class to adhere to the single responsibility principle and avoid storing large amounts of logic in the install script.

<?php
/* File: app/code/Atwix/CartCouponSample/Setup/SetupService/CreateCartPriceRuleService.php */
namespace Atwix\CartCouponSample\Setup\SetupService;

use Atwix\CartCouponSample\Service\GenerateCouponCodesService;
use Exception;
use Magento\Backend\App\Area\FrontNameResolver as BackendFrontNameResolver;
use Magento\Customer\Model\ResourceModel\Group\Collection as CustomerGroupCollection;
use Magento\Customer\Model\ResourceModel\Group\CollectionFactory as CustomerGroupCollectionFactory;
use Magento\Framework\App\State as AppState;
use Magento\SalesRule\Api\Data\RuleInterface;
use Magento\SalesRule\Api\Data\RuleInterfaceFactory;
use Magento\SalesRule\Api\RuleRepositoryInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Class CreateCartPriceRuleService
 */
class CreateCartPriceRuleService
{
    /**
     * The initial number of generated coupon codes
     */
    const INITIAL_COUPON_CODES_QTY = 1000;

    /**
     * Coupon Code Length
     */
    const LENGTH = 10;

    /**
     * Coupon Code Prefix
     */
    const PREFIX = 'TEST-';

    /**
     * Generate Coupon Codes Service
     *
     * @var GenerateCouponCodesService
     */
    protected $generateCouponCodesService;

    /**
     * Rule Repository
     *
     * @var RuleRepositoryInterface
     */
    protected $ruleRepository;

    /**
     * Store Manager
     *
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * Catalog Price Rule Factory
     *
     * @var RuleInterfaceFactory
     */
    protected $cartPriceRuleFactory;

    /**
     * Customer Group Collection Factory
     *
     * @var CustomerGroupCollectionFactory
     */
    protected $customerGroupCollectionFactory;

    /**
     * App State
     *
     * @var AppState
     */
    protected $appState;

    /**
     * CreateCartPriceRuleService constructor
     *
     * @param GenerateCouponCodesService $generateCouponCodesService
     * @param RuleRepositoryInterface $ruleRepository
     * @param StoreManagerInterface $storeManager
     * @param AppState $appState
     * @param RuleInterfaceFactory $cartPriceRuleFactory
     * @param CustomerGroupCollectionFactory $customerGroupCollectionFactory
     */
    public function __construct(
        GenerateCouponCodesService $generateCouponCodesService,
        RuleRepositoryInterface $ruleRepository,
        StoreManagerInterface $storeManager,
        AppState $appState,
        RuleInterfaceFactory $cartPriceRuleFactory,
        CustomerGroupCollectionFactory $customerGroupCollectionFactory
    ) {
        $this->generateCouponCodesService = $generateCouponCodesService;
        $this->ruleRepository = $ruleRepository;
        $this->storeManager = $storeManager;
        $this->appState = $appState;
        $this->cartPriceRuleFactory = $cartPriceRuleFactory;
        $this->customerGroupCollectionFactory = $customerGroupCollectionFactory;
    }

    /**
     * Create cart price rule and generate coupon codes
     *
     * @return void
     *
     * @throws Exception
     */
    public function execute()
    {
        $customerGroupIds = $this->getAvailableCustomerGroupIds();
        $websiteIds = $this->getAvailableWebsiteIds();

        /** @var RuleInterface $cartPriceRule */
        $cartPriceRule = $this->cartPriceRuleFactory->create();

        // Set the required parameters.
        $cartPriceRule->setName('Sample Cart Price Rule');
        $cartPriceRule->setIsActive(true);
        $cartPriceRule->setCouponType(RuleInterface::COUPON_TYPE_SPECIFIC_COUPON);
        $cartPriceRule->setCustomerGroupIds($customerGroupIds);
        $cartPriceRule->setWebsiteIds($websiteIds);

        // Set the usage limit per customer.
        $cartPriceRule->setUsesPerCustomer(1);

        // Make the multiple coupon codes generation possible.
        $cartPriceRule->setUseAutoGeneration(true);

        // We need to set the area code due to the existent implementation of RuleRepository.
        // The specific area need to be emulated while running the RuleRepository::save method from CLI in order to
        // avoid the corresponding error ("Area code is not set").
        $savedCartPriceRule = $this->appState->emulateAreaCode(
            BackendFrontNameResolver::AREA_CODE,
            [$this->ruleRepository, 'save'],
            [$cartPriceRule]
        );

        // Generate and assign coupon codes to the newly created Cart Price Rule.
        $ruleId = (int) $savedCartPriceRule->getRuleId();
        $params = ['length' => self::LENGTH, 'prefix' => self::PREFIX];
        $this->generateCouponCodesService->execute(self::INITIAL_COUPON_CODES_QTY, $ruleId, $params);
    }

    /**
     * Get all available customer group IDs
     *
     * @return int[]
     */
    protected function getAvailableCustomerGroupIds()
    {
        /** @var CustomerGroupCollection $collection */
        $collection = $this->customerGroupCollectionFactory->create();
        $collection->addFieldToSelect('customer_group_id');
        $customerGroupIds = $collection->getAllIds();

        return $customerGroupIds;
    }

    /**
     * Get all available website IDs
     *
     * @return int[]
     */
    protected function getAvailableWebsiteIds()
    {
        $websiteIds = [];
        $websites = $this->storeManager->getWebsites();

        foreach ($websites as $website) {
            $websiteIds[] = $website->getId();
        }

        return $websiteIds;
    }
}

The following steps are being performed by \Atwix\CartCouponSample\Setup\SetupService\CreateCartPriceRuleService::execute method:

  1. Create cart price rule object using factory.
  2. Set the required data and additional configurations to the cart price rule object. We also use the coupon type “specific coupon” and set “use auto generation” flag in order to make the multiple coupon codes generation possible.
  3. Save the Cart Price Rule using corresponding repository. Note, in this case we need to set the area code due to the existent implementation of RuleRepository. The specific area need to be emulated while running the RuleRepository::save method from CLI in order to avoid the corresponding error (“Area code is not set”).
  4. Generate and assign coupon codes to the newly created Cart Price Rule using Atwix\CartCouponSample\Service\GenerateCouponCodesService.

Now, let’s create the data install script for our module:

<?php
/* File: app/code/Atwix/CartCouponSample/Setup/InstallData.php */
namespace Atwix\CartCouponSample\Setup;

use Atwix\CartCouponSample\Setup\SetupService\CreateCartPriceRuleService;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * Class InstallData
 */
class InstallData implements InstallDataInterface
{
    /**
     * Create Cart Price Rule Service
     *
     * @var CreateCartPriceRuleService
     */
    protected $createCartPriceRuleService;

    /**
     * InstallData constructor
     *
     * @param CreateCartPriceRuleService $createCartPriceRuleService
     */
    public function __construct(CreateCartPriceRuleService $createCartPriceRuleService)
    {
        $this->createCartPriceRuleService = $createCartPriceRuleService;
    }

    /**
     * @inheritdoc
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $this->createCartPriceRuleService->execute();
    }
}

By the way, if you are wondering why we don’t use the startSetup and endSetup, please, feel free to check our recent blog post: When and why we need to use start/end setup methods?.

Once the data upgrade is performed you will be able to check the results in the admin panel.

Generate coupon codes for price rule programmatically

The full module’s source code can be found in Atwix_CartCouponSample GIT repository.

Don’t hesitate to share your ideas in the comments. Thanks for reading!