We will work on Apr 26th (Saturday) and will be off from Apr 30th (Wednesday) until May 2nd (Friday) for public holiday in our country

Cron.php 68 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/**
 *                       ######
 *                       ######
 * ############    ####( ######  #####. ######  ############   ############
 * #############  #####( ######  #####. ######  #############  #############
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
 * #############  #############  #############  #############  #####  ######
 *  ############   ############  #############   ############  #####  ######
 *                                      ######
 *                               #############
 *                               ############
 *
 * 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>
 */
23 24 25

namespace Adyen\Payment\Model;

26
use Magento\Framework\Api\SearchCriteriaBuilder;
27
use Magento\Framework\Webapi\Exception;
28
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
29
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
30
use Magento\Framework\App\Area;
31 32 33
use Magento\Framework\App\AreaList;
use Magento\Framework\Phrase\Renderer\Placeholder;
use Magento\Framework\Phrase;
34
use Magento\Sales\Model\OrderRepository;
35 36 37 38 39 40 41 42 43 44

class Cron
{

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

45
    /**
46
     * @var ResourceModel\Notification\CollectionFactory
47
     */
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
    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;

67 68 69
    /**
     * @var \Adyen\Payment\Helper\Data
     */
70 71 72 73 74 75 76
    protected $_adyenHelper;

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

77 78 79 80 81
    /**
     * @var InvoiceSender
     */
    protected $_invoiceSender;

rikterbeek's avatar
rikterbeek committed
82 83 84 85 86
    /**
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $_transactionFactory;

87 88 89 90 91 92
    /**
     * @var \Adyen\Payment\Model\Billing\AgreementFactory
     */
    protected $_billingAgreementFactory;

    /**
93
     * @var ResourceModel\Billing\Agreement\CollectionFactory
94 95 96 97 98 99 100 101
     */
    protected $_billingAgreementCollectionFactory;

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

102 103 104
    /**
     * notification attributes
     */
105
    protected $_pspReference;
106

107 108 109 110 111
    /**
     * @var
     */
    protected $_originalReference;

112 113 114
    /**
     * @var
     */
115
    protected $_merchantReference;
116

117 118 119 120 121
    /**
     * @var
     */
    protected $_acquirerReference;

122 123 124 125 126
    /**
     * @var
     */
    protected $ratepayDescriptor;

127 128 129
    /**
     * @var
     */
130
    protected $_eventCode;
131 132 133 134

    /**
     * @var
     */
135
    protected $_success;
136 137 138 139

    /**
     * @var
     */
140
    protected $_paymentMethod;
141 142 143 144

    /**
     * @var
     */
145
    protected $_reason;
146 147 148 149

    /**
     * @var
     */
150
    protected $_value;
151 152 153 154

    /**
     * @var
     */
155
    protected $_boletoOriginalAmount;
156 157 158 159

    /**
     * @var
     */
160
    protected $_boletoPaidAmount;
161 162 163 164

    /**
     * @var
     */
165
    protected $_modificationResult;
166 167 168 169

    /**
     * @var
     */
170
    protected $_klarnaReservationNumber;
171 172 173 174

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

177 178 179 180 181 182
    /**
     * @var Order\PaymentFactory
     */
    protected $_adyenOrderPaymentFactory;

    /**
183
     * @var ResourceModel\Order\Payment\CollectionFactory
184 185 186
     */
    protected $_adyenOrderPaymentCollectionFactory;

187 188 189 190 191
    /**
     * @var ResourceModel\InvoiceFactory
     */
    protected $_adyenInvoiceFactory;

192
    /**
193
     * @var AreaList
194
     */
195
    protected $_areaList;
196

Bas Maassen's avatar
Bas Maassen committed
197 198 199 200 201
    /**
     * @var \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory
     */
    protected $_orderStatusCollection;

202 203 204 205 206 207 208 209 210 211
    /**
     * @var SearchCriteriaBuilder
     */
    private $searchCriteriaBuilder;

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

212
    /**
213 214
     * Cron constructor.
     *
215 216
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
217
     * @param ResourceModel\Notification\CollectionFactory $notificationFactory
218 219 220
     * @param \Magento\Sales\Model\OrderFactory $orderFactory
     * @param \Adyen\Payment\Helper\Data $adyenHelper
     * @param OrderSender $orderSender
221
     * @param InvoiceSender $invoiceSender
222
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
223
     * @param Billing\AgreementFactory $billingAgreementFactory
224
     * @param ResourceModel\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory
225
     * @param Api\PaymentRequest $paymentRequest
226
     * @param Order\PaymentFactory $adyenOrderPaymentFactory
227
     * @param ResourceModel\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory
Bas Maassen's avatar
Bas Maassen committed
228
     * @param InvoiceFactory $adyenInvoiceFactory
229
     * @param AreaList $areaList
Bas Maassen's avatar
Bas Maassen committed
230
     * @param \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $orderStatusCollection
231 232
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param OrderRepository $orderRepository
233 234 235 236
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Adyen\Payment\Logger\AdyenLogger $adyenLogger,
237
        \Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory $notificationFactory,
238 239
        \Magento\Sales\Model\OrderFactory $orderFactory,
        \Adyen\Payment\Helper\Data $adyenHelper,
rikterbeek's avatar
rikterbeek committed
240
        OrderSender $orderSender,
241
        InvoiceSender $invoiceSender,
242 243
        \Magento\Framework\DB\TransactionFactory $transactionFactory,
        \Adyen\Payment\Model\Billing\AgreementFactory $billingAgreementFactory,
244
        \Adyen\Payment\Model\ResourceModel\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory,
245 246
        \Adyen\Payment\Model\Api\PaymentRequest $paymentRequest,
        \Adyen\Payment\Model\Order\PaymentFactory $adyenOrderPaymentFactory,
247
        \Adyen\Payment\Model\ResourceModel\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory,
248
        \Adyen\Payment\Model\InvoiceFactory $adyenInvoiceFactory,
Bas Maassen's avatar
Bas Maassen committed
249
        AreaList $areaList,
250 251 252
        \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $orderStatusCollection,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        OrderRepository $orderRepository
253
    ) {
254
        $this->_scopeConfig = $scopeConfig;
255
        $this->_adyenLogger = $adyenLogger;
256 257 258 259
        $this->_notificationFactory = $notificationFactory;
        $this->_orderFactory = $orderFactory;
        $this->_adyenHelper = $adyenHelper;
        $this->_orderSender = $orderSender;
260
        $this->_invoiceSender = $invoiceSender;
rikterbeek's avatar
rikterbeek committed
261
        $this->_transactionFactory = $transactionFactory;
262 263 264
        $this->_billingAgreementFactory = $billingAgreementFactory;
        $this->_billingAgreementCollectionFactory = $billingAgreementCollectionFactory;
        $this->_adyenPaymentRequest = $paymentRequest;
265 266
        $this->_adyenOrderPaymentFactory = $adyenOrderPaymentFactory;
        $this->_adyenOrderPaymentCollectionFactory = $adyenOrderPaymentCollectionFactory;
267
        $this->_adyenInvoiceFactory = $adyenInvoiceFactory;
268
        $this->_areaList = $areaList;
Bas Maassen's avatar
Bas Maassen committed
269
        $this->_orderStatusCollection = $orderStatusCollection;
270 271
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->orderRepository = $orderRepository;
272 273
    }

274 275 276 277
    /**
     * Process the notification
     * @return void
     */
278
    public function processNotification()
279 280 281
    {
        try {
            $this->execute();
Rik ter Beek's avatar
Rik ter Beek committed
282
        } catch (\Exception $e) {
283 284 285 286 287 288
            $this->_adyenLogger->addAdyenNotificationCronjob($e->getMessage() . "\n" . $e->getTraceAsString());
            throw $e;
        }
    }

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

296 297
        $this->_order = null;

298
        // execute notifications from 2 minute or earlier because order could not yet been created by magento
299
        $dateStart = new \DateTime();
300
        $dateStart->modify('-5 day');
301
        $dateEnd = new \DateTime();
302
        $dateEnd->modify('-1 minute');
303 304
        $dateRange = ['from' => $dateStart, 'to' => $dateEnd, 'datetime' => true];

305
        // create collection
306 307
        $notifications = $this->_notificationFactory->create();
        $notifications->addFieldToFilter('done', 0);
308
        $notifications->addFieldToFilter('processing', 0);
309 310
        $notifications->addFieldToFilter('created_at', $dateRange);

311 312
        foreach ($notifications as $notification) {
            // set Cron processing to true
313
            $this->_updateNotification($notification, true, false);
314 315
        }

316
        // loop over the notifications
317
        $count = 0;
318
        foreach ($notifications as $notification) {
319 320 321 322
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf("Processing notification %s", $notification->getEntityId())
            );

323 324 325 326 327 328 329 330 331
            // 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;
            }
332

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

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

339 340 341 342 343 344 345 346 347 348 349
            $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) {
350 351 352
                // order does not exists remove from queue
                $notification->delete();
                continue;
353 354 355 356 357 358 359 360 361
            }

            // 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');
362

363 364
            // update order details
            $this->_updateAdyenAttributes($notification);
365 366 367

            // check if success is true of false
            if (strcmp($this->_success, 'false') == 0 || strcmp($this->_success, '0') == 0) {
368 369 370 371 372 373
                /*
                 * 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 ||
374 375
                    $this->_eventCode == Notification::ORDER_CLOSED
                ) {
376
                    $this->_adyenLogger->addAdyenNotificationCronjob('Going to cancel the order');
377 378

                    // if payment is API check, check if API result pspreference is the same as reference
379
                    if ($this->_eventCode == NOTIFICATION::AUTHORISATION && $this->_getPaymentMethodType() == 'api') {
380
                        // don't cancel the order becasue order was successfull through api
381 382 383
                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'order is not cancelled because api result was succesfull'
                        );
384
                    } else {
385 386 387 388 389 390
                        /*
                         * 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" ||
391 392
                            $this->_eventCode == Notification::ORDER_CLOSED
                        ) {
393 394 395
                            $this->_holdCancelOrder(false);
                        } else {
                            $this->_order->setData('adyen_notification_event_code', $previousAdyenEventCode);
396 397 398 399
                            $this->_adyenLogger->addAdyenNotificationCronjob(
                                'order is not cancelled because previous notification 
                                was an authorisation that succeeded'
                            );
400 401 402
                        }
                    }
                } else {
403 404 405
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'Order is already processed so ignore this notification state is:' . $this->_order->getState()
                    );
406 407 408 409 410 411 412
                }
            } else {
                // Notification is successful
                $this->_processNotification();
            }

            $this->_order->save();
413
            // set done to true
414
            $this->_updateNotification($notification, false, true);
415 416 417 418 419 420 421 422
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf("Notification %s is processed", $notification->getEntityId())
            );
            ++$count;
        }

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

426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    /**
     * @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(
450 451 452 453 454
            $notification->getPspreference(),
            $notification->getEventCode(),
            $notification->getSuccess(),
            $notification->getOriginalReference(),
            true
455 456 457
        );
    }

458 459 460 461 462 463
    /**
     * Declare private variables for processing notification
     *
     * @param Object $notification
     * @return void
     */
464 465 466 467
    protected function _declareVariables($notification)
    {
        //  declare the common parameters
        $this->_pspReference = $notification->getPspreference();
468
        $this->_originalReference = $notification->getOriginalReference();
469 470 471 472 473 474 475 476 477 478 479
        $this->_merchantReference = $notification->getMerchantReference();
        $this->_eventCode = $notification->getEventCode();
        $this->_success = $notification->getSuccess();
        $this->_paymentMethod = $notification->getPaymentMethod();
        $this->_reason = $notification->getPaymentMethod();
        $this->_value = $notification->getAmountValue();


        $additionalData = unserialize($notification->getAdditionalData());

        // boleto data
480 481
        if ($this->_paymentMethodCode() == "adyen_boleto") {
            if ($additionalData && is_array($additionalData)) {
482
                $boletobancario = isset($additionalData['boletobancario']) ? $additionalData['boletobancario'] : null;
483 484 485 486 487
                if ($boletobancario && is_array($boletobancario)) {
                    $this->_boletoOriginalAmount =
                        isset($boletobancario['originalAmount']) ? trim($boletobancario['originalAmount']) : "";
                    $this->_boletoPaidAmount =
                        isset($boletobancario['paidAmount']) ? trim($boletobancario['paidAmount']) : "";
488 489 490 491
                }
            }
        }

492
        if ($additionalData && is_array($additionalData)) {
493
            // check if the payment is in status manual review
494 495 496
            $fraudManualReview = isset($additionalData['fraudManualReview']) ?
                $additionalData['fraudManualReview'] : "";
            if ($fraudManualReview == "true") {
497 498 499 500 501
                $this->_fraudManualReview = true;
            } else {
                $this->_fraudManualReview = false;
            }

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

509
            $modification = isset($additionalData['modification']) ? $additionalData['modification'] : null;
510
            if ($modification && is_array($modification)) {
511
                $this->_modificationResult = isset($modification['action']) ? trim($modification['action']) : "";
512 513
            }
            $additionalData2 = isset($additionalData['additionalData']) ? $additionalData['additionalData'] : null;
514
            if ($additionalData2 && is_array($additionalData2)) {
515 516
                $this->_klarnaReservationNumber = isset($additionalData2['acquirerReference']) ? trim($additionalData2['acquirerReference']) : "";
            }
517 518 519 520
            $acquirerReference = isset($additionalData['acquirerReference']) ? $additionalData['acquirerReference'] : null;
            if ($acquirerReference != "") {
                $this->_acquirerReference = $acquirerReference;
            }
521 522 523 524
            $ratepayDescriptor = isset($additionalData['openinvoicedata.descriptor']) ? $additionalData['openinvoicedata.descriptor'] : "";
            if ($ratepayDescriptor !== "") {
                $this->ratepayDescriptor = $ratepayDescriptor;
            }
525 526 527 528 529 530 531 532 533 534 535
        }
    }

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

536 537 538 539 540
    /**
     * @return mixed
     */
    protected function _getPaymentMethodType()
    {
rikterbeek's avatar
rikterbeek committed
541 542 543
        return $this->_order->getPayment()->getPaymentMethodType();
    }

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

554
        if ($this->_eventCode == Notification::REFUND || $this->_eventCode == Notification::CAPTURE) {
555 556 557 558
            $currency = $this->_order->getOrderCurrencyCode();

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

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

565 566
            if ($amount == $orderAmount) {
                $this->_order->setData(
567 568
                    'adyen_notification_event_code',
                    $this->_eventCode . " : " . strtoupper($successResult)
569
                );
570
            } else {
571
                $this->_order->setData(
572 573
                    'adyen_notification_event_code',
                    "(PARTIAL) " .
574 575
                    $this->_eventCode . " : " . strtoupper($successResult)
                );
576 577
            }
        } else {
578
            $this->_order->setData(
579 580
                'adyen_notification_event_code',
                $this->_eventCode . " : " . strtoupper($successResult)
581
            );
582 583
        }

Rik ter Beek's avatar
Rik ter Beek committed
584
        // if payment method is klarna, ratepay or openinvoice/afterpay show the reservartion number
Rik ter Beek's avatar
Rik ter Beek committed
585
        if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($this->_paymentMethod) && !empty($this->_klarnaReservationNumber)) {
586 587 588 589 590
            $klarnaReservationNumberText = "<br /> reservationNumber: " . $this->_klarnaReservationNumber;
        } else {
            $klarnaReservationNumberText = "";
        }

591
        if ($this->_boletoPaidAmount != null && $this->_boletoPaidAmount != "") {
592 593 594 595 596 597
            $boletoPaidAmountText = "<br /> Paid amount: " . $this->_boletoPaidAmount;
        } else {
            $boletoPaidAmountText = "";
        }

        $type = 'Adyen HTTP Notification(s):';
598 599 600 601 602 603 604 605 606 607 608
        $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
        );
609 610

        // If notification is pending status and pending status is set add the status change to the comment history
611
        if ($this->_eventCode == Notification::PENDING) {
612
            $pendingStatus = $this->_getConfigData(
613 614 615
                'pending_status',
                'adyen_abstract',
                $this->_order->getStoreId()
616
            );
617
            if ($pendingStatus != "") {
618
                $this->_order->addStatusHistoryComment($comment, $pendingStatus);
619 620 621
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Created comment history for this notification with status change to: ' . $pendingStatus
                );
622 623 624 625 626
                return;
            }
        }

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

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

640 641 642
    /**
     * @param $notification
     */
643 644
    protected function _updateAdyenAttributes($notification)
    {
645
        $this->_adyenLogger->addAdyenNotificationCronjob('Updating the Adyen attributes of the order');
646 647 648 649 650 651

        $additionalData = unserialize($notification->getAdditionalData());
        $_paymentCode = $this->_paymentMethodCode();

        if ($this->_eventCode == Notification::AUTHORISATION
            || $this->_eventCode == Notification::HANDLED_EXTERNALLY
652
        ) {
653 654 655 656 657 658
            /*
             * 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 ||
659 660
                strcmp($this->_success, '') == 0
            ) {
661 662 663 664 665 666 667 668 669 670
                $previousAdyenEventCode = $this->_order->getData('adyen_notification_event_code');
                if ($previousAdyenEventCode != "AUTHORISATION : TRUE") {
                    $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
                }
            } else {
                $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
            }
        }
    }

671 672 673
    /**
     * @param $additionalData
     */
674 675 676 677 678 679 680 681
    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'] : "";
682 683
            $acquirerReference = (isset($additionalData['acquirerReference'])) ?
                $additionalData['acquirerReference'] : "";
684
            $authCode = (isset($additionalData['authCode'])) ? $additionalData['authCode'] : "";
685 686
            $cardBin = (isset($additionalData['cardBin'])) ? $additionalData['cardBin'] : "";
            $expiryDate = (isset($additionalData['expiryDate'])) ? $additionalData['expiryDate'] : "";
687 688 689 690 691 692 693 694 695 696 697
        }

        // 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 != "") {
698
            $this->_order->getPayment()->setAdditionalInformation(
699 700
                'adyen_klarna_number',
                $this->_klarnaReservationNumber
701
            );
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
        }
        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);
        }
728 729 730 731 732 733
        if (!empty($cardBin)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_card_bin', $cardBin);
        }
        if (!empty($expiryDate)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_expiry_date', $expiryDate);
        }
734 735
        if ($this->ratepayDescriptor !== "") {
            $this->_order->getPayment()->setAdditionalInformation(
736 737
                'adyen_ratepay_descriptor',
                $this->ratepayDescriptor
738 739
            );
        }
740 741 742 743 744 745 746 747 748 749 750
    }

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

751
        if ($reason != "") {
752
            $reasonArray = explode(":", $reason);
753 754
            if ($reasonArray != null && is_array($reasonArray)) {
                if (isset($reasonArray[1])) {
755 756 757 758 759 760 761
                    $result = $reasonArray[1];
                }
            }
        }
        return $result;
    }

762
    /**
763 764
     * @param $ignoreHasInvoice
     * @throws \Magento\Framework\Exception\LocalizedException
765 766 767
     */
    protected function _holdCancelOrder($ignoreHasInvoice)
    {
768
        $orderStatus = $this->_getConfigData(
769 770 771
            'payment_cancelled',
            'adyen_abstract',
            $this->_order->getStoreId()
772
        );
773 774 775

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

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

790 791 792
                if ($this->_order->canCancel()) {
                    $this->_order->cancel();
                } else {
793
                    $this->_adyenLogger->addAdyenNotificationCronjob('Order can not be canceled');
794 795 796 797
                    return;
                }
            }
        } else {
798
            $this->_adyenLogger->addAdyenNotificationCronjob('Order has already an invoice so cannot be canceled');
799 800 801 802
        }
    }

    /**
803
     * Process the Notification
804 805 806
     */
    protected function _processNotification()
    {
807

808
        $this->_adyenLogger->addAdyenNotificationCronjob('Processing the notification');
809 810 811 812 813 814 815
        $_paymentCode = $this->_paymentMethodCode();

        switch ($this->_eventCode) {
            case Notification::REFUND_FAILED:
                // do nothing only inform the merchant with order comment history
                break;
            case Notification::REFUND:
816
                $ignoreRefundNotification = $this->_getConfigData(
817 818 819
                    'ignore_refund_notification',
                    'adyen_abstract',
                    $this->_order->getStoreId()
820 821
                );
                if ($ignoreRefundNotification != true) {
rikterbeek's avatar
rikterbeek committed
822
                    $this->_refundOrder();
823
                    //refund completed
rikterbeek's avatar
rikterbeek committed
824
                    $this->_setRefundAuthorized();
825
                } else {
826 827 828
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'Setting to ignore refund notification is enabled so ignore this notification'
                    );
829 830 831
                }
                break;
            case Notification::PENDING:
832
                if ($this->_getConfigData(
833 834 835 836
                    'send_email_bank_sepa_on_pending',
                    'adyen_abstract',
                    $this->_order->getStoreId()
                )
837
                ) {
838
                    // Check if payment is banktransfer or sepa if true then send out order confirmation email
rikterbeek's avatar
rikterbeek committed
839
                    $isBankTransfer = $this->_isBankTransfer();
840 841
                    if ($isBankTransfer || $this->_paymentMethod == 'sepadirectdebit') {
                        if (!$this->_order->getEmailSent()) {
842
                            $this->_sendOrderMail();
843
                        }
844 845 846 847 848
                    }
                }
                break;
            case Notification::HANDLED_EXTERNALLY:
            case Notification::AUTHORISATION:
849
                $this->_authorizePayment();
850 851 852 853 854
                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:
855 856 857 858
                /*
                 * only process this if you are on auto capture.
                 * On manual capture you will always get Capture or CancelOrRefund notification
                 */
859
                if ($this->_isAutoCapture()) {
860
                    $this->_setPaymentAuthorized(false);
861 862 863
                }
                break;
            case Notification::CAPTURE:
864 865 866 867 868 869 870
                /*
                 * 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);

871
                    /*
872
                     * Add invoice in the adyen_invoice table
873
                     */
874 875 876 877 878 879 880 881 882 883
                    $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');
884
                        }
885 886 887
                    }
                }
                break;
888
            case Notification::OFFER_CLOSED:
889
                if (!$this->_order->canCancel()) {
890 891 892 893 894
                    // 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;
895 896 897 898 899 900
            case Notification::CAPTURE_FAILED:
            case Notification::CANCELLATION:
            case Notification::CANCELLED:
                $this->_holdCancelOrder(true);
                break;
            case Notification::CANCEL_OR_REFUND:
901 902
                if (isset($this->_modificationResult) && $this->_modificationResult != "") {
                    if ($this->_modificationResult == "cancel") {
903
                        $this->_holdCancelOrder(true);
904
                    } elseif ($this->_modificationResult == "refund") {
rikterbeek's avatar
rikterbeek committed
905
                        $this->_refundOrder();
906
                        //refund completed
rikterbeek's avatar
rikterbeek committed
907
                        $this->_setRefundAuthorized();
908 909
                    }
                } else {
910
                    if ($this->_order->isCanceled() ||
911 912
                        $this->_order->getState() === \Magento\Sales\Model\Order::STATE_HOLDED
                    ) {
913 914 915
                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'Order is already cancelled or holded so do nothing'
                        );
916
                    } else {
917 918 919 920 921 922 923 924 925 926
                        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();
                        }
927 928 929
                    }
                }
                break;
930

931
            case Notification::RECURRING_CONTRACT:
932 933 934
                // storedReferenceCode
                $recurringDetailReference = $this->_pspReference;

935
                $storeId = $this->_order->getStoreId();
936
                $customerReference = $this->_order->getCustomerId();
937 938
                $listRecurringContracts = null;
                $this->_adyenLogger->addAdyenNotificationCronjob(
939 940 941 942 943 944
                    __(
                        'CustomerReference is: %1 and storeId is %2 and RecurringDetailsReference is %3',
                        $customerReference,
                        $storeId,
                        $recurringDetailReference
                    )
945 946 947
                );
                try {
                    $listRecurringContracts = $this->_adyenPaymentRequest->getRecurringContractsForShopper(
948 949
                        $customerReference,
                        $storeId
950 951 952 953
                    );
                    $contractDetail = null;
                    // get current Contract details and get list of all current ones
                    $recurringReferencesList = [];
954

955 956 957 958 959 960 961
                    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']) &&
962 963
                            $rc['recurringDetailReference'] == $recurringDetailReference
                        ) {
964 965
                            $contractDetail = $rc;
                        }
966 967
                    }

968 969 970 971 972 973 974 975
                    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);
                    }
976

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

980
                    // Get collection and update existing agreements
981

982 983 984 985 986 987 988 989
                    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
990
                            );
991
                        }
Aleffio's avatar
Aleffio committed
992
                        $updateBillingAgreement->save();
993
                    }
994

995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
                    // 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(),
                            ]
                        );
1008

1009 1010 1011 1012
                        $billingAgreement = $this->_billingAgreementFactory->create();
                        $billingAgreement->setStoreId($this->_order->getStoreId());
                        $billingAgreement->importOrderPayment($this->_order->getPayment());
                        $message = __('Created billing agreement #%1.', $recurringDetailReference);
1013
                    } else {
1014 1015
                        $this->_adyenLogger->addAdyenNotificationCronjob("Using existing Billing Agreement");
                        $billingAgreement->setIsObjectChanged(true);
Aleffio's avatar
Aleffio committed
1016
                        $message = __('Updated billing agreement #%1.', $recurringDetailReference);
1017
                    }
1018

1019 1020 1021 1022 1023
                    // Populate billing agreement data
                    $billingAgreement->parseRecurringContractData($contractDetail);
                    if ($billingAgreement->isValid()) {
                        // save into sales_billing_agreement_order
                        $billingAgreement->addOrderRelation($this->_order);
1024

1025 1026 1027 1028 1029
                        // add to order to save agreement
                        $this->_order->addRelatedObject($billingAgreement);
                    } else {
                        $message = __('Failed to create billing agreement for this order.');
                        throw new \Exception($message);
1030
                    }
1031
                } catch (\Exception $exception) {
1032
                    $message = $exception->getMessage();
1033
                }
1034 1035 1036 1037

                $this->_adyenLogger->addAdyenNotificationCronjob($message);
                $comment = $this->_order->addStatusHistoryComment($message);
                $this->_order->addRelatedObject($comment);
1038
                break;
1039
            default:
1040 1041 1042
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf('This notification event: %s is not supported so will be ignored', $this->_eventCode)
                );
1043 1044 1045 1046
                break;
        }
    }

rikterbeek's avatar
rikterbeek committed
1047 1048 1049 1050 1051 1052
    /**
     * Not implemented
     * @return bool
     */
    protected function _refundOrder()
    {
1053 1054
        $this->_adyenLogger->addAdyenNotificationCronjob('Refunding the order');

1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
        // 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();
1066
                $amountRefunded = $amountRefunded = $orderPayment->getTotalRefunded() +
1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
                    $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');
            }
        }

1077 1078 1079 1080
        /*
         * Don't create a credit memo if refund is initialize in Magento
         * because in this case the credit memo already exists
         */
1081
        $lastTransactionId = $this->_order->getPayment()->getLastTransId();
1082
        if ($lastTransactionId != $this->_pspReference) {
1083
            // refund is done through adyen backoffice so create a credit memo
1084 1085
            $order = $this->_order;
            if ($order->canCreditmemo()) {
1086 1087 1088
                $currency = $this->_order->getOrderCurrencyCode();
                $amount = $this->_adyenHelper->originalAmount($this->_value, $currency);
                $order->getPayment()->registerRefundNotification($amount);
1089

1090
                $this->_adyenLogger->addAdyenNotificationCronjob('Created credit memo for order');
1091 1092 1093 1094
            } else {
                $this->_adyenLogger->addAdyenNotificationCronjob('Could not create a credit memo for order');
            }
        } else {
1095
            $this->_adyenLogger->addAdyenNotificationCronjob(
1096
                'Did not create a credit memo for this order because refund is done through Magento'
1097
            );
1098
        }
rikterbeek's avatar
rikterbeek committed
1099 1100 1101 1102 1103 1104 1105
    }

    /**
     * @param $order
     */
    protected function _setRefundAuthorized()
    {
1106 1107 1108
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Status update to default status or refund_authorized status if this is set'
        );
rikterbeek's avatar
rikterbeek committed
1109 1110 1111
        $this->_order->addStatusHistoryComment(__('Adyen Refund Successfully completed'));
    }

1112
    /**
1113
     * authorize payment
1114 1115 1116
     */
    protected function _authorizePayment()
    {
1117
        $this->_adyenLogger->addAdyenNotificationCronjob('Authorisation of the order');
1118 1119 1120
        $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();

        // If manual review is active and a seperate status is used then ignore the pre authorized status
1121
        if ($this->_fraudManualReview != true || $fraudManualReviewStatus == "") {
1122 1123
            $this->_setPrePaymentAuthorized();
        } else {
1124 1125 1126 1127
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Ignore the pre authorized status because the order is ' .
                'under manual review and use the Manual review status'
            );
1128 1129 1130 1131 1132 1133
        }

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

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

1141 1142
        if ($this->_paymentMethod == "c_cash" &&
            $this->_getConfigData('create_shipment', 'adyen_cash', $this->_order->getStoreId())
1143
        ) {
rikterbeek's avatar
rikterbeek committed
1144
            $this->_createShipment();
1145 1146 1147
        }
    }

1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
    /**
     * Send order Mail
     *
     * @return void
     */
    private function _sendOrderMail()
    {
        try {
            $this->_orderSender->send($this->_order);
            $this->_adyenLogger->addAdyenNotificationCronjob('Send orderconfirmation email to shopper');
1158
        } catch (\Exception $exception) {
1159 1160 1161 1162 1163 1164 1165
            $this->_adyenLogger->addAdyenNotificationCronjob(
                "Exception in Send Mail in Magento. This is an issue in the the core of Magento" .
                $exception->getMessage()
            );
        }
    }

1166 1167
    /**
     * Set status on authorisation
1168 1169
     *
     * @return void
1170
     */
1171 1172
    private function _setPrePaymentAuthorized()
    {
1173
        $status = $this->_getConfigData(
1174 1175 1176
            'payment_pre_authorized',
            'adyen_abstract',
            $this->_order->getStoreId()
1177
        );
1178 1179

        // only do this if status in configuration is set
1180
        if (!empty($status)) {
rikterbeek's avatar
rikterbeek committed
1181
            $this->_order->addStatusHistoryComment(__('Payment is authorised waiting for capture'), $status);
Bas Maassen's avatar
Bas Maassen committed
1182 1183
            $this->_setState($status);

1184 1185 1186
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Order status is changed to Pre-authorised status, status is ' . $status
            );
1187
        } else {
1188
            $this->_adyenLogger->addAdyenNotificationCronjob('No pre-authorised status is used so ignore');
1189 1190 1191 1192
        }
    }

    /**
1193
     * @throws Exception
1194
     * @return void
1195 1196 1197
     */
    protected function _prepareInvoice()
    {
1198
        $this->_adyenLogger->addAdyenNotificationCronjob('Prepare invoice for order');
1199 1200 1201 1202 1203

        //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);
        }
1204

1205 1206 1207 1208 1209 1210 1211 1212 1213
        $paymentObj = $this->_order->getPayment();

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

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

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

            // show message if order is in manual review
1220
            if ($this->_fraudManualReview) {
1221 1222
                // check if different status is selected
                $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
1223
                if ($fraudManualReviewStatus != "") {
1224 1225 1226 1227 1228 1229
                    $status = $fraudManualReviewStatus;
                    $comment = "Adyen Payment is in Manual Review check the Adyen platform";
                    $this->_order->addStatusHistoryComment(__($comment), $status);
                }
            }

1230
            $createPendingInvoice = (bool)$this->_getConfigData(
1231 1232 1233
                'create_pending_invoice',
                'adyen_abstract',
                $this->_order->getStoreId()
1234 1235 1236 1237 1238 1239
            );

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

        // validate if amount is total amount
        $orderCurrencyCode = $this->_order->getOrderCurrencyCode();
1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
        $amount = $this->_adyenHelper->originalAmount($this->_value, $orderCurrencyCode);

        // 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();


        if ($this->_isTotalAmount($paymentObj->getEntityId(), $orderCurrencyCode)) {
1263
            $this->_createInvoice();
1264
        } else {
1265 1266 1267
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'This is a partial AUTHORISATION and the full amount is not reached'
            );
1268 1269 1270 1271 1272 1273 1274 1275
        }
    }

    /**
     * @return bool
     */
    protected function _isAutoCapture()
    {
Alessio Zampatti's avatar
Alessio Zampatti committed
1276
        // validate if payment methods allows manual capture
1277
        if ($this->_manualCaptureAllowed()) {
1278
            $captureMode = trim($this->_getConfigData(
1279 1280 1281 1282
                'capture_mode',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));
1283
            $sepaFlow = trim($this->_getConfigData(
1284 1285 1286 1287
                'sepa_flow',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));
1288
            $_paymentCode = $this->_paymentMethodCode();
1289
            $captureModeOpenInvoice = $this->_getConfigData(
1290 1291 1292
                'auto_capture_openinvoice',
                'adyen_abstract',
                $this->_order->getStoreId()
1293
            );
1294
            $manualCapturePayPal = trim($this->_getConfigData(
1295 1296 1297 1298
                'paypal_capture_mode',
                'adyen_abstract',
                $this->_order->getStoreId()
            ));
1299 1300 1301 1302 1303 1304

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

1313 1314 1315
            // payment method ideal, cash adyen_boleto has direct capture
            if (($_paymentCode == "adyen_sepa" ||
                    $this->_paymentMethod == "sepadirectdebit") && $sepaFlow != "authcap"
1316
            ) {
1317 1318
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method does not allow manual capture.(2) paymentCode:' .
1319
                    $_paymentCode . ' paymentMethod:' . $this->_paymentMethod . ' sepaFLow:' . $sepaFlow
1320
                );
1321 1322
                return true;
            }
1323

1324
            if ($_paymentCode == "adyen_pos_cloud") {
1325 1326
                $captureModePos = $this->_adyenHelper->getAdyenPosCloudConfigData('capture_mode_pos',
                    $this->_order->getStoreId());
1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339
                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;
                }
            }

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

1350
            // if PayPal capture modues is different from the default use this one
1351 1352
            if (strcmp($this->_paymentMethod, 'paypal') === 0) {
                if ($manualCapturePayPal) {
1353 1354 1355
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as manual capture'
                    );
1356
                    return false;
1357 1358 1359 1360 1361
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as auto capture'
                    );
                    return true;
1362 1363 1364
                }
            }
            if (strcmp($captureMode, 'manual') === 0) {
1365
                $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode for this payment is set to manual');
1366 1367
                return false;
            }
1368 1369 1370 1371 1372

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

            $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode is set to auto capture');
1379 1380 1381
            return true;
        } else {
            // does not allow manual capture so is always immediate capture
1382
            $this->_adyenLogger->addAdyenNotificationCronjob('This payment method does not allow manual capture');
1383
            return true;
1384
        }
1385 1386 1387 1388 1389
    }

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

Rik ter Beek's avatar
Rik ter Beek committed
1398 1399 1400 1401 1402
        // For all openinvoice methods manual capture is the default
        if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod)) {
            return true;
        }

1403
        switch ($paymentMethod) {
1404 1405 1406
            case 'cup':
            case 'cartebancaire':
            case 'visa':
1407
            case 'visadankort':
1408 1409 1410 1411
            case 'mc':
            case 'uatp':
            case 'amex':
            case 'maestro':
rikterbeek's avatar
rikterbeek committed
1412
            case 'maestrouk':
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
            case 'diners':
            case 'discover':
            case 'jcb':
            case 'laser':
            case 'paypal':
            case 'sepadirectdebit':
                $manualCaptureAllowed = true;
                break;
            default:
                $manualCaptureAllowed = false;
1423
        }
1424 1425

        return $manualCaptureAllowed;
1426 1427 1428 1429 1430
    }

    /**
     * @return bool
     */
1431 1432
    protected function _isBankTransfer()
    {
1433
        if (strlen($this->_paymentMethod) >= 12 && substr($this->_paymentMethod, 0, 12) == "bankTransfer") {
1434 1435 1436 1437 1438 1439 1440
            $isBankTransfer = true;
        } else {
            $isBankTransfer = false;
        }
        return $isBankTransfer;
    }

1441 1442 1443
    /**
     * @return mixed
     */
1444 1445
    protected function _getFraudManualReviewStatus()
    {
1446
        return $this->_getConfigData(
1447 1448 1449
            'fraud_manual_review_status',
            'adyen_abstract',
            $this->_order->getStoreId()
1450
        );
1451 1452
    }

1453 1454 1455
    /**
     * @return mixed
     */
1456 1457
    protected function _getFraudManualReviewAcceptStatus()
    {
1458
        return $this->_getConfigData(
1459 1460 1461
            'fraud_manual_review_accept_status',
            'adyen_abstract',
            $this->_order->getStoreId()
1462
        );
1463 1464
    }

1465
    /**
1466 1467
     * @param int $paymentId
     * @param string $orderCurrencyCode
1468 1469
     * @return bool
     */
1470
    protected function _isTotalAmount($paymentId, $orderCurrencyCode)
1471 1472 1473 1474
    {
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Validate if AUTHORISATION notification has the total amount of the order'
        );
1475

1476
        // get total amount of the order
1477
        $grandTotal = (int)$this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $orderCurrencyCode);
1478 1479 1480 1481 1482

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

1484
        if ($res && isset($res[0]) && is_array($res[0])) {
1485 1486
            $amount = $res[0]['total_amount'];
            $orderAmount = $this->_adyenHelper->formatAmount($amount, $orderCurrencyCode);
1487
            $this->_adyenLogger->addAdyenNotificationCronjob(
1488 1489
                sprintf(
                    'The grandtotal amount is %s and the total order amount that is authorised is: %s',
1490 1491 1492 1493
                    $grandTotal,
                    $orderAmount
                )
            );
1494 1495 1496 1497 1498 1499 1500 1501 1502 1503

            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;
            }
1504
        }
1505
        return false;
1506 1507
    }

1508 1509 1510
    /**
     * @throws Exception
     * @throws \Magento\Framework\Exception\LocalizedException
1511
     * @return void
1512
     */
1513 1514
    protected function _createInvoice()
    {
1515
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating invoice for order');
1516 1517

        if ($this->_order->canInvoice()) {
1518 1519
            /* We do not use this inside a transaction because order->save()
             * is always done on the end of the notification
1520 1521 1522 1523 1524 1525 1526
             * 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
1527
                $invoice->setTransactionId($this->_pspReference);
1528

1529

1530
                $autoCapture = $this->_isAutoCapture();
1531
                $createPendingInvoice = (bool)$this->_getConfigData(
1532 1533 1534
                    'create_pending_invoice',
                    'adyen_abstract',
                    $this->_order->getStoreId()
1535
                );
1536

1537
                if ((!$autoCapture) && ($createPendingInvoice)) {
1538 1539
                    // if amount is zero create a offline invoice
                    $value = (int)$this->_value;
1540
                    if ($value == 0) {
rikterbeek's avatar
rikterbeek committed
1541
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
1542
                    } else {
rikterbeek's avatar
rikterbeek committed
1543
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::NOT_CAPTURE);
1544 1545 1546 1547 1548 1549 1550 1551
                    }

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

                $invoice->save();
1552
                $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice');
1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564

                /*
                 * 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');
1565
            } catch (Exception $e) {
1566 1567 1568
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Error saving invoice. The error message is: ' . $e->getMessage()
                );
1569 1570 1571 1572 1573
                throw new Exception(sprintf('Error saving invoice. The error message is:', $e->getMessage()));
            }

            $this->_setPaymentAuthorized();

1574 1575 1576 1577
            $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()
1578 1579
            );

1580
            if ($invoiceAutoMail) {
1581
                $this->_invoiceSender->send($invoice);
1582 1583
            }
        } else {
1584
            $this->_adyenLogger->addAdyenNotificationCronjob('It is not possible to create invoice for this order');
1585 1586 1587 1588
        }
    }

    /**
1589 1590 1591
     * @param bool $manualReviewComment
     * @param bool $createInvoice
     * @throws Exception
1592 1593 1594
     */
    protected function _setPaymentAuthorized($manualReviewComment = true, $createInvoice = false)
    {
1595
        $this->_adyenLogger->addAdyenNotificationCronjob('Set order to authorised');
1596 1597 1598 1599

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

        // create invoice for the capture notification if you are on manual capture
1603 1604
        if ($createInvoice == true && $amount == $orderAmount) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
1605
                'amount notification:' . $amount . ' amount order:' . $orderAmount
1606
            );
rikterbeek's avatar
rikterbeek committed
1607
            $this->_createInvoice();
1608
        }
1609

1610
        $status = $this->_getConfigData(
1611 1612 1613
            'payment_authorized',
            'adyen_abstract',
            $this->_order->getStoreId()
1614
        );
1615 1616

        // virtual order can have different status
1617
        if ($this->_order->getIsVirtual()) {
1618
            $this->_adyenLogger->addAdyenNotificationCronjob('Product is a virtual product');
1619
            $virtualStatus = $this->_getConfigData(
1620 1621 1622
                'payment_authorized_virtual',
                'adyen_abstract',
                $this->_order->getStoreId()
1623
            );
1624 1625
            if ($virtualStatus != "") {
                $status = $virtualStatus;
1626 1627 1628 1629
            }
        }

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

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

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

1644
                if ($paidAmount > $orginalAmount) {
1645
                    $overpaidStatus = $this->_getConfigData(
1646 1647 1648
                        'order_overpaid_status',
                        'adyen_boleto',
                        $this->_order->getStoreId()
1649
                    );
1650 1651 1652
                    // check if there is selected a status if not fall back to the default
                    $status = (!empty($overpaidStatus)) ? $overpaidStatus : $status;
                } else {
1653
                    $underpaidStatus = $this->_getConfigData(
1654 1655 1656
                        'order_underpaid_status',
                        'adyen_boleto',
                        $this->_order->getStoreId()
1657
                    );
1658 1659 1660 1661 1662 1663 1664 1665 1666
                    // 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
1667
        if ($manualReviewComment == true && $this->_fraudManualReview) {
1668 1669
            // check if different status is selected
            $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
1670
            if ($fraudManualReviewStatus != "") {
1671 1672 1673 1674 1675 1676
                $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);
Bas Maassen's avatar
Bas Maassen committed
1677 1678
        $this->_setState($status);

1679 1680 1681
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Order status is changed to authorised status, status is ' . $status
        );
1682 1683
    }

Bas Maassen's avatar
Bas Maassen committed
1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698
    /**
     * 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());
    }
1699

rikterbeek's avatar
rikterbeek committed
1700
    /**
1701
     * Create shipment
rikterbeek's avatar
rikterbeek committed
1702
     *
1703
     * @throws bool
rikterbeek's avatar
rikterbeek committed
1704
     */
1705 1706
    protected function _createShipment()
    {
1707
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating shipment for order');
rikterbeek's avatar
rikterbeek committed
1708 1709
        // create shipment for cash payment
        $payment = $this->_order->getPayment()->getMethodInstance();
1710 1711
        if ($this->_order->canShip()) {
            $itemQty = [];
rikterbeek's avatar
rikterbeek committed
1712
            $shipment = $this->_order->prepareShipment($itemQty);
1713
            if ($shipment) {
rikterbeek's avatar
rikterbeek committed
1714 1715 1716 1717
                $shipment->register();
                $shipment->getOrder()->setIsInProcess(true);
                $comment = __('Shipment created by Adyen');
                $shipment->addComment($comment);
rikterbeek's avatar
rikterbeek committed
1718 1719 1720 1721

                /** @var \Magento\Framework\DB\Transaction $transaction */
                $transaction = $this->_transactionFactory->create();
                $transaction->addObject($shipment)
rikterbeek's avatar
rikterbeek committed
1722 1723
                    ->addObject($shipment->getOrder())
                    ->save();
rikterbeek's avatar
rikterbeek committed
1724

1725
                $this->_adyenLogger->addAdyenNotificationCronjob('Order is shipped');
rikterbeek's avatar
rikterbeek committed
1726 1727
            }
        } else {
1728
            $this->_adyenLogger->addAdyenNotificationCronjob('Order can\'t be shipped');
rikterbeek's avatar
rikterbeek committed
1729 1730 1731
        }
    }

1732 1733 1734
    /**
     * Retrieve information from payment configuration
     *
1735 1736 1737
     * @param $field
     * @param string $paymentMethodCode
     * @param $storeId
1738 1739 1740 1741 1742 1743 1744
     * @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);
    }
1745
}