<?php
/**
 *                       ######
 *                       ######
 * ############    ####( ######  #####. ######  ############   ############
 * #############  #####( ######  #####. ######  #############  #############
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
 * #############  #############  #############  #############  #####  ######
 *  ############   ############  #############   ############  #####  ######
 *                                      ######
 *                               #############
 *                               ############
 *
 * Adyen Payment module (https://www.adyen.com/)
 *
 * Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
 * See LICENSE.txt for license details.
 *
 * Author: Adyen <magento@adyen.com>
 */

namespace Adyen\Payment\Model;

use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Webapi\Exception;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
use Magento\Framework\App\Area;
use Magento\Framework\App\AreaList;
use Magento\Framework\Phrase\Renderer\Placeholder;
use Magento\Framework\Phrase;
use Magento\Sales\Model\OrderRepository;

class Cron
{

    /**
     * Logging instance
     *
     * @var \Adyen\Payment\Logger\AdyenLogger
     */
    protected $_logger;

    /**
     * @var ResourceModel\Notification\CollectionFactory
     */
    protected $_notificationFactory;

    /**
     * @var \Magento\Sales\Model\OrderFactory
     */
    protected $_orderFactory;

    /**
     * @var \Magento\Sales\Model\Order
     */
    protected $_order;

    /**
     * Core store config
     *
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $_scopeConfig;

    /**
     * @var \Adyen\Payment\Helper\Data
     */
    protected $_adyenHelper;

    /**
     * @var OrderSender
     */
    protected $_orderSender;

    /**
     * @var InvoiceSender
     */
    protected $_invoiceSender;

    /**
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $_transactionFactory;

    /**
     * @var \Adyen\Payment\Model\Billing\AgreementFactory
     */
    protected $_billingAgreementFactory;

    /**
     * @var ResourceModel\Billing\Agreement\CollectionFactory
     */
    protected $_billingAgreementCollectionFactory;

    /**
     * @var Api\PaymentRequest
     */
    protected $_adyenPaymentRequest;

    /**
     * notification attributes
     */
    protected $_pspReference;

    /**
     * @var
     */
    protected $_originalReference;

    /**
     * @var
     */
    protected $_merchantReference;

    /**
     * @var
     */
    protected $_acquirerReference;

    /**
     * @var
     */
    protected $ratepayDescriptor;

    /**
     * @var
     */
    protected $_eventCode;

    /**
     * @var
     */
    protected $_success;

    /**
     * @var
     */
    protected $_paymentMethod;

    /**
     * @var
     */
    protected $_reason;

    /**
     * @var
     */
    protected $_value;

    /**
     * @var
     */
    protected $_boletoOriginalAmount;

    /**
     * @var
     */
    protected $_boletoPaidAmount;

    /**
     * @var
     */
    protected $_modificationResult;

    /**
     * @var
     */
    protected $_klarnaReservationNumber;

    /**
     * @var
     */
    protected $_fraudManualReview;

    /**
     * @var Order\PaymentFactory
     */
    protected $_adyenOrderPaymentFactory;

    /**
     * @var ResourceModel\Order\Payment\CollectionFactory
     */
    protected $_adyenOrderPaymentCollectionFactory;

    /**
     * @var ResourceModel\InvoiceFactory
     */
    protected $_adyenInvoiceFactory;

    /**
     * @var AreaList
     */
    protected $_areaList;

    /**
     * @var \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory
     */
    protected $_orderStatusCollection;

    /**
     * @var SearchCriteriaBuilder
     */
    private $searchCriteriaBuilder;

    /**
     * @var OrderRepository
     */
    private $orderRepository;

    /**
     * @var ResourceModel\Billing\Agreement
     */
    private $agreementResourceModel;

    /**
     * @var \Magento\Sales\Model\Order\Payment\Transaction\Builder
     */
    private $transactionBuilder;

    /**
     * @var \Magento\Framework\Serialize\SerializerInterface
     */
    private $serializer;

    /**
     * @var \Magento\Framework\Notification\NotifierInterface
     */
    private $notifierPool;

    /**
     * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
     */
    private $timezone;

    /**
     * @var \Adyen\Payment\Helper\Config
     */
    protected $configHelper;

    /**
     * Cron constructor.
     *
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
     * @param ResourceModel\Notification\CollectionFactory $notificationFactory
     * @param \Magento\Sales\Model\OrderFactory $orderFactory
     * @param \Adyen\Payment\Helper\Data $adyenHelper
     * @param OrderSender $orderSender
     * @param InvoiceSender $invoiceSender
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
     * @param Billing\AgreementFactory $billingAgreementFactory
     * @param ResourceModel\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory
     * @param Api\PaymentRequest $paymentRequest
     * @param Order\PaymentFactory $adyenOrderPaymentFactory
     * @param ResourceModel\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory
     * @param InvoiceFactory $adyenInvoiceFactory
     * @param AreaList $areaList
     * @param \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $orderStatusCollection
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param OrderRepository $orderRepository
     * @param ResourceModel\Billing\Agreement $agreementResourceModel
     * @param \Magento\Sales\Model\Order\Payment\Transaction\Builder $transactionBuilder
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Adyen\Payment\Logger\AdyenLogger $adyenLogger,
        \Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory $notificationFactory,
        \Magento\Sales\Model\OrderFactory $orderFactory,
        \Adyen\Payment\Helper\Data $adyenHelper,
        OrderSender $orderSender,
        InvoiceSender $invoiceSender,
        \Magento\Framework\DB\TransactionFactory $transactionFactory,
        \Adyen\Payment\Model\Billing\AgreementFactory $billingAgreementFactory,
        \Adyen\Payment\Model\ResourceModel\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory,
        \Adyen\Payment\Model\Api\PaymentRequest $paymentRequest,
        \Adyen\Payment\Model\Order\PaymentFactory $adyenOrderPaymentFactory,
        \Adyen\Payment\Model\ResourceModel\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory,
        \Adyen\Payment\Model\InvoiceFactory $adyenInvoiceFactory,
        AreaList $areaList,
        \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $orderStatusCollection,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        OrderRepository $orderRepository,
        \Adyen\Payment\Model\ResourceModel\Billing\Agreement $agreementResourceModel,
        \Magento\Sales\Model\Order\Payment\Transaction\Builder $transactionBuilder,
        \Magento\Framework\Serialize\SerializerInterface $serializer,
        \Magento\Framework\Notification\NotifierInterface $notifierPool,
        \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone,
        \Adyen\Payment\Helper\Config $configHelper
    ) {
        $this->_scopeConfig = $scopeConfig;
        $this->_adyenLogger = $adyenLogger;
        $this->_notificationFactory = $notificationFactory;
        $this->_orderFactory = $orderFactory;
        $this->_adyenHelper = $adyenHelper;
        $this->_orderSender = $orderSender;
        $this->_invoiceSender = $invoiceSender;
        $this->_transactionFactory = $transactionFactory;
        $this->_billingAgreementFactory = $billingAgreementFactory;
        $this->_billingAgreementCollectionFactory = $billingAgreementCollectionFactory;
        $this->_adyenPaymentRequest = $paymentRequest;
        $this->_adyenOrderPaymentFactory = $adyenOrderPaymentFactory;
        $this->_adyenOrderPaymentCollectionFactory = $adyenOrderPaymentCollectionFactory;
        $this->_adyenInvoiceFactory = $adyenInvoiceFactory;
        $this->_areaList = $areaList;
        $this->_orderStatusCollection = $orderStatusCollection;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->orderRepository = $orderRepository;
        $this->agreementResourceModel = $agreementResourceModel;
        $this->transactionBuilder = $transactionBuilder;
        $this->serializer = $serializer;
        $this->notifierPool = $notifierPool;
        $this->timezone = $timezone;
        $this->configHelper = $configHelper;
    }

    /**
     * Process the notification
     *
     * @return void
     */
    public function processNotification()
    {
        try {
            $this->execute();
        } catch (\Exception $e) {
            $this->_adyenLogger->addAdyenNotificationCronjob($e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
    }

    public function execute()
    {
        // needed for Magento < 2.2.0 https://github.com/magento/magento2/pull/8413
        $renderer = Phrase::getRenderer();
        if ($renderer instanceof Placeholder) {
            $this->_areaList->getArea(Area::AREA_CRONTAB)->load(Area::PART_TRANSLATE);
        }

        $this->_order = null;

        $notifications = $this->_notificationFactory->create();
        $notifications->notificationsToProcessFilter();

        // OFFER_CLOSED notifications needs to be at least 10 minutes old to be processed
        $offerClosedMinDate = new \DateTime();
        $offerClosedMinDate->modify('-10 minutes');

        foreach ($notifications as $notification) {
            // Remove OFFER_CLOSED notifications arrived in the last 10 minutes from the list to process to ensure it
            // won't close any order which has an AUTHORISED notification arrived a bit later than the OFFER_CLOSED one.
            $createdAt = \DateTime::createFromFormat('Y-m-d H:i:s', $notification['created_at']);
            // Difference between $offerClosedMinDate and $createdAt in minutes is the time until processing the
            // notification in minutes
            $minutesUntilProcessing = $offerClosedMinDate->diff($createdAt)->i;
            if ($notification['event_code'] == Notification::OFFER_CLOSED && $minutesUntilProcessing > 0) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf('OFFER_CLOSED notification %s skipped! Wait %s minute(s) before processing.',
                        $notification->getEntityId(), $minutesUntilProcessing)
                );
                $notifications->removeItemByKey($notification->getId());
                continue;
            }

            // set Cron processing to true
            $this->_updateNotification($notification, true, false);
        }

        // loop over the notifications
        $count = 0;
        foreach ($notifications as $notification) {
            try {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf("Processing notification %s", $notification->getEntityId())
                );

                // ignore duplicate notification
                if ($this->_isDuplicate($notification)) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        "This is a duplicate notification and will be ignored"
                    );
                    $this->_updateNotification($notification, false, true);
                    ++$count;
                    continue;
                }

                // log the executed notification
                $this->_adyenLogger->addAdyenNotificationCronjob(print_r($notification->debug(), 1));

                // get order
                $incrementId = $notification->getMerchantReference();

                $searchCriteria = $this->searchCriteriaBuilder
                    ->addFilter('increment_id', $incrementId, 'eq')
                    ->create();

                $orderList = $this->orderRepository->getList($searchCriteria)->getItems();

                /** @var \Magento\Sales\Model\Order $order */
                $order = reset($orderList);
                $this->_order = $order;

                if (!$this->_order) {
                    // order does not exists remove from queue
                    $notification->delete();
                    continue;
                }

                // declare all variables that are needed
                $this->_declareVariables($notification);

                // add notification to comment history status is current status
                $this->_addStatusHistoryComment();

                $previousAdyenEventCode = $this->_order->getData('adyen_notification_event_code');

                // update order details
                $this->_updateAdyenAttributes($notification);

                // check if success is true of false
                if (strcmp($this->_success, 'false') == 0 || strcmp($this->_success, '0') == 0) {
                    /*
                     * Only cancel the order when it is in state pending, payment review or
                     * if the ORDER_CLOSED is failed (means split payment has not be successful)
                     */
                    if ($this->_order->getState() === \Magento\Sales\Model\Order::STATE_PENDING_PAYMENT ||
                        $this->_order->getState() === \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW ||
                        $this->_eventCode == Notification::ORDER_CLOSED
                    ) {
                        $this->_adyenLogger->addAdyenNotificationCronjob('Going to cancel the order');

                        // if payment is API check, check if API result pspreference is the same as reference
                        if ($this->_eventCode == NOTIFICATION::AUTHORISATION && $this->_getPaymentMethodType() == 'api') {
                            // don't cancel the order becasue order was successfull through api
                            $this->_adyenLogger->addAdyenNotificationCronjob(
                                'order is not cancelled because api result was succesfull'
                            );
                        } else {
                            /*
                             * don't cancel the order if previous state is authorisation with success=true
                             * Split payments can fail if the second payment has failed the first payment is
                             * refund/cancelled as well so if it is a split payment that failed cancel the order as well
                             */
                            if ($previousAdyenEventCode != "AUTHORISATION : TRUE" ||
                                $this->_eventCode == Notification::ORDER_CLOSED
                            ) {
                                // Move the order from PAYMENT_REVIEW to NEW, so that can be cancelled
                                if ($this->_order->getState() === \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW &&
                                    $this->configHelper->getNotificationsCanCancel($this->_order->getStoreId())
                                ) {
                                    $this->_order->setState(\Magento\Sales\Model\Order::STATE_NEW);
                                }
                                $this->_holdCancelOrder(false);
                            } else {
                                $this->_order->setData('adyen_notification_event_code', $previousAdyenEventCode);
                                $this->_adyenLogger->addAdyenNotificationCronjob(
                                    'order is not cancelled because previous notification
                                    was an authorisation that succeeded'
                                );
                            }
                        }
                    } else {
                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'Order is already processed so ignore this notification state is:' . $this->_order->getState()
                        );
                    }
                    //Trigger admin notice for unsuccessful REFUND notifications
                    if ($this->_eventCode == Notification::REFUND) {
                        $this->addRefundFailedNotice();
                    }
                } else {
                    // Notification is successful
                    $this->_processNotification();
                }

                try {
                    // set done to true
                    $this->_order->save();
                } catch (\Exception $e) {
                    $this->_adyenLogger->addAdyenNotificationCronjob($e->getMessage());
                }

                $this->_updateNotification($notification, false, true);
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf("Notification %s is processed", $notification->getEntityId())
                );
                ++$count;
            } catch (\Exception $e) {
                $this->_updateNotification($notification, false, false);
                $this->handleNotificationError($notification, $e->getMessage());
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf("Notification %s had an error: %s \n %s", $notification->getEntityId(), $e->getMessage(),
                        $e->getTraceAsString())
                );
            }
        }

        if ($count > 0) {
            $this->_adyenLogger->addAdyenNotificationCronjob(sprintf("Cronjob updated %s notification(s)", $count));
        }
    }

    /**
     * @param $notification
     * @param $processing
     * @param $done
     */
    protected function _updateNotification($notification, $processing, $done)
    {
        if ($done) {
            $notification->setDone(true);
        }
        $notification->setProcessing($processing);
        $notification->setUpdatedAt(new \DateTime());
        $notification->save();
    }

    /**
     * Check if the notification is already executed if so this is a duplicate and ignore this one
     *
     * @param $notification
     * @return bool
     */
    protected function _isDuplicate($notification)
    {
        return $notification->isDuplicate(
            $notification->getPspreference(),
            $notification->getEventCode(),
            $notification->getSuccess(),
            $notification->getOriginalReference(),
            true
        );
    }

    /**
     * Declare private variables for processing notification
     *
     * @param Object $notification
     * @return void
     */
    protected function _declareVariables($notification)
    {
        //  declare the common parameters
        $this->_pspReference = $notification->getPspreference();
        $this->_originalReference = $notification->getOriginalReference();
        $this->_merchantReference = $notification->getMerchantReference();
        $this->_eventCode = $notification->getEventCode();
        $this->_success = $notification->getSuccess();
        $this->_paymentMethod = $notification->getPaymentMethod();
        $this->_reason = $notification->getReason();
        $this->_value = $notification->getAmountValue();
        $this->_live = $notification->getLive();

        $additionalData = !empty($notification->getAdditionalData()) ? $this->serializer->unserialize($notification->getAdditionalData()) : "";

        // boleto data
        if ($this->_paymentMethodCode() == "adyen_boleto") {
            if ($additionalData && is_array($additionalData)) {
                $boletobancario = isset($additionalData['boletobancario']) ? $additionalData['boletobancario'] : null;
                if ($boletobancario && is_array($boletobancario)) {
                    $this->_boletoOriginalAmount =
                        isset($boletobancario['originalAmount']) ? trim($boletobancario['originalAmount']) : "";
                    $this->_boletoPaidAmount =
                        isset($boletobancario['paidAmount']) ? trim($boletobancario['paidAmount']) : "";
                }
            }
        }

        if ($additionalData && is_array($additionalData)) {
            // check if the payment is in status manual review
            $fraudManualReview = isset($additionalData['fraudManualReview']) ?
                $additionalData['fraudManualReview'] : "";
            if ($fraudManualReview == "true") {
                $this->_fraudManualReview = true;
            } else {
                $this->_fraudManualReview = false;
            }

            // modification.action is it for JSON
            $modificationActionJson = isset($additionalData['modification.action']) ?
                $additionalData['modification.action'] : null;
            if ($modificationActionJson != "") {
                $this->_modificationResult = $modificationActionJson;
            }

            $modification = isset($additionalData['modification']) ? $additionalData['modification'] : null;
            if ($modification && is_array($modification)) {
                $this->_modificationResult = isset($modification['action']) ? trim($modification['action']) : "";
            }
            $additionalData2 = isset($additionalData['additionalData']) ? $additionalData['additionalData'] : null;
            if ($additionalData2 && is_array($additionalData2)) {
                $this->_klarnaReservationNumber = isset($additionalData2['acquirerReference']) ? trim($additionalData2['acquirerReference']) : "";
            }
            $acquirerReference = isset($additionalData['acquirerReference']) ? $additionalData['acquirerReference'] : null;
            if ($acquirerReference != "") {
                $this->_acquirerReference = $acquirerReference;
            }
            $ratepayDescriptor = isset($additionalData['openinvoicedata.descriptor']) ? $additionalData['openinvoicedata.descriptor'] : "";
            if ($ratepayDescriptor !== "") {
                $this->ratepayDescriptor = $ratepayDescriptor;
            }
        }
    }

    /**
     * @return mixed
     */
    protected function _paymentMethodCode()
    {
        return $this->_order->getPayment()->getMethod();
    }

    /**
     * @return mixed
     */
    protected function _getPaymentMethodType()
    {
        return $this->_order->getPayment()->getPaymentMethodType();
    }

    /**
     * @desc order comments or history
     * @param type $order
     */
    protected function _addStatusHistoryComment()
    {
        $successResult = (strcmp($this->_success, 'true') == 0 ||
            strcmp($this->_success, '1') == 0) ? 'true' : 'false';
        $success = (!empty($this->_reason)) ? "$successResult <br />reason:$this->_reason" : $successResult;

        if ($this->_eventCode == Notification::REFUND || $this->_eventCode == Notification::CAPTURE) {
            $currency = $this->_order->getOrderCurrencyCode();

            // check if it is a full or partial refund
            $amount = $this->_value;
            $orderAmount = (int)$this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $currency);

            $this->_adyenLogger->addAdyenNotificationCronjob(
                'amount notification:' . $amount . ' amount order:' . $orderAmount
            );

            if ($amount == $orderAmount) {
                $this->_order->setData(
                    'adyen_notification_event_code',
                    $this->_eventCode . " : " . strtoupper($successResult)
                );
            } else {
                $this->_order->setData(
                    'adyen_notification_event_code',
                    "(PARTIAL) " .
                    $this->_eventCode . " : " . strtoupper($successResult)
                );
            }
        } else {
            $this->_order->setData(
                'adyen_notification_event_code',
                $this->_eventCode . " : " . strtoupper($successResult)
            );
        }

        // if payment method is klarna, ratepay or openinvoice/afterpay show the reservartion number
        if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($this->_paymentMethod) && !empty($this->_klarnaReservationNumber)) {
            $klarnaReservationNumberText = "<br /> reservationNumber: " . $this->_klarnaReservationNumber;
        } else {
            $klarnaReservationNumberText = "";
        }

        if ($this->_boletoPaidAmount != null && $this->_boletoPaidAmount != "") {
            $boletoPaidAmountText = "<br /> Paid amount: " . $this->_boletoPaidAmount;
        } else {
            $boletoPaidAmountText = "";
        }

        $type = 'Adyen HTTP Notification(s):';
        $comment = __(
            '%1 <br /> eventCode: %2 <br /> pspReference: %3 <br /> paymentMethod: %4 <br />' .
            ' success: %5 %6 %7',
            $type,
            $this->_eventCode,
            $this->_pspReference,
            $this->_paymentMethod,
            $success,
            $klarnaReservationNumberText,
            $boletoPaidAmountText
        );

        // If notification is pending status and pending status is set add the status change to the comment history
        if ($this->_eventCode == Notification::PENDING) {
            $pendingStatus = $this->_getConfigData(
                'pending_status',
                'adyen_abstract',
                $this->_order->getStoreId()
            );
            if ($pendingStatus != "") {
                $this->_order->addStatusHistoryComment($comment, $pendingStatus);
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Created comment history for this notification with status change to: ' . $pendingStatus
                );
                return;
            }
        }

        // if manual review is accepted and a status is selected. Change the status through this comment history item
        if ($this->_eventCode == Notification::MANUAL_REVIEW_ACCEPT
            && $this->_getFraudManualReviewAcceptStatus() != ""
        ) {
            $manualReviewAcceptStatus = $this->_getFraudManualReviewAcceptStatus();
            $this->_order->addStatusHistoryComment($comment, $manualReviewAcceptStatus);
            $this->_adyenLogger->addAdyenNotificationCronjob('Created comment history for this notification with status change to: ' . $manualReviewAcceptStatus);
            return;
        }

        $this->_order->addStatusHistoryComment($comment);
        $this->_adyenLogger->addAdyenNotificationCronjob('Created comment history for this notification');
    }

    /**
     * @param $notification
     */
    protected function _updateAdyenAttributes($notification)
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Updating the Adyen attributes of the order');

        $additionalData = !empty($notification->getAdditionalData()) ? $this->serializer->unserialize($notification->getAdditionalData()) : "";

        $_paymentCode = $this->_paymentMethodCode();

        if ($this->_eventCode == Notification::AUTHORISATION
            || $this->_eventCode == Notification::HANDLED_EXTERNALLY
        ) {
            /*
             * if current notification is authorisation : false and
             * the  previous notification was authorisation : true do not update pspreference
             */
            if (strcmp($this->_success, 'false') == 0 ||
                strcmp($this->_success, '0') == 0 ||
                strcmp($this->_success, '') == 0
            ) {
                $previousAdyenEventCode = $this->_order->getData('adyen_notification_event_code');
                if ($previousAdyenEventCode != "AUTHORISATION : TRUE") {
                    $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
                }
            } else {
                $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
            }
        }
    }

    /**
     * @param $additionalData
     */
    protected function _updateOrderPaymentWithAdyenAttributes($additionalData)
    {
        if ($additionalData && is_array($additionalData)) {
            $avsResult = (isset($additionalData['avsResult'])) ? $additionalData['avsResult'] : "";
            $cvcResult = (isset($additionalData['cvcResult'])) ? $additionalData['cvcResult'] : "";
            $totalFraudScore = (isset($additionalData['totalFraudScore'])) ? $additionalData['totalFraudScore'] : "";
            $ccLast4 = (isset($additionalData['cardSummary'])) ? $additionalData['cardSummary'] : "";
            $refusalReasonRaw = (isset($additionalData['refusalReasonRaw'])) ? $additionalData['refusalReasonRaw'] : "";
            $acquirerReference = (isset($additionalData['acquirerReference'])) ?
                $additionalData['acquirerReference'] : "";
            $authCode = (isset($additionalData['authCode'])) ? $additionalData['authCode'] : "";
            $cardBin = (isset($additionalData['cardBin'])) ? $additionalData['cardBin'] : "";
            $expiryDate = (isset($additionalData['expiryDate'])) ? $additionalData['expiryDate'] : "";
        }

        // if there is no server communication setup try to get last4 digits from reason field
        if (!isset($ccLast4) || $ccLast4 == "") {
            $ccLast4 = $this->_retrieveLast4DigitsFromReason($this->_reason);
        }

        $this->_order->getPayment()->setAdyenPspReference($this->_pspReference);
        $this->_order->getPayment()->setAdditionalInformation('pspReference', $this->_pspReference);

        if ($this->_klarnaReservationNumber != "") {
            $this->_order->getPayment()->setAdditionalInformation(
                'adyen_klarna_number',
                $this->_klarnaReservationNumber
            );
        }
        if (isset($ccLast4) && $ccLast4 != "") {
            // this field is column in db by core
            $this->_order->getPayment()->setccLast4($ccLast4);
        }
        if (isset($avsResult) && $avsResult != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_avs_result', $avsResult);
        }
        if (isset($cvcResult) && $cvcResult != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_cvc_result', $cvcResult);
        }
        if ($this->_boletoPaidAmount != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_boleto_paid_amount', $this->_boletoPaidAmount);
        }
        if (isset($totalFraudScore) && $totalFraudScore != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_total_fraud_score', $totalFraudScore);
        }
        if (isset($refusalReasonRaw) && $refusalReasonRaw != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_refusal_reason_raw', $refusalReasonRaw);
        }
        if (isset($acquirerReference) && $acquirerReference != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_acquirer_reference', $acquirerReference);
        }
        if (isset($authCode) && $authCode != "") {
            $this->_order->getPayment()->setAdditionalInformation('adyen_auth_code', $authCode);
        }
        if (!empty($cardBin)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_card_bin', $cardBin);
        }
        if (!empty($expiryDate)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_expiry_date', $expiryDate);
        }
        if ($this->ratepayDescriptor !== "") {
            $this->_order->getPayment()->setAdditionalInformation(
                'adyen_ratepay_descriptor',
                $this->ratepayDescriptor
            );
        }
    }

    /**
     * retrieve last 4 digits of card from the reason field
     *
     * @param $reason
     * @return string
     */
    protected function _retrieveLast4DigitsFromReason($reason)
    {
        $result = "";

        if ($reason != "") {
            $reasonArray = explode(":", $reason);
            if ($reasonArray != null && is_array($reasonArray)) {
                if (isset($reasonArray[1])) {
                    $result = $reasonArray[1];
                }
            }
        }
        return $result;
    }

    /**
     * @param $ignoreHasInvoice
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    protected function _holdCancelOrder($ignoreHasInvoice)
    {
        if (!$this->configHelper->getNotificationsCanCancel($this->_order->getStoreId())) {
            $this->_adyenLogger->addAdyenNotificationCronjob('Order cannot be canceled based on the plugin configuration');
            return;
        }

        $orderStatus = $this->_getConfigData(
            'payment_cancelled',
            'adyen_abstract',
            $this->_order->getStoreId()
        );

        // check if order has in invoice only cancel/hold if this is not the case
        if ($ignoreHasInvoice || !$this->_order->hasInvoices()) {
            if ($orderStatus == \Magento\Sales\Model\Order::STATE_HOLDED) {
                // Allow magento to hold order
                $this->_order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_HOLD, true);

                if ($this->_order->canHold()) {
                    $this->_order->hold();
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob('Order can not hold or is already on Hold');
                    return;
                }
            } else {
                // Allow magento to cancel order
                $this->_order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_CANCEL, true);

                if ($this->_order->canCancel()) {
                    $this->_order->cancel();
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob('Order can not be canceled');
                    return;
                }
            }
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob('Order has already an invoice so cannot be canceled');
        }
    }

    /**
     * Process the Notification
     */
    protected function _processNotification()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Processing the notification');
        $_paymentCode = $this->_paymentMethodCode();

        switch ($this->_eventCode) {
            case Notification::REFUND_FAILED:
                //Trigger admin notice for REFUND_FAILED notifications
                $this->addRefundFailedNotice();
                break;
            case Notification::REFUND:
                $ignoreRefundNotification = $this->_getConfigData(
                    'ignore_refund_notification',
                    'adyen_abstract',
                    $this->_order->getStoreId()
                );
                if ($ignoreRefundNotification != true) {
                    $this->_refundOrder();
                    //refund completed
                    $this->_setRefundAuthorized();
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'Setting to ignore refund notification is enabled so ignore this notification'
                    );
                }
                break;
            case Notification::PENDING:
                if ($this->_getConfigData(
                    'send_email_bank_sepa_on_pending',
                    'adyen_abstract',
                    $this->_order->getStoreId()
                )
                ) {
                    // Check if payment is banktransfer or sepa if true then send out order confirmation email
                    $isBankTransfer = $this->_isBankTransfer();
                    if ($isBankTransfer || $this->_paymentMethod == 'sepadirectdebit') {
                        if (!$this->_order->getEmailSent()) {
                            $this->_sendOrderMail();
                        }
                    }
                }
                break;
            case Notification::HANDLED_EXTERNALLY:
            case Notification::AUTHORISATION:
                $this->_authorizePayment();
                break;
            case Notification::MANUAL_REVIEW_REJECT:
                // don't do anything it will send a CANCEL_OR_REFUND notification when this payment is captured
                break;
            case Notification::MANUAL_REVIEW_ACCEPT:
                /*
                 * only process this if you are on auto capture.
                 * On manual capture you will always get Capture or CancelOrRefund notification
                 */
                if ($this->_isAutoCapture()) {
                    $this->_setPaymentAuthorized(false);
                }
                break;
            case Notification::CAPTURE:
                /*
                 * ignore capture if you are on auto capture
                 * this could be called if manual review is enabled and you have a capture delay
                 */
                if (!$this->_isAutoCapture()) {
                    $this->_setPaymentAuthorized(false, true);

                    /*
                     * Add invoice in the adyen_invoice table
                     */
                    $invoiceCollection = $this->_order->getInvoiceCollection();
                    foreach ($invoiceCollection as $invoice) {
                        if ($invoice->getTransactionId() == $this->_pspReference) {
                            $this->_adyenInvoiceFactory->create()
                                ->setInvoiceId($invoice->getEntityId())
                                ->setPspreference($this->_pspReference)
                                ->setOriginalReference($this->_originalReference)
                                ->setAcquirerReference($this->_acquirerReference)
                                ->save();
                            $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice entry in the Adyen table');
                        }
                    }
                }
                break;
            case Notification::OFFER_CLOSED:
                $previousSuccess = $this->_order->getData('adyen_notification_event_code_success');

                // Order is already Authorised
                if (!empty($previousSuccess)) {
                    $this->_adyenLogger->addAdyenNotificationCronjob("Order is already authorised, skipping OFFER_CLOSED");
                    break;
                }

                // Order is already Cancelled
                if ($this->_order->isCanceled()) {
                    $this->_adyenLogger->addAdyenNotificationCronjob("Order is already cancelled, skipping OFFER_CLOSED");
                    break;
                }

                /*
                 * For cards, it can be 'visa', 'maestro',...
                 * For alternatives, it can be 'ideal', 'directEbanking',...
                 */
                $notificationPaymentMethod = $this->_paymentMethod;

                /*
                * For cards, it can be 'VI', 'MI',...
                * For alternatives, it can be 'ideal', 'directEbanking',...
                */
                $orderPaymentMethod = $this->_order->getPayment()->getCcType();

                $isOrderCc = strcmp($this->_paymentMethodCode(),
                        'adyen_cc') == 0 || strcmp($this->_paymentMethodCode(), 'adyen_oneclick') == 0;

                /*
                 * If the order was made with an Alternative payment method,
                 * continue with the cancellation only if the payment method of
                 * the notification matches the payment method of the order.
                 */
                if (!$isOrderCc && strcmp($notificationPaymentMethod, $orderPaymentMethod) !== 0) {
                    $this->_adyenLogger->addAdyenNotificationCronjob("Order is not a credit card, 
                    or the payment method in the notification does not match the payment method of the order, 
                    skipping OFFER_CLOSED");
                    break;
                }

                if (!$this->_order->canCancel() && $this->configHelper->getNotificationsCanCancel($this->_order->getStoreId())) {
                    // Move the order from PAYMENT_REVIEW to NEW, so that can be cancelled
                    $this->_order->setState(\Magento\Sales\Model\Order::STATE_NEW);
                }
                $this->_holdCancelOrder(true);
                break;
            case Notification::CAPTURE_FAILED:
            case Notification::CANCELLATION:
            case Notification::CANCELLED:
                $this->_holdCancelOrder(true);
                break;
            case Notification::CANCEL_OR_REFUND:
                if (isset($this->_modificationResult) && $this->_modificationResult != "") {
                    if ($this->_modificationResult == "cancel") {
                        $this->_holdCancelOrder(true);
                    } elseif ($this->_modificationResult == "refund") {
                        $this->_refundOrder();
                        //refund completed
                        $this->_setRefundAuthorized();
                    }
                } else {
                    if ($this->_order->isCanceled() ||
                        $this->_order->getState() === \Magento\Sales\Model\Order::STATE_HOLDED
                    ) {
                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'Order is already cancelled or holded so do nothing'
                        );
                    } else {
                        if ($this->_order->canCancel() || $this->_order->canHold()) {
                            $this->_adyenLogger->addAdyenNotificationCronjob('try to cancel the order');
                            $this->_holdCancelOrder(true);
                        } else {
                            $this->_adyenLogger->addAdyenNotificationCronjob('try to refund the order');
                            // refund
                            $this->_refundOrder();
                            //refund completed
                            $this->_setRefundAuthorized();
                        }
                    }
                }
                break;

            case Notification::RECURRING_CONTRACT:


                // only store billing agreements if Vault is disabled
                if (!$this->_adyenHelper->isCreditCardVaultEnabled()) {
                    // storedReferenceCode
                    $recurringDetailReference = $this->_pspReference;

                    $storeId = $this->_order->getStoreId();
                    $customerReference = $this->_order->getCustomerId();
                    $listRecurringContracts = null;
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        __(
                            'CustomerReference is: %1 and storeId is %2 and RecurringDetailsReference is %3',
                            $customerReference,
                            $storeId,
                            $recurringDetailReference
                        )
                    );
                    try {
                        $listRecurringContracts = $this->_adyenPaymentRequest->getRecurringContractsForShopper(
                            $customerReference,
                            $storeId
                        );
                        $contractDetail = null;
                        // get current Contract details and get list of all current ones
                        $recurringReferencesList = [];

                        if (!$listRecurringContracts) {
                            throw new \Exception("Empty list recurring contracts");
                        }
                        // Find the reference on the list
                        foreach ($listRecurringContracts as $rc) {
                            $recurringReferencesList[] = $rc['recurringDetailReference'];
                            if (isset($rc['recurringDetailReference']) &&
                                $rc['recurringDetailReference'] == $recurringDetailReference
                            ) {
                                $contractDetail = $rc;
                            }
                        }

                        if ($contractDetail == null) {
                            $this->_adyenLogger->addAdyenNotificationCronjob(print_r($listRecurringContracts, 1));
                            $message = __(
                                'Failed to create billing agreement for this order ' .
                                '(listRecurringCall did not contain contract)'
                            );
                            throw new \Exception($message);
                        }

                        $billingAgreements = $this->_billingAgreementCollectionFactory->create();
                        $billingAgreements->addFieldToFilter('customer_id', $customerReference);

                        // Get collection and update existing agreements

                        foreach ($billingAgreements as $updateBillingAgreement) {
                            if (!in_array($updateBillingAgreement->getReferenceId(), $recurringReferencesList)) {
                                $updateBillingAgreement->setStatus(
                                    \Adyen\Payment\Model\Billing\Agreement::STATUS_CANCELED
                                );
                            } else {
                                $updateBillingAgreement->setStatus(
                                    \Adyen\Payment\Model\Billing\Agreement::STATUS_ACTIVE
                                );
                            }
                            $updateBillingAgreement->save();
                        }

                        // Get or create billing agreement
                        $billingAgreement = $this->_billingAgreementFactory->create();
                        $billingAgreement->load($recurringDetailReference, 'reference_id');
                        // check if BA exists
                        if (!($billingAgreement && $billingAgreement->getAgreementId() > 0 && $billingAgreement->isValid())) {
                            // create new
                            $this->_adyenLogger->addAdyenNotificationCronjob("Creating new Billing Agreement");
                            $this->_order->getPayment()->setBillingAgreementData(
                                [
                                    'billing_agreement_id' => $recurringDetailReference,
                                    'method_code' => $this->_order->getPayment()->getMethodCode(),
                                ]
                            );

                            $billingAgreement = $this->_billingAgreementFactory->create();
                            $billingAgreement->setStoreId($this->_order->getStoreId());
                            $billingAgreement->importOrderPayment($this->_order->getPayment());
                            $message = __('Created billing agreement #%1.', $recurringDetailReference);
                        } else {
                            $this->_adyenLogger->addAdyenNotificationCronjob("Using existing Billing Agreement");
                            $billingAgreement->setIsObjectChanged(true);
                            $message = __('Updated billing agreement #%1.', $recurringDetailReference);
                        }

                        // Populate billing agreement data
                        $billingAgreement->parseRecurringContractData($contractDetail);
                        if ($billingAgreement->isValid()) {
                            if (!$this->agreementResourceModel->getOrderRelation($billingAgreement->getAgreementId(),
                                $this->_order->getId())) {
                                // save into sales_billing_agreement_order
                                $billingAgreement->addOrderRelation($this->_order);

                                // add to order to save agreement
                                $this->_order->addRelatedObject($billingAgreement);
                            }
                        } else {
                            $message = __('Failed to create billing agreement for this order.');
                            throw new \Exception($message);
                        }
                    } catch (\Exception $exception) {
                        $message = $exception->getMessage();
                    }

                    $this->_adyenLogger->addAdyenNotificationCronjob($message);
                    $comment = $this->_order->addStatusHistoryComment($message);
                    $this->_order->addRelatedObject($comment);
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob('Ignore recurring_contract notification because Vault feature is enabled');
                }
                break;
            default:
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf('This notification event: %s is not supported so will be ignored', $this->_eventCode)
                );
                break;
        }
    }

    /**
     * Not implemented
     *
     * @return bool
     */
    protected function _refundOrder()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Refunding the order');

        // check if it is a split payment if so save the refunded data
        if ($this->_originalReference != "") {
            $this->_adyenLogger->addAdyenNotificationCronjob('Going to update the refund to split payments table');

            $orderPayment = $this->_adyenOrderPaymentCollectionFactory
                ->create()
                ->addFieldToFilter(\Adyen\Payment\Model\Notification::PSPREFRENCE, $this->_originalReference)
                ->getFirstItem();

            if ($orderPayment->getId() > 0) {
                $currency = $this->_order->getOrderCurrencyCode();
                $amountRefunded = $amountRefunded = $orderPayment->getTotalRefunded() +
                    $this->_adyenHelper->originalAmount($this->_value, $currency);
                $orderPayment->setUpdatedAt(new \DateTime());
                $orderPayment->setTotalRefunded($amountRefunded);
                $orderPayment->save();
                $this->_adyenLogger->addAdyenNotificationCronjob('Update the refund in the split payments table');
            } else {
                $this->_adyenLogger->addAdyenNotificationCronjob('Payment not found in split payment table');
            }
        }

        /*
         * Don't create a credit memo if refund is initialize in Magento
         * because in this case the credit memo already exists
         */
        $lastTransactionId = $this->_order->getPayment()->getLastTransId();
        if ($lastTransactionId != $this->_pspReference) {
            // refund is done through adyen backoffice so create a credit memo
            $order = $this->_order;
            if ($order->canCreditmemo()) {
                $currency = $this->_order->getOrderCurrencyCode();
                $amount = $this->_adyenHelper->originalAmount($this->_value, $currency);
                $order->getPayment()->registerRefundNotification($amount);

                $this->_adyenLogger->addAdyenNotificationCronjob('Created credit memo for order');
            } else {
                $this->_adyenLogger->addAdyenNotificationCronjob('Could not create a credit memo for order');
            }
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Did not create a credit memo for this order because refund is done through Magento'
            );
        }
    }

    /**
     * @param $order
     */
    protected function _setRefundAuthorized()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Status update to default status or refund_authorized status if this is set'
        );
        $this->_order->addStatusHistoryComment(__('Adyen Refund Successfully completed'));
    }

    /**
     * authorize payment
     */
    protected function _authorizePayment()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Authorisation of the order');

        // Set adyen_notification_event_code_success to true so that we ignore a possible OFFER_CLOSED
        if (strcmp($this->_success, 'true') == 0) {
            $this->_order->setData('adyen_notification_event_code_success', 1);
        }
        $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();

        // If manual review is active and a separate status is used then ignore the pre authorized status
        if ($this->_fraudManualReview != true || $fraudManualReviewStatus == "") {
            $this->_setPrePaymentAuthorized();
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Ignore the pre authorized status because the order is ' .
                'under manual review and use the Manual review status'
            );
        }

        $this->_prepareInvoice();
        $_paymentCode = $this->_paymentMethodCode();

        // for boleto confirmation mail is send on order creation
        if ($this->_paymentMethod != "adyen_boleto") {
            // send order confirmation mail after invoice creation so merchant can add invoicePDF to this mail
            if (!$this->_order->getEmailSent()) {
                $this->_sendOrderMail();
            }
        }

        if ($this->_paymentMethod == "c_cash" &&
            $this->_getConfigData('create_shipment', 'adyen_cash', $this->_order->getStoreId())
        ) {
            $this->_createShipment();
        }
    }

    /**
     * Send order Mail
     *
     * @return void
     */
    private function _sendOrderMail()
    {
        try {
            $this->_orderSender->send($this->_order);
            $this->_adyenLogger->addAdyenNotificationCronjob('Send orderconfirmation email to shopper');
        } catch (\Exception $exception) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                "Exception in Send Mail in Magento. This is an issue in the the core of Magento" .
                $exception->getMessage()
            );
        }
    }

    /**
     * Set status on authorisation
     *
     * @return void
     */
    private function _setPrePaymentAuthorized()
    {
        $status = $this->_getConfigData(
            'payment_pre_authorized',
            'adyen_abstract',
            $this->_order->getStoreId()
        );

        // only do this if status in configuration is set
        if (!empty($status)) {
            $this->_order->addStatusHistoryComment(__('Payment is authorised waiting for capture'), $status);
            $this->_setState($status);

            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Order status is changed to Pre-authorised status, status is ' . $status
            );
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob('No pre-authorised status is used so ignore');
        }
    }

    /**
     * @return void
     * @throws Exception
     */
    protected function _prepareInvoice()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Prepare invoice for order');

        //Set order state to new because with order state payment_review it is not possible to create an invoice
        if (strcmp($this->_order->getState(), \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW) == 0) {
            $this->_order->setState(\Magento\Sales\Model\Order::STATE_NEW);
        }

        $paymentObj = $this->_order->getPayment();

        // set pspReference as transactionId
        $paymentObj->setCcTransId($this->_pspReference);
        $paymentObj->setLastTransId($this->_pspReference);

        // set transaction
        $paymentObj->setTransactionId($this->_pspReference);

        // Prepare transaction
        $transaction = $this->transactionBuilder->setPayment($paymentObj)
            ->setOrder($this->_order)
            ->setTransactionId($this->_pspReference)
            ->build(\Magento\Sales\Api\Data\TransactionInterface::TYPE_AUTH);

        $transaction->setIsClosed(false);

        $transaction->save();

        //capture mode
        if (!$this->_isAutoCapture()) {
            $this->_order->addStatusHistoryComment(__('Capture Mode set to Manual'));
            $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode is set to Manual');

            // show message if order is in manual review
            if ($this->_fraudManualReview) {
                // check if different status is selected
                $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
                if ($fraudManualReviewStatus != "") {
                    $status = $fraudManualReviewStatus;
                    $comment = "Adyen Payment is in Manual Review check the Adyen platform";
                    $this->_order->addStatusHistoryComment(__($comment), $status);
                }
            }

            $createPendingInvoice = (bool)$this->_getConfigData(
                'create_pending_invoice',
                'adyen_abstract',
                $this->_order->getStoreId()
            );

            if (!$createPendingInvoice) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Setting pending invoice is off so don\'t create an invoice wait for the capture notification'
                );
                return;
            }
        }

        // validate if amount is total amount
        $orderCurrencyCode = $this->_order->getOrderCurrencyCode();
        $amount = $this->_adyenHelper->originalAmount($this->_value, $orderCurrencyCode);

        try {
            // add to order payment
            $date = new \DateTime();
            $this->_adyenOrderPaymentFactory->create()
                ->setPspreference($this->_pspReference)
                ->setMerchantReference($this->_merchantReference)
                ->setPaymentId($paymentObj->getId())
                ->setPaymentMethod($this->_paymentMethod)
                ->setAmount($amount)
                ->setTotalRefunded(0)
                ->setCreatedAt($date)
                ->setUpdatedAt($date)
                ->save();
        } catch (\Exception $e) {
            $this->_adyenLogger->addError(
                'While processing a notification an exception occured. The payment has already been saved in the ' .
                'adyen_order_payment table but something went wrong later. Please check your logs for potential ' .
                'error messages regarding the merchant reference (order id): "' . $this->_merchantReference .
                '" and PSP reference: "' . $this->_pspReference . '"'
            );
        }

        if ($this->_isTotalAmount($paymentObj->getEntityId(), $orderCurrencyCode)) {
            $this->_createInvoice();
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'This is a partial AUTHORISATION and the full amount is not reached'
            );
        }
    }

    /**
     * @return bool
     */
    protected function _isAutoCapture()
    {
        // validate if payment methods allows manual capture
        if ($this->_manualCaptureAllowed()) {
            $captureMode = trim($this->_getConfigData(
                'capture_mode',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));
            $sepaFlow = trim($this->_getConfigData(
                'sepa_flow',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));
            $_paymentCode = $this->_paymentMethodCode();
            $captureModeOpenInvoice = $this->_getConfigData(
                'auto_capture_openinvoice',
                'adyen_abstract',
                $this->_order->getStoreId()
            );
            $manualCapturePayPal = trim($this->_getConfigData(
                'paypal_capture_mode',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));

            /*
             * if you are using authcap the payment method is manual.
             * There will be a capture send to indicate if payment is successful
             */
            if ($this->_paymentMethod == "sepadirectdebit" && $sepaFlow == "authcap") {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Manual Capture is applied for sepa because it is in authcap flow'
                );
                return false;
            }

            // payment method ideal, cash adyen_boleto has direct capture
            if ($this->_paymentMethod == "sepadirectdebit" && $sepaFlow != "authcap") {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method does not allow manual capture.(2) paymentCode:' .
                    $_paymentCode . ' paymentMethod:' . $this->_paymentMethod . ' sepaFLow:' . $sepaFlow
                );
                return true;
            }

            if ($_paymentCode == "adyen_pos_cloud") {
                $captureModePos = $this->_adyenHelper->getAdyenPosCloudConfigData('capture_mode_pos',
                    $this->_order->getStoreId());
                if (strcmp($captureModePos, 'auto') === 0) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is POS Cloud and configured to be working as auto capture '
                    );
                    return true;
                } elseif (strcmp($captureModePos, 'manual') === 0) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is POS Cloud and configured to be working as manual capture '
                    );
                    return false;
                }
            }

            // if auto capture mode for openinvoice is turned on then use auto capture
            if ($captureModeOpenInvoice == true &&
                $this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($this->_paymentMethod)
            ) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method is configured to be working as auto capture '
                );
                return true;
            }

            // if PayPal capture modues is different from the default use this one
            if (strcmp($this->_paymentMethod, 'paypal') === 0) {
                if ($manualCapturePayPal) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as manual capture'
                    );
                    return false;
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as auto capture'
                    );
                    return true;
                }
            }
            if (strcmp($captureMode, 'manual') === 0) {
                $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode for this payment is set to manual');
                return false;
            }

            /*
             * online capture after delivery, use Magento backend to online invoice
             * (if the option auto capture mode for openinvoice is not set)
             */
            if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($this->_paymentMethod)) {
                $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode for klarna is by default set to manual');
                return false;
            }

            $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode is set to auto capture');
            return true;
        } else {
            // does not allow manual capture so is always immediate capture
            $this->_adyenLogger->addAdyenNotificationCronjob('This payment method does not allow manual capture');
            return true;
        }
    }

    /**
     * Validate if this payment methods allows manual capture
     * This is a default can be forced differently to overrule on acquirer level
     *
     * @return bool|null
     */
    protected function _manualCaptureAllowed()
    {
        $manualCaptureAllowed = null;
        $paymentMethod = $this->_paymentMethod;

        // For all openinvoice methods manual capture is the default
        if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod)) {
            return true;
        }

        switch ($paymentMethod) {
            case 'cup':
            case 'cartebancaire':
            case 'visa':
            case 'visadankort':
            case 'mc':
            case 'uatp':
            case 'amex':
            case 'maestro':
            case 'maestrouk':
            case 'diners':
            case 'discover':
            case 'jcb':
            case 'laser':
            case 'paypal':
            case 'sepadirectdebit':
            case 'dankort':
            case 'elo':
            case 'hipercard':
            case 'mc_applepay':
            case 'visa_applepay':
            case 'amex_applepay':
            case 'discover_applepay':
            case 'maestro_applepay':
            case 'paywithgoogle':
                $manualCaptureAllowed = true;
                break;
            default:
                $manualCaptureAllowed = false;
        }

        return $manualCaptureAllowed;
    }

    /**
     * @return bool
     */
    protected function _isBankTransfer()
    {
        if (strlen($this->_paymentMethod) >= 12 && substr($this->_paymentMethod, 0, 12) == "bankTransfer") {
            $isBankTransfer = true;
        } else {
            $isBankTransfer = false;
        }
        return $isBankTransfer;
    }

    /**
     * @return mixed
     */
    protected function _getFraudManualReviewStatus()
    {
        return $this->_getConfigData(
            'fraud_manual_review_status',
            'adyen_abstract',
            $this->_order->getStoreId()
        );
    }

    /**
     * @return mixed
     */
    protected function _getFraudManualReviewAcceptStatus()
    {
        return $this->_getConfigData(
            'fraud_manual_review_accept_status',
            'adyen_abstract',
            $this->_order->getStoreId()
        );
    }

    /**
     * @param int $paymentId
     * @param string $orderCurrencyCode
     * @return bool
     */
    protected function _isTotalAmount($paymentId, $orderCurrencyCode)
    {
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Validate if AUTHORISATION notification has the total amount of the order'
        );

        // get total amount of the order
        $grandTotal = (int)$this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $orderCurrencyCode);

        // check if total amount of the order is authorised
        $res = $this->_adyenOrderPaymentCollectionFactory
            ->create()
            ->getTotalAmount($paymentId);

        if ($res && isset($res[0]) && is_array($res[0])) {
            $amount = $res[0]['total_amount'];
            $orderAmount = $this->_adyenHelper->formatAmount($amount, $orderCurrencyCode);
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf(
                    'The grandtotal amount is %s and the total order amount that is authorised is: %s',
                    $grandTotal,
                    $orderAmount
                )
            );

            if ($grandTotal == $orderAmount) {
                $this->_adyenLogger->addAdyenNotificationCronjob('AUTHORISATION has the full amount');
                return true;
            } else {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This is a partial AUTHORISATION, the amount is ' . $this->_value
                );
                return false;
            }
        }
        return false;
    }

    /**
     * @return void
     * @throws \Magento\Framework\Exception\LocalizedException
     * @throws Exception
     */
    protected function _createInvoice()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating invoice for order');

        if ($this->_order->canInvoice()) {
            /* We do not use this inside a transaction because order->save()
             * is always done on the end of the notification
             * and it could result in a deadlock see https://github.com/Adyen/magento/issues/334
             */
            try {
                $invoice = $this->_order->prepareInvoice();
                $invoice->getOrder()->setIsInProcess(true);

                // set transaction id so you can do a online refund from credit memo
                $invoice->setTransactionId($this->_pspReference);


                $autoCapture = $this->_isAutoCapture();
                $createPendingInvoice = (bool)$this->_getConfigData(
                    'create_pending_invoice',
                    'adyen_abstract',
                    $this->_order->getStoreId()
                );

                if ((!$autoCapture) && ($createPendingInvoice)) {
                    // if amount is zero create a offline invoice
                    $value = (int)$this->_value;
                    if ($value == 0) {
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
                    } else {
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::NOT_CAPTURE);
                    }

                    $invoice->register();
                } else {
                    $invoice->register()->pay();
                }

                $invoice->save();
                $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice');

                /*
                 * Add invoice in the adyen_invoice table
                 */
                $this->_adyenInvoiceFactory->create()
                    ->setInvoiceId($invoice->getEntityId())
                    ->setPspreference($this->_pspReference)
                    ->setOriginalReference($this->_pspReference)
                    ->setAcquirerReference($this->_acquirerReference)
                    ->save();

                $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice entry in the Adyen table');
            } catch (Exception $e) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Error saving invoice. The error message is: ' . $e->getMessage()
                );
                throw new Exception(sprintf('Error saving invoice. The error message is:', $e->getMessage()));
            }

            $this->_setPaymentAuthorized();

            $invoiceAutoMail = (bool)$this->_scopeConfig->isSetFlag(
                \Magento\Sales\Model\Order\Email\Container\InvoiceIdentity::XML_PATH_EMAIL_ENABLED,
                \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
                $this->_order->getStoreId()
            );

            if ($invoiceAutoMail) {
                $this->_invoiceSender->send($invoice);
            }
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob('It is not possible to create invoice for this order');
        }
    }

    /**
     * @param bool $manualReviewComment
     * @param bool $createInvoice
     * @throws Exception
     */
    protected function _setPaymentAuthorized($manualReviewComment = true, $createInvoice = false)
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Set order to authorised');

        // if full amount is captured create invoice
        $currency = $this->_order->getOrderCurrencyCode();
        $amount = $this->_value;
        $orderAmount = (int)$this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $currency);

        // create invoice for the capture notification if you are on manual capture
        if ($createInvoice == true && $amount == $orderAmount) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'amount notification:' . $amount . ' amount order:' . $orderAmount
            );
            $this->_createInvoice();
        }

        $status = $this->_getConfigData(
            'payment_authorized',
            'adyen_abstract',
            $this->_order->getStoreId()
        );

        // virtual order can have different status
        if ($this->_order->getIsVirtual()) {
            $this->_adyenLogger->addAdyenNotificationCronjob('Product is a virtual product');
            $virtualStatus = $this->_getConfigData(
                'payment_authorized_virtual',
                'adyen_abstract',
                $this->_order->getStoreId()
            );
            if ($virtualStatus != "") {
                $status = $virtualStatus;
            }
        }

        // check for boleto if payment is totally paid
        if ($this->_paymentMethodCode() == "adyen_boleto") {
            // check if paid amount is the same as orginal amount
            $orginalAmount = $this->_boletoOriginalAmount;
            $paidAmount = $this->_boletoPaidAmount;

            if ($orginalAmount != $paidAmount) {
                // not the full amount is paid. Check if it is underpaid or overpaid
                // strip the  BRL of the string
                $orginalAmount = str_replace("BRL", "", $orginalAmount);
                $orginalAmount = floatval(trim($orginalAmount));

                $paidAmount = str_replace("BRL", "", $paidAmount);
                $paidAmount = floatval(trim($paidAmount));

                if ($paidAmount > $orginalAmount) {
                    $overpaidStatus = $this->_getConfigData(
                        'order_overpaid_status',
                        'adyen_boleto',
                        $this->_order->getStoreId()
                    );
                    // check if there is selected a status if not fall back to the default
                    $status = (!empty($overpaidStatus)) ? $overpaidStatus : $status;
                } else {
                    $underpaidStatus = $this->_getConfigData(
                        'order_underpaid_status',
                        'adyen_boleto',
                        $this->_order->getStoreId()
                    );
                    // check if there is selected a status if not fall back to the default
                    $status = (!empty($underpaidStatus)) ? $underpaidStatus : $status;
                }
            }
        }

        $comment = "Adyen Payment Successfully completed";

        // if manual review is true use the manual review status if this is set
        if ($manualReviewComment == true && $this->_fraudManualReview) {
            // check if different status is selected
            $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
            if ($fraudManualReviewStatus != "") {
                $status = $fraudManualReviewStatus;
                $comment = "Adyen Payment is in Manual Review check the Adyen platform";
            }
        }
        $status = (!empty($status)) ? $status : $this->_order->getStatus();
        $this->_order->addStatusHistoryComment(__($comment), $status);
        $this->_setState($status);

        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Order status is changed to authorised status, status is ' . $status
        );
    }

    /**
     * Set State from Status
     */

    protected function _setState($status)
    {
        $statusObject = $this->_orderStatusCollection->create()
            ->addFieldToFilter('main_table.status', $status)
            ->addFieldToFilter('state_table.is_default', true)
            ->joinStates()
            ->getFirstItem();

        $this->_order->setState($statusObject->getState());
        $this->_adyenLogger->addAdyenNotificationCronjob('State is changed to  ' . $statusObject->getState());
    }

    /**
     * Create shipment
     *
     * @throws bool
     */
    protected function _createShipment()
    {
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating shipment for order');
        // create shipment for cash payment
        $payment = $this->_order->getPayment()->getMethodInstance();
        if ($this->_order->canShip()) {
            $itemQty = [];
            $shipment = $this->_order->prepareShipment($itemQty);
            if ($shipment) {
                $shipment->register();
                $shipment->getOrder()->setIsInProcess(true);
                $comment = __('Shipment created by Adyen');
                $shipment->addComment($comment);

                /** @var \Magento\Framework\DB\Transaction $transaction */
                $transaction = $this->_transactionFactory->create();
                $transaction->addObject($shipment)
                    ->addObject($shipment->getOrder())
                    ->save();

                $this->_adyenLogger->addAdyenNotificationCronjob('Order is shipped');
            }
        } else {
            $this->_adyenLogger->addAdyenNotificationCronjob('Order can\'t be shipped');
        }
    }

    /**
     * Retrieve information from payment configuration
     *
     * @param $field
     * @param string $paymentMethodCode
     * @param $storeId
     * @return mixed
     */
    protected function _getConfigData($field, $paymentMethodCode = 'adyen_cc', $storeId)
    {
        $path = 'payment/' . $paymentMethodCode . '/' . $field;
        return $this->_scopeConfig->getValue($path, \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeId);
    }

    /**
     * Add admin notice message for refund failed notification
     *
     * @return void
     */
    protected function addRefundFailedNotice()
    {
        $this->notifierPool->addNotice(
            __("Adyen: Refund for order #%1 has failed", $this->_merchantReference),
            __("Reason: %1 | PSPReference: %2 | You can go to Adyen Customer Area and trigger this refund manually or contact our support.",
                $this->_reason, $this->_pspReference),
            $this->_adyenHelper->getPspReferenceSearchUrl($this->_pspReference, $this->_live)
        );
    }

    /**
     * Add/update info on notification processing errors
     *
     * @param \Adyen\Payment\Model\Notification $notification
     * @param string $errorMessage
     * @return void
     */
    private function handleNotificationError($notification, $errorMessage)
    {
        $this->setNotificationError($notification, $errorMessage);
        $this->addNotificationErrorComment($errorMessage);
    }

    /**
     * Increases error count and appends error message to notification
     *
     * @param \Adyen\Payment\Model\Notification $notification
     * @param string $errorMessage
     * @return void
     */
    private function setNotificationError($notification, $errorMessage)
    {
        $notification->setErrorCount($notification->getErrorCount() + 1);
        $oldMessage = $notification->getErrorMessage();
        $newMessage = sprintf(
            "[%s]: %s",
            $this->timezone->formatDateTime($notification->getUpdatedAt()),
            $errorMessage
        );
        if (empty($oldMessage)) {
            $notification->setErrorMessage($newMessage);
        } else {
            $notification->setErrorMessage($oldMessage . "\n" . $newMessage);
        }
        $notification->save();
    }

    /**
     * Adds a comment to the order history with the notification processing error
     *
     * @param string $errorMessage
     * @return void
     */
    private function addNotificationErrorComment($errorMessage)
    {
        $comment = __('The order failed to update: %1', $errorMessage);
        if ($this->_order) {
            $this->_order->addStatusHistoryComment($comment);
            $this->_order->save();
        }
    }
}