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 67.8 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
Bas Maassen's avatar
Bas Maassen committed
253 254
    )
    {
255
        $this->_scopeConfig = $scopeConfig;
256
        $this->_adyenLogger = $adyenLogger;
257 258 259 260
        $this->_notificationFactory = $notificationFactory;
        $this->_orderFactory = $orderFactory;
        $this->_adyenHelper = $adyenHelper;
        $this->_orderSender = $orderSender;
261
        $this->_invoiceSender = $invoiceSender;
rikterbeek's avatar
rikterbeek committed
262
        $this->_transactionFactory = $transactionFactory;
263 264 265
        $this->_billingAgreementFactory = $billingAgreementFactory;
        $this->_billingAgreementCollectionFactory = $billingAgreementCollectionFactory;
        $this->_adyenPaymentRequest = $paymentRequest;
266 267
        $this->_adyenOrderPaymentFactory = $adyenOrderPaymentFactory;
        $this->_adyenOrderPaymentCollectionFactory = $adyenOrderPaymentCollectionFactory;
268
        $this->_adyenInvoiceFactory = $adyenInvoiceFactory;
269
        $this->_areaList = $areaList;
Bas Maassen's avatar
Bas Maassen committed
270
        $this->_orderStatusCollection = $orderStatusCollection;
271 272
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->orderRepository = $orderRepository;
273 274
    }

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

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

297 298
        $this->_order = null;

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

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

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

317
        // loop over the notifications
318
        $count = 0;
319
        foreach ($notifications as $notification) {
320

321 322 323 324
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf("Processing notification %s", $notification->getEntityId())
            );

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

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

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

341 342 343 344 345 346 347 348 349 350 351
            $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) {
352 353 354 355

                // order does not exists remove from queue
                $notification->delete();
                continue;
356 357 358 359 360 361 362 363 364
            }

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

366 367
            // update order details
            $this->_updateAdyenAttributes($notification);
368 369 370

            // check if success is true of false
            if (strcmp($this->_success, 'false') == 0 || strcmp($this->_success, '0') == 0) {
371 372 373 374 375 376
                /*
                 * 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 ||
377 378
                    $this->_eventCode == Notification::ORDER_CLOSED
                ) {
379

380
                    $this->_adyenLogger->addAdyenNotificationCronjob('Going to cancel the order');
381 382

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

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

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

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
    /**
     * @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
        );
    }

459 460 461 462 463 464
    /**
     * Declare private variables for processing notification
     *
     * @param Object $notification
     * @return void
     */
465 466 467 468
    protected function _declareVariables($notification)
    {
        //  declare the common parameters
        $this->_pspReference = $notification->getPspreference();
469
        $this->_originalReference = $notification->getOriginalReference();
470 471 472 473 474 475 476 477 478 479 480
        $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
481 482
        if ($this->_paymentMethodCode() == "adyen_boleto") {
            if ($additionalData && is_array($additionalData)) {
483
                $boletobancario = isset($additionalData['boletobancario']) ? $additionalData['boletobancario'] : null;
484 485 486 487 488
                if ($boletobancario && is_array($boletobancario)) {
                    $this->_boletoOriginalAmount =
                        isset($boletobancario['originalAmount']) ? trim($boletobancario['originalAmount']) : "";
                    $this->_boletoPaidAmount =
                        isset($boletobancario['paidAmount']) ? trim($boletobancario['paidAmount']) : "";
489 490 491 492
                }
            }
        }

493
        if ($additionalData && is_array($additionalData)) {
494 495

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

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

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

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

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

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

556
        if ($this->_eventCode == Notification::REFUND || $this->_eventCode == Notification::CAPTURE) {
557 558 559 560 561

            $currency = $this->_order->getOrderCurrencyCode();

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

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

568 569 570 571
            if ($amount == $orderAmount) {
                $this->_order->setData(
                    'adyen_notification_event_code', $this->_eventCode . " : " . strtoupper($successResult)
                );
572
            } else {
573 574 575 576
                $this->_order->setData(
                    'adyen_notification_event_code', "(PARTIAL) " .
                    $this->_eventCode . " : " . strtoupper($successResult)
                );
577 578
            }
        } else {
579 580 581
            $this->_order->setData(
                'adyen_notification_event_code', $this->_eventCode . " : " . strtoupper($successResult)
            );
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
        $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);
601 602

        // If notification is pending status and pending status is set add the status change to the comment history
603
        if ($this->_eventCode == Notification::PENDING) {
604 605 606
            $pendingStatus = $this->_getConfigData(
                'pending_status', 'adyen_abstract', $this->_order->getStoreId()
            );
607
            if ($pendingStatus != "") {
608
                $this->_order->addStatusHistoryComment($comment, $pendingStatus);
609 610 611
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Created comment history for this notification with status change to: ' . $pendingStatus
                );
612 613 614 615 616
                return;
            }
        }

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

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

630 631 632
    /**
     * @param $notification
     */
633 634
    protected function _updateAdyenAttributes($notification)
    {
635
        $this->_adyenLogger->addAdyenNotificationCronjob('Updating the Adyen attributes of the order');
636 637 638 639 640 641

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

        if ($this->_eventCode == Notification::AUTHORISATION
            || $this->_eventCode == Notification::HANDLED_EXTERNALLY
642 643
            || ($this->_eventCode == Notification::CAPTURE && $_paymentCode == "adyen_pos")
        ) {
644 645 646 647 648 649 650

            /*
             * 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 ||
651 652
                strcmp($this->_success, '') == 0
            ) {
653

654 655 656 657 658 659 660 661 662 663
                $previousAdyenEventCode = $this->_order->getData('adyen_notification_event_code');
                if ($previousAdyenEventCode != "AUTHORISATION : TRUE") {
                    $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
                }
            } else {
                $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
            }
        }
    }

664 665 666
    /**
     * @param $additionalData
     */
667 668 669 670 671 672 673 674
    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'] : "";
675 676
            $acquirerReference = (isset($additionalData['acquirerReference'])) ?
                $additionalData['acquirerReference'] : "";
677
            $authCode = (isset($additionalData['authCode'])) ? $additionalData['authCode'] : "";
678 679
            $cardBin = (isset($additionalData['cardBin'])) ? $additionalData['cardBin'] : "";
            $expiryDate = (isset($additionalData['expiryDate'])) ? $additionalData['expiryDate'] : "";
680 681 682 683 684 685 686 687 688 689 690
        }

        // 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 != "") {
691 692 693
            $this->_order->getPayment()->setAdditionalInformation(
                'adyen_klarna_number', $this->_klarnaReservationNumber
            );
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
        }
        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);
        }
720 721 722 723 724 725
        if (!empty($cardBin)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_card_bin', $cardBin);
        }
        if (!empty($expiryDate)) {
            $this->_order->getPayment()->setAdditionalInformation('adyen_expiry_date', $expiryDate);
        }
726 727 728 729 730 731
        if ($this->ratepayDescriptor !== "") {
            $this->_order->getPayment()->setAdditionalInformation(
                'adyen_ratepay_descriptor', $this->ratepayDescriptor
            );
        }

732 733 734 735 736 737 738 739 740 741 742
    }

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

743
        if ($reason != "") {
744
            $reasonArray = explode(":", $reason);
745 746
            if ($reasonArray != null && is_array($reasonArray)) {
                if (isset($reasonArray[1])) {
747 748 749 750 751 752 753
                    $result = $reasonArray[1];
                }
            }
        }
        return $result;
    }

754
    /**
755 756
     * @param $ignoreHasInvoice
     * @throws \Magento\Framework\Exception\LocalizedException
757 758 759
     */
    protected function _holdCancelOrder($ignoreHasInvoice)
    {
760 761 762
        $orderStatus = $this->_getConfigData(
            'payment_cancelled', 'adyen_abstract', $this->_order->getStoreId()
        );
763 764 765 766

        // check if order has in invoice only cancel/hold if this is not the case
        if ($ignoreHasInvoice || !$this->_order->hasInvoices()) {

767
            if ($orderStatus == \Magento\Sales\Model\Order::STATE_HOLDED) {
768 769 770 771

                // Allow magento to hold order
                $this->_order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_HOLD, true);

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

782 783 784
                if ($this->_order->canCancel()) {
                    $this->_order->cancel();
                } else {
785
                    $this->_adyenLogger->addAdyenNotificationCronjob('Order can not be canceled');
786 787 788 789
                    return;
                }
            }
        } else {
790
            $this->_adyenLogger->addAdyenNotificationCronjob('Order has already an invoice so cannot be canceled');
791 792 793 794
        }
    }

    /**
795
     * Process the Notification
796 797 798
     */
    protected function _processNotification()
    {
799

800
        $this->_adyenLogger->addAdyenNotificationCronjob('Processing the notification');
801 802 803 804 805 806 807
        $_paymentCode = $this->_paymentMethodCode();

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

                        /*
                         * 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');
                            }
                        }
877 878 879
                    }
                } else {
                    // FOR POS authorize the payment on the CAPTURE notification
rikterbeek's avatar
rikterbeek committed
880
                    $this->_authorizePayment();
881 882
                }
                break;
883
            case Notification::OFFER_CLOSED:
884
                if (!$this->_order->canCancel()) {
885 886 887 888 889
                    // 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;
890 891 892 893 894 895
            case Notification::CAPTURE_FAILED:
            case Notification::CANCELLATION:
            case Notification::CANCELLED:
                $this->_holdCancelOrder(true);
                break;
            case Notification::CANCEL_OR_REFUND:
896 897
                if (isset($this->_modificationResult) && $this->_modificationResult != "") {
                    if ($this->_modificationResult == "cancel") {
898
                        $this->_holdCancelOrder(true);
899
                    } elseif ($this->_modificationResult == "refund") {
rikterbeek's avatar
rikterbeek committed
900
                        $this->_refundOrder();
901
                        //refund completed
rikterbeek's avatar
rikterbeek committed
902
                        $this->_setRefundAuthorized();
903 904
                    }
                } else {
905
                    if ($this->_order->isCanceled() ||
906 907
                        $this->_order->getState() === \Magento\Sales\Model\Order::STATE_HOLDED
                    ) {
908 909 910 911

                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'Order is already cancelled or holded so do nothing'
                        );
912
                    } else {
913 914 915 916 917 918 919 920 921 922
                        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();
                        }
923 924 925
                    }
                }
                break;
926

927
            case Notification::RECURRING_CONTRACT:
928 929 930
                // storedReferenceCode
                $recurringDetailReference = $this->_pspReference;

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

946 947 948 949 950 951 952
                    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']) &&
953 954
                            $rc['recurringDetailReference'] == $recurringDetailReference
                        ) {
955 956
                            $contractDetail = $rc;
                        }
957 958
                    }

959 960 961 962 963 964 965 966
                    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);
                    }
967

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

971
                    // Get collection and update existing agreements
972

973 974 975 976 977 978 979 980
                    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
981
                            );
982
                        }
Aleffio's avatar
Aleffio committed
983
                        $updateBillingAgreement->save();
984
                    }
985

986 987 988 989 990 991 992 993 994 995 996 997 998
                    // 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(),
                            ]
                        );
999

1000 1001 1002 1003
                        $billingAgreement = $this->_billingAgreementFactory->create();
                        $billingAgreement->setStoreId($this->_order->getStoreId());
                        $billingAgreement->importOrderPayment($this->_order->getPayment());
                        $message = __('Created billing agreement #%1.', $recurringDetailReference);
1004
                    } else {
1005 1006
                        $this->_adyenLogger->addAdyenNotificationCronjob("Using existing Billing Agreement");
                        $billingAgreement->setIsObjectChanged(true);
Aleffio's avatar
Aleffio committed
1007
                        $message = __('Updated billing agreement #%1.', $recurringDetailReference);
1008
                    }
1009

1010 1011 1012
                    // Populate billing agreement data
                    $billingAgreement->parseRecurringContractData($contractDetail);
                    if ($billingAgreement->isValid()) {
1013

1014 1015
                        // save into sales_billing_agreement_order
                        $billingAgreement->addOrderRelation($this->_order);
1016

1017 1018 1019 1020 1021
                        // add to order to save agreement
                        $this->_order->addRelatedObject($billingAgreement);
                    } else {
                        $message = __('Failed to create billing agreement for this order.');
                        throw new \Exception($message);
1022
                    }
1023

1024
                } catch (\Exception $exception) {
1025
                    $message = $exception->getMessage();
1026
                }
1027 1028 1029 1030

                $this->_adyenLogger->addAdyenNotificationCronjob($message);
                $comment = $this->_order->addStatusHistoryComment($message);
                $this->_order->addRelatedObject($comment);
1031
                break;
1032
            default:
1033 1034 1035
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf('This notification event: %s is not supported so will be ignored', $this->_eventCode)
                );
1036 1037 1038 1039
                break;
        }
    }

rikterbeek's avatar
rikterbeek committed
1040 1041 1042 1043 1044 1045
    /**
     * Not implemented
     * @return bool
     */
    protected function _refundOrder()
    {
1046 1047
        $this->_adyenLogger->addAdyenNotificationCronjob('Refunding the order');

1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
        // 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();
1060
                $amountRefunded = $amountRefunded = $orderPayment->getTotalRefunded() +
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
                    $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');
            }
        }

1071 1072 1073 1074
        /*
         * Don't create a credit memo if refund is initialize in Magento
         * because in this case the credit memo already exists
         */
1075
        $lastTransactionId = $this->_order->getPayment()->getLastTransId();
1076
        if ($lastTransactionId != $this->_pspReference) {
1077

1078
            // refund is done through adyen backoffice so create a credit memo
1079 1080 1081
            $order = $this->_order;
            if ($order->canCreditmemo()) {

1082 1083 1084
                $currency = $this->_order->getOrderCurrencyCode();
                $amount = $this->_adyenHelper->originalAmount($this->_value, $currency);
                $order->getPayment()->registerRefundNotification($amount);
1085

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

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

1108
    /**
1109
     * authorize payment
1110 1111 1112
     */
    protected function _authorizePayment()
    {
1113
        $this->_adyenLogger->addAdyenNotificationCronjob('Authorisation of the order');
1114 1115 1116
        $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();

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

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

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

1136 1137
        }

1138 1139 1140
        if (($this->_paymentMethod == "c_cash" &&
                $this->_getConfigData('create_shipment', 'adyen_cash', $this->_order->getStoreId())) ||
            ($this->_getConfigData('create_shipment', 'adyen_pos', $this->_order->getStoreId()) &&
1141 1142
                $_paymentCode == "adyen_pos")
        ) {
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 1174 1175
        $status = $this->_getConfigData(
            'payment_pre_authorized', 'adyen_abstract', $this->_order->getStoreId()
        );
1176 1177

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

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

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

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

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

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

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

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

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

1228
            $createPendingInvoice = (bool)$this->_getConfigData(
1229 1230 1231 1232 1233 1234 1235
                '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'
                );
1236 1237 1238 1239 1240 1241
                return;
            }
        }

        // validate if amount is total amount
        $orderCurrencyCode = $this->_order->getOrderCurrencyCode();
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
        $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)) {
1259
            $this->_createInvoice();
1260
        } else {
1261 1262 1263
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'This is a partial AUTHORISATION and the full amount is not reached'
            );
1264 1265 1266 1267 1268 1269 1270 1271
        }
    }

    /**
     * @return bool
     */
    protected function _isAutoCapture()
    {
Alessio Zampatti's avatar
Alessio Zampatti committed
1272
        // validate if payment methods allows manual capture
1273
        if ($this->_manualCaptureAllowed()) {
1274 1275 1276 1277 1278 1279
            $captureMode = trim($this->_getConfigData(
                'capture_mode', 'adyen_abstract', $this->_order->getStoreId())
            );
            $sepaFlow = trim($this->_getConfigData(
                'sepa_flow', 'adyen_abstract', $this->_order->getStoreId())
            );
1280
            $_paymentCode = $this->_paymentMethodCode();
1281 1282 1283
            $captureModeOpenInvoice = $this->_getConfigData(
                'auto_capture_openinvoice', 'adyen_abstract', $this->_order->getStoreId()
            );
1284
            $manualCapturePayPal = trim($this->_getConfigData(
1285 1286 1287 1288 1289 1290 1291 1292
                '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 (($_paymentCode == "adyen_sepa" || $this->_paymentMethod == "sepadirectdebit") &&
1293 1294
                $sepaFlow == "authcap"
            ) {
1295 1296 1297
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Manual Capture is applied for sepa because it is in authcap flow'
                );
1298 1299
                return false;
            }
1300

1301
            // payment method ideal, cash adyen_boleto or adyen_pos has direct capture
1302
            if ($_paymentCode == "adyen_pos" || (($_paymentCode == "adyen_sepa" ||
1303 1304
                        $this->_paymentMethod == "sepadirectdebit") && $sepaFlow != "authcap")
            ) {
1305 1306
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method does not allow manual capture.(2) paymentCode:' .
1307
                    $_paymentCode . ' paymentMethod:' . $this->_paymentMethod . ' sepaFLow:' . $sepaFlow
1308
                );
1309 1310
                return true;
            }
1311

1312
            if ($_paymentCode == "adyen_pos_cloud") {
1313
                $captureModePos = $this->_adyenHelper->getAdyenPosCloudConfigData('capture_mode_pos', $this->_order->getStoreId());
1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
                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;
                }

            }

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

1338
            // if PayPal capture modues is different from the default use this one
1339 1340
            if (strcmp($this->_paymentMethod, 'paypal') === 0) {
                if ($manualCapturePayPal) {
1341 1342 1343
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as manual capture'
                    );
1344
                    return false;
1345 1346 1347 1348 1349
                } else {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as auto capture'
                    );
                    return true;
1350 1351 1352
                }
            }
            if (strcmp($captureMode, 'manual') === 0) {
1353
                $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode for this payment is set to manual');
1354 1355
                return false;
            }
1356 1357 1358 1359 1360

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

            $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode is set to auto capture');
1367 1368 1369 1370
            return true;

        } else {
            // does not allow manual capture so is always immediate capture
1371
            $this->_adyenLogger->addAdyenNotificationCronjob('This payment method does not allow manual capture');
1372
            return true;
1373
        }
1374 1375 1376 1377 1378 1379

    }

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

Rik ter Beek's avatar
Rik ter Beek committed
1388 1389 1390 1391 1392
        // For all openinvoice methods manual capture is the default
        if ($this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod)) {
            return true;
        }

1393
        switch ($paymentMethod) {
1394 1395 1396
            case 'cup':
            case 'cartebancaire':
            case 'visa':
1397
            case 'visadankort':
1398 1399 1400 1401
            case 'mc':
            case 'uatp':
            case 'amex':
            case 'maestro':
rikterbeek's avatar
rikterbeek committed
1402
            case 'maestrouk':
1403 1404 1405 1406 1407 1408 1409 1410 1411 1412
            case 'diners':
            case 'discover':
            case 'jcb':
            case 'laser':
            case 'paypal':
            case 'sepadirectdebit':
                $manualCaptureAllowed = true;
                break;
            default:
                $manualCaptureAllowed = false;
1413
        }
1414 1415

        return $manualCaptureAllowed;
1416 1417 1418 1419 1420
    }

    /**
     * @return bool
     */
1421 1422
    protected function _isBankTransfer()
    {
1423
        if (strlen($this->_paymentMethod) >= 12 && substr($this->_paymentMethod, 0, 12) == "bankTransfer") {
1424 1425 1426 1427 1428 1429 1430
            $isBankTransfer = true;
        } else {
            $isBankTransfer = false;
        }
        return $isBankTransfer;
    }

1431 1432 1433
    /**
     * @return mixed
     */
1434 1435
    protected function _getFraudManualReviewStatus()
    {
1436 1437 1438
        return $this->_getConfigData(
            'fraud_manual_review_status', 'adyen_abstract', $this->_order->getStoreId()
        );
1439 1440
    }

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

1451
    /**
1452 1453
     * @param int $paymentId
     * @param string $orderCurrencyCode
1454 1455
     * @return bool
     */
1456
    protected function _isTotalAmount($paymentId, $orderCurrencyCode)
1457 1458 1459 1460
    {
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Validate if AUTHORISATION notification has the total amount of the order'
        );
1461

1462
        // get total amount of the order
1463
        $grandTotal = (int)$this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $orderCurrencyCode);
1464 1465 1466 1467 1468

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

1470
        if ($res && isset($res[0]) && is_array($res[0])) {
1471 1472
            $amount = $res[0]['total_amount'];
            $orderAmount = $this->_adyenHelper->formatAmount($amount, $orderCurrencyCode);
1473 1474 1475 1476 1477 1478
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf('The grandtotal amount is %s and the total order amount that is authorised is: %s',
                    $grandTotal,
                    $orderAmount
                )
            );
1479 1480 1481 1482 1483 1484 1485 1486 1487 1488

            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;
            }
1489
        }
1490
        return false;
1491 1492
    }

1493 1494 1495
    /**
     * @throws Exception
     * @throws \Magento\Framework\Exception\LocalizedException
1496
     * @return void
1497
     */
1498 1499
    protected function _createInvoice()
    {
1500
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating invoice for order');
1501 1502 1503

        if ($this->_order->canInvoice()) {

1504 1505
            /* We do not use this inside a transaction because order->save()
             * is always done on the end of the notification
1506 1507 1508 1509 1510 1511 1512
             * 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
1513
                $invoice->setTransactionId($this->_pspReference);
1514

1515

1516
                $autoCapture = $this->_isAutoCapture();
1517
                $createPendingInvoice = (bool)$this->_getConfigData(
1518 1519
                    'create_pending_invoice', 'adyen_abstract', $this->_order->getStoreId()
                );
1520

1521
                if ((!$autoCapture) && ($createPendingInvoice)) {
1522 1523 1524

                    // if amount is zero create a offline invoice
                    $value = (int)$this->_value;
1525
                    if ($value == 0) {
rikterbeek's avatar
rikterbeek committed
1526
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
1527
                    } else {
rikterbeek's avatar
rikterbeek committed
1528
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::NOT_CAPTURE);
1529 1530 1531 1532 1533 1534 1535 1536
                    }

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

                $invoice->save();
1537
                $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice');
1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550

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

1551
            } catch (Exception $e) {
1552 1553 1554
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Error saving invoice. The error message is: ' . $e->getMessage()
                );
1555 1556 1557 1558 1559
                throw new Exception(sprintf('Error saving invoice. The error message is:', $e->getMessage()));
            }

            $this->_setPaymentAuthorized();

1560 1561 1562 1563
            $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()
1564 1565
            );

1566
            if ($invoiceAutoMail) {
1567
                $this->_invoiceSender->send($invoice);
1568 1569
            }
        } else {
1570
            $this->_adyenLogger->addAdyenNotificationCronjob('It is not possible to create invoice for this order');
1571 1572 1573 1574
        }
    }

    /**
1575 1576 1577
     * @param bool $manualReviewComment
     * @param bool $createInvoice
     * @throws Exception
1578 1579 1580
     */
    protected function _setPaymentAuthorized($manualReviewComment = true, $createInvoice = false)
    {
1581
        $this->_adyenLogger->addAdyenNotificationCronjob('Set order to authorised');
1582 1583 1584 1585

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

        // create invoice for the capture notification if you are on manual capture
1589 1590
        if ($createInvoice == true && $amount == $orderAmount) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
1591
                'amount notification:' . $amount . ' amount order:' . $orderAmount
1592
            );
rikterbeek's avatar
rikterbeek committed
1593
            $this->_createInvoice();
1594
        }
1595

1596 1597 1598
        $status = $this->_getConfigData(
            'payment_authorized', 'adyen_abstract', $this->_order->getStoreId()
        );
1599 1600

        // virtual order can have different status
1601
        if ($this->_order->getIsVirtual()) {
1602
            $this->_adyenLogger->addAdyenNotificationCronjob('Product is a virtual product');
1603 1604 1605
            $virtualStatus = $this->_getConfigData(
                'payment_authorized_virtual', 'adyen_abstract', $this->_order->getStoreId()
            );
1606 1607
            if ($virtualStatus != "") {
                $status = $virtualStatus;
1608 1609 1610 1611
            }
        }

        // check for boleto if payment is totally paid
1612
        if ($this->_paymentMethodCode() == "adyen_boleto") {
1613 1614 1615 1616 1617

            // check if paid amount is the same as orginal amount
            $orginalAmount = $this->_boletoOriginalAmount;
            $paidAmount = $this->_boletoPaidAmount;

1618
            if ($orginalAmount != $paidAmount) {
1619 1620 1621

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

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

1628
                if ($paidAmount > $orginalAmount) {
1629
                    $overpaidStatus = $this->_getConfigData(
1630 1631
                        'order_overpaid_status', 'adyen_boleto', $this->_order->getStoreId()
                    );
1632 1633 1634
                    // check if there is selected a status if not fall back to the default
                    $status = (!empty($overpaidStatus)) ? $overpaidStatus : $status;
                } else {
1635 1636 1637
                    $underpaidStatus = $this->_getConfigData(
                        'order_underpaid_status', 'adyen_boleto', $this->_order->getStoreId()
                    );
1638 1639 1640 1641 1642 1643 1644 1645 1646
                    // 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
1647
        if ($manualReviewComment == true && $this->_fraudManualReview) {
1648 1649
            // check if different status is selected
            $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
1650
            if ($fraudManualReviewStatus != "") {
1651 1652 1653 1654 1655 1656
                $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
1657 1658
        $this->_setState($status);

1659 1660 1661
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Order status is changed to authorised status, status is ' . $status
        );
1662 1663
    }

Bas Maassen's avatar
Bas Maassen committed
1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678
    /**
     * 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());
    }
1679

rikterbeek's avatar
rikterbeek committed
1680
    /**
1681
     * Create shipment
rikterbeek's avatar
rikterbeek committed
1682
     *
1683
     * @throws bool
rikterbeek's avatar
rikterbeek committed
1684
     */
1685 1686
    protected function _createShipment()
    {
1687
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating shipment for order');
rikterbeek's avatar
rikterbeek committed
1688 1689
        // create shipment for cash payment
        $payment = $this->_order->getPayment()->getMethodInstance();
1690 1691
        if ($this->_order->canShip()) {
            $itemQty = [];
rikterbeek's avatar
rikterbeek committed
1692
            $shipment = $this->_order->prepareShipment($itemQty);
1693
            if ($shipment) {
rikterbeek's avatar
rikterbeek committed
1694 1695 1696 1697
                $shipment->register();
                $shipment->getOrder()->setIsInProcess(true);
                $comment = __('Shipment created by Adyen');
                $shipment->addComment($comment);
rikterbeek's avatar
rikterbeek committed
1698 1699 1700 1701

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

1705
                $this->_adyenLogger->addAdyenNotificationCronjob('Order is shipped');
rikterbeek's avatar
rikterbeek committed
1706 1707
            }
        } else {
1708
            $this->_adyenLogger->addAdyenNotificationCronjob('Order can\'t be shipped');
rikterbeek's avatar
rikterbeek committed
1709 1710 1711
        }
    }

1712 1713 1714
    /**
     * Retrieve information from payment configuration
     *
1715 1716 1717
     * @param $field
     * @param string $paymentMethodCode
     * @param $storeId
1718 1719 1720 1721 1722 1723 1724
     * @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);
    }
1725 1726


1727
}