1

In my custom module I am using a custom customer attribute called disable_proforma_email, and using a plugin to stop the proforma email being sent if it is set to yes. This is working (or at least I thought it was) until I noticed that it is creating duplicate orders. If someone was to order 10 products it creates 1 orders every couple of seconds until it hits the amount of items they ordered originally (so until it creates a 10th order)

I am not sure why this is happening, can anyone help?

etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Sales\Model\Order\Email\Container\OrderIdentity">
        <plugin name="change_is_enable_method" type="XX\BlockProforma\Plugin\Sales\Order\Email\Container\OrderIdentityPlugin"/>
    </type>
</config>

Plugin/Sales/Order/Email/Container/OrderIdentityPlugin.php

<?php

namespace XX\BlockProforma\Plugin\Sales\Order\Email\Container;
use Magento\Customer\Api\CustomerRepositoryInterface;

class OrderIdentityPlugin
{
    /**
     * @var \Magento\Checkout\Model\Session $checkoutSession
     */
    protected $checkoutSession;

    protected $resourceConnection;

    protected $_customer;

    protected $customerRepository;

    /**
     * @param \Magento\Checkout\Model\Session $checkoutSession
     *
     * @codeCoverageIgnore
     */
    public function __construct(
        \Magento\Checkout\Model\Session $checkoutSession,
        \Magento\Framework\App\ResourceConnection $resourceConnection,
        CustomerRepositoryInterface $customerRepository, 
        \Magento\Customer\Model\Customer $customer
    )
    {
        $this->checkoutSession = $checkoutSession;
        $this->resourceConnection = $resourceConnection;
        $this->customerRepository = $customerRepository;
        $this->_customer = $customer;
    }

    /**
     * @param \Magento\Sales\Model\Order\Email\Container\OrderIdentity $subject
     * @param callable $proceed
     * @return bool
     */
    public function aroundIsEnabled(\Magento\Sales\Model\Order\Email\Container\OrderIdentity $subject, callable $proceed)
    {
        $returnValue = $proceed();

        $order = $this->checkoutSession->getLastRealOrder();
        $orderId=$order->getEntityId();  // order id
        
        $connection = $this->resourceConnection->getConnection();
        $sql = "select customer_id FROM sales_order where entity_id = " . $orderId;
        $result = $connection->fetchAll($sql);
        $customerId = null;
        foreach($result as $cid){
            $customerId = $cid["customer_id"];
        }

        $customerGetValue = $this->customerRepository->getById($customerId);
        $dpe = $customerGetValue->getCustomAttribute('disable_proforma_email')->getValue();

        if($dpe == 1){
            return false;
        }
        else{
            return true;
        }

    }
}

The functionality of the proforma not sending or sending depending on the attribute is working but of course I need it to stop duplicating orders.

This is not happening on my local but when put live it is when orders get duplicated. i just tried again and 1 order duplicated 18 times and 1 order didn't duplicate.

To be clear, the sending of proforma email depending on the customer attribute is working - the question is why is this code creating orders. one order duplicated over 40 times over the period of a minute.

2
  • 1
    the plugin you created is only for check whether sales email is enable or Not..it is no where related to order creation.did you get same issue when this plugin is disable ? – Pawan Jul 8 at 7:28
  • Its working.. it disabled the proforma email and sends it depending on the attribute its just duplicating orders – Gaz Smith Jul 21 at 13:20
1
+50

Try the following way:

<preference for="Magento\Sales\Model\Order\Email\Sender\OrderSender"
            type="VendorName\ModuleName\Model\Sales\Order\Email\Sender\OrderSender"/>

Now app/code/VendorName/ModuleName/Model/Sales/Order/Email/Sender/OrderSender.php

<?php


namespace VendorName\ModuleName\Model\Sales\Order\Email\Sender;

use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Payment\Helper\Data as PaymentHelper;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Address\Renderer;
use Magento\Sales\Model\Order\Email\Container\OrderIdentity;
use Magento\Sales\Model\Order\Email\Container\Template;
use Magento\Sales\Model\Order\Email\Sender\OrderSender as BaseOrderSender;
use Magento\Sales\Model\Order\Email\SenderBuilderFactory;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;
use Psr\Log\LoggerInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;

class OrderSender extends BaseOrderSender
{
    /**
     * @var CustomerRepositoryInterface
     */
    private $customerRepository;

    /**
     * OrderSender constructor.
     *
     * @param Template $templateContainer
     * @param OrderIdentity $identityContainer
     * @param SenderBuilderFactory $senderBuilderFactory
     * @param LoggerInterface $logger
     * @param Renderer $addressRenderer
     * @param PaymentHelper $paymentHelper
     * @param OrderResource $orderResource
     * @param ScopeConfigInterface $globalConfig
     * @param ManagerInterface $eventManager
     * @param CustomerRepositoryInterface $customerRepository
     */
    public function __construct(
        Template $templateContainer,
        OrderIdentity $identityContainer,
        SenderBuilderFactory $senderBuilderFactory,
        LoggerInterface $logger,
        Renderer $addressRenderer,
        PaymentHelper $paymentHelper,
        OrderResource $orderResource,
        ScopeConfigInterface $globalConfig,
        ManagerInterface $eventManager,
        CustomerRepositoryInterface $customerRepository
    ) {
        $this->customerRepository = $customerRepository;
        parent::__construct(
            $templateContainer,
            $identityContainer,
            $senderBuilderFactory,
            $logger,
            $addressRenderer,
            $paymentHelper,
            $orderResource,
            $globalConfig,
            $eventManager
        );
    }

    /**
     * @inheirtDoc
     */
    protected function checkAndSend(Order $order)
    {
        // If order place by registered customer
        if ($order->getCustomerId()) {
            $customer = $order->getCustomer();
            $disableProformaEmail = 0;
            if (!$customer) {
                try {
                    $customer = $this->customerRepository->getById($order->getCustomerId());
                } catch (\Exception $e) {

                }
            }
            if ($customer && ($customer instanceof CustomerInterface)) {
                $disableProformaEmailObject = $customer->getCustomAttribute('disable_proforma_email');
                if ($disableProformaEmailObject) {
                    $disableProformaEmail = $disableProformaEmailObject->getValue();
                }
            } elseif ($customer) {
                $disableProformaEmail = $customer->getData('disable_proforma_email');
            }

            if ($disableProformaEmail) {
                return false;
            } else {
                return parent::checkAndSend($order);
            }
        }

        // For guest customer order. If you don't allow then just return false instead of calling to the original method
        return parent::checkAndSend($order);
    }
}
12
  • I was confident about this answer but unfortunately i get Fatal Error: 'Uncaught Error: Call to a member function getData() on null – Gaz Smith Jul 21 at 11:08
  • Check the updated answer – Sohel Rana 2 days ago
  • Ill give this a try now, if it works locally ill deploy it and hopefully it will stop duplicating orders! do you know why it was duplicating orders with the previous method? – Gaz Smith 2 days ago
  • Fatal Error: 'Uncaught Error: Call to a member function getData() on null line 25 – Gaz Smith 2 days ago
  • That strange why $order->getCustomer() is return null, You need to load customer if $order->getCustomer() is return null. Check updated answer – Sohel Rana 2 days ago
0

Idea 1 - Calling Method On Null

The only thing so far that stands out about the code posted is the assumption that $customerGetValue->getCustomAttribute('disable_proforma_email') will always return an \Magento\Framework\Api\AttributeInterface that you can call AttributeInterface::getValue() on.

See the interface method for that method below. If an attribute happens to not be set, it will return null which you can't call getValue() on. This is a stretch but maybe that is a culprit? Either way, you will want to do a null check before calling getValue() on what you think is the custom attribute.

Example

$dpe = $customerGetValue->getCustomAttribute('disable_proforma_email')->getValue();
$dpe = ($dpe !== null) ? $dpe->getValue() : null;

enter image description here

Idea 2 - Check the logs

Check logs in var/log/*.log. I can't tell you how many times logs have saved me from spending a lot of time tracking down a problem.

Idea 3 - Custom Code "Process of Elimination"

Depending on the number of modules being used, it can be pretty hard to narrow down a problem when there are no obvious errors getting captured in logs. A sometimes useful strategy you can employ is to build a list of all custom modules that are enabled, then disable all of them to see if the problem stops happening.

If you are able to stop the problem with all custom modules being disabled, you can then keep enabling half of the extensions and re-testing in between to determine which batch of extensions is producing the problem.

Build List of All Custom Modules

Use the following command to output all custom modules not made by Magento.

php bin/magento module:status | grep -v 'Magento_'

Disable All Custom Modules

Use the following command to disable all of the custom modules. Some modules might have dependencies on each other so you might have to group them by vendor to have it succeed at disabling.

php bin/magento module:disable Vendor_Module1 Vendor_Module2, Vendor2_Module1 Vendor2_Module2

Re-enable Modules In Batches

Use the following command to keep enabling half of the list of custom modules, testing in between, until you can hopefully reproduce the problem in one of the enabled batches.

php bin/magento module:enable Vendor_Module1 Vendor_Module2, Vendor2_Module1 Vendor2_Module2

Idea 4 - General Thoughts

  • I have experienced Cybersource payment method in the past that caused duplicate orders the way the payment method worked in checkout. Curious if you have any custom payment methods that you could mention that might reveal something of interest.
1
  • Thanks for your answer, i went through each of them. Number 1) the attribute is set to 1 by default so the proforma sends to everyone, unless you go into the customer and turn it to 0 which then prevents it sending. the functionality i want acctually works i just cant understand why it is duplicating orders. somtimes over 20 times in miliseconds. 2) nothing in logs 3) ive disabled all other modules and it still does it (only on production, not on development stack) – Gaz Smith Jul 21 at 14:51
0

Try to change your code like this one :

public function aroundIsEnabled(\Magento\Sales\Model\Order\Email\Container\OrderIdentity $subject, callable $proceed)
    {
        $order = $this->checkoutSession->getLastRealOrder();
        $orderId=$order->getEntityId();  // order id
        
        $connection = $this->resourceConnection->getConnection();
        $sql = "select customer_id FROM sales_order where entity_id = " . $orderId;
        $result = $connection->fetchAll($sql);
        $customerId = null;
        foreach($result as $cid){
            $customerId = $cid["customer_id"];
        }

        $customerGetValue = $this->customerRepository->getById($customerId);
        $dpe = $customerGetValue->getCustomAttribute('disable_proforma_email')->getValue();

        if($dpe == 1){
            $returnValue = $proceed();
            return false;
        }
        else{
            $returnValue = $proceed();
            return true;
        }

    }
4
  • This caused more duplicates – Gaz Smith Jul 21 at 13:21
  • That means you not get those attribute value properly there and that why this not working. – Dhiren Vasoya Jul 21 at 13:24
  • It is working - the proforma is being sent if the atrtribute is set to no, the proforma is not being sent if its set to yes. This is correct. The problem is, i have no idea why, loads of orders start duplicating. I just enabled the module again and someone placed an order and it duplicated 18 times. another person placed an order and it didnt duplicate. – Gaz Smith Jul 21 at 13:50
  • Why would an email sender be creating orders? – Gaz Smith Jul 21 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.