Cron.php 62.5 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\Webapi\Exception;
27
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
28 29
use Magento\Framework\App\Area;
use Magento\Framework\ObjectManagerInterface;
30 31 32 33 34 35 36 37 38 39

class Cron
{

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

40 41 42
    /**
     * @var Resource\Notification\CollectionFactory
     */
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    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;

62 63 64
    /**
     * @var \Adyen\Payment\Helper\Data
     */
65 66 67 68 69 70 71
    protected $_adyenHelper;

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

rikterbeek's avatar
rikterbeek committed
72 73 74 75 76
    /**
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $_transactionFactory;

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    /**
     * @var \Adyen\Payment\Model\Billing\AgreementFactory
     */
    protected $_billingAgreementFactory;

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

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

92 93 94
    /**
     * notification attributes
     */
95
    protected $_pspReference;
96

97 98 99 100 101
    /**
     * @var
     */
    protected $_originalReference;

102 103 104
    /**
     * @var
     */
105
    protected $_merchantReference;
106 107 108 109

    /**
     * @var
     */
110
    protected $_eventCode;
111 112 113 114

    /**
     * @var
     */
115
    protected $_success;
116 117 118 119

    /**
     * @var
     */
120
    protected $_paymentMethod;
121 122 123 124

    /**
     * @var
     */
125
    protected $_reason;
126 127 128 129

    /**
     * @var
     */
130
    protected $_value;
131 132 133 134

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

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

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

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

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

157 158 159 160 161 162 163 164 165 166
    /**
     * @var Order\PaymentFactory
     */
    protected $_adyenOrderPaymentFactory;

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

167 168 169 170 171
    /**
     * @var ObjectManagerInterface
     */
    protected $_objectManager;

172
    /**
173 174
     * Cron constructor.
     *
175 176 177 178 179 180 181
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
     * @param Resource\Notification\CollectionFactory $notificationFactory
     * @param \Magento\Sales\Model\OrderFactory $orderFactory
     * @param \Adyen\Payment\Helper\Data $adyenHelper
     * @param OrderSender $orderSender
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
182
     * @param Billing\AgreementFactory $billingAgreementFactory
183
     * @param Resource\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory
184
     * @param Api\PaymentRequest $paymentRequest
185 186
     * @param Order\PaymentFactory $adyenOrderPaymentFactory
     * @param Resource\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory
187
     * @param ObjectManagerInterface $objectManager
188 189 190 191 192 193 194
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Adyen\Payment\Logger\AdyenLogger $adyenLogger,
        \Adyen\Payment\Model\Resource\Notification\CollectionFactory $notificationFactory,
        \Magento\Sales\Model\OrderFactory $orderFactory,
        \Adyen\Payment\Helper\Data $adyenHelper,
rikterbeek's avatar
rikterbeek committed
195
        OrderSender $orderSender,
196 197 198
        \Magento\Framework\DB\TransactionFactory $transactionFactory,
        \Adyen\Payment\Model\Billing\AgreementFactory $billingAgreementFactory,
        \Adyen\Payment\Model\Resource\Billing\Agreement\CollectionFactory $billingAgreementCollectionFactory,
199 200
        \Adyen\Payment\Model\Api\PaymentRequest $paymentRequest,
        \Adyen\Payment\Model\Order\PaymentFactory $adyenOrderPaymentFactory,
201 202
        \Adyen\Payment\Model\Resource\Order\Payment\CollectionFactory $adyenOrderPaymentCollectionFactory,
        ObjectManagerInterface $objectManager
203
    ) {
204
        $this->_scopeConfig = $scopeConfig;
205
        $this->_adyenLogger = $adyenLogger;
206 207 208 209
        $this->_notificationFactory = $notificationFactory;
        $this->_orderFactory = $orderFactory;
        $this->_adyenHelper = $adyenHelper;
        $this->_orderSender = $orderSender;
rikterbeek's avatar
rikterbeek committed
210
        $this->_transactionFactory = $transactionFactory;
211 212 213
        $this->_billingAgreementFactory = $billingAgreementFactory;
        $this->_billingAgreementCollectionFactory = $billingAgreementCollectionFactory;
        $this->_adyenPaymentRequest = $paymentRequest;
214 215
        $this->_adyenOrderPaymentFactory = $adyenOrderPaymentFactory;
        $this->_adyenOrderPaymentCollectionFactory = $adyenOrderPaymentCollectionFactory;
216
        $this->_objectManager = $objectManager;
217 218
    }

219 220 221 222
    /**
     * Process the notification
     * @return void
     */
223 224
    public function processNotification()
    {
225 226 227 228 229 230
        // needed for Magento < 2.2.0 https://github.com/magento/magento2/pull/8413
        $areaList = $this->_objectManager->get(\Magento\Framework\App\AreaList::class);
        if($areaList) {
            $areaList->getArea(Area::AREA_CRONTAB)->load(Area::PART_TRANSLATE);
        }

231 232
        $this->_order = null;

233
        // execute notifications from 2 minute or earlier because order could not yet been created by magento
234
        $dateStart = new \DateTime();
235
        $dateStart->modify('-5 day');
236
        $dateEnd = new \DateTime();
237
        $dateEnd->modify('-1 minute');
238 239
        $dateRange = ['from' => $dateStart, 'to' => $dateEnd, 'datetime' => true];

240
        // create collection
241 242
        $notifications = $this->_notificationFactory->create();
        $notifications->addFieldToFilter('done', 0);
243
        $notifications->addFieldToFilter('processing', 0);
244 245
        $notifications->addFieldToFilter('created_at', $dateRange);

246 247 248 249 250 251 252 253
        foreach ($notifications as $notification) {
            // set Cron processing to true
            $dateEnd = new \DateTime();
            $notification->setProcessing(true);
            $notification->setUpdatedAt($dateEnd);
            $notification->save();
        }

254
        // loop over the notifications
255
        $count = 0;
256
        foreach ($notifications as $notification) {
257

258 259 260 261
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf("Processing notification %s", $notification->getEntityId())
            );

262 263 264 265
            /**
             *  If the event is a RECURRING_CONTRACT wait an extra 5 minutes
             * before processing so we are sure the RECURRING_CONTRACT
             */
Rik ter Beek's avatar
Rik ter Beek committed
266
            if (trim($notification->getEventCode()) == Notification::RECURRING_CONTRACT &&
267 268 269 270 271 272 273 274
                strtotime($notification->getCreatedAt()) >= strtotime('-5 minutes', time())) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    "This is a recurring_contract notification wait an extra 5 minutes 
                    before processing this to make sure the contract exists"
                );
                continue;
            }

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

278 279 280 281 282
            // get order
            $incrementId = $notification->getMerchantReference();

            $this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId);
            if (!$this->_order->getId()) {
283 284 285 286

                // order does not exists remove from queue
                $notification->delete();
                continue;
287 288 289 290 291 292 293 294 295
            }

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

297 298
            // update order details
            $this->_updateAdyenAttributes($notification);
299 300 301

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

310
                    $this->_adyenLogger->addAdyenNotificationCronjob('Going to cancel the order');
311 312

                    // if payment is API check, check if API result pspreference is the same as reference
313
                    if ($this->_eventCode == NOTIFICATION::AUTHORISATION && $this->_getPaymentMethodType() == 'api') {
314
                        // don't cancel the order becasue order was successfull through api
315 316 317
                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'order is not cancelled because api result was succesfull'
                        );
318
                    } else {
319 320 321 322 323 324 325
                        /*
                         * don't cancel the order if previous state is authorisation with success=true
                         * Split payments can fail if the second payment has failed the first payment is
                         * refund/cancelled as well so if it is a split payment that failed cancel the order as well
                         */
                        if ($previousAdyenEventCode != "AUTHORISATION : TRUE" ||
                            $this->_eventCode == Notification::ORDER_CLOSED) {
326 327 328
                            $this->_holdCancelOrder(false);
                        } else {
                            $this->_order->setData('adyen_notification_event_code', $previousAdyenEventCode);
329 330 331 332
                            $this->_adyenLogger->addAdyenNotificationCronjob(
                                'order is not cancelled because previous notification 
                                was an authorisation that succeeded'
                            );
333 334 335
                        }
                    }
                } else {
336 337 338
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'Order is already processed so ignore this notification state is:' . $this->_order->getState()
                    );
339 340 341 342 343 344 345 346
                }
            } else {
                // Notification is successful
                $this->_processNotification();
            }

            $this->_order->save();

347 348 349
            // set done to true
            $dateEnd = new \DateTime();
            $notification->setDone(true);
350
            $notification->setProcessing(false);
351 352
            $notification->setUpdatedAt($dateEnd);
            $notification->save();
353 354 355 356 357 358 359 360
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf("Notification %s is processed", $notification->getEntityId())
            );
            ++$count;
        }

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

364 365 366 367 368 369
    /**
     * Declare private variables for processing notification
     *
     * @param Object $notification
     * @return void
     */
370 371 372 373
    protected function _declareVariables($notification)
    {
        //  declare the common parameters
        $this->_pspReference = $notification->getPspreference();
374
        $this->_originalReference = $notification->getOriginalReference();
375 376 377 378 379 380 381 382 383 384 385
        $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
386 387
        if ($this->_paymentMethodCode() == "adyen_boleto") {
            if ($additionalData && is_array($additionalData)) {
388
                $boletobancario = isset($additionalData['boletobancario']) ? $additionalData['boletobancario'] : null;
389 390 391 392 393
                if ($boletobancario && is_array($boletobancario)) {
                    $this->_boletoOriginalAmount =
                        isset($boletobancario['originalAmount']) ? trim($boletobancario['originalAmount']) : "";
                    $this->_boletoPaidAmount =
                        isset($boletobancario['paidAmount']) ? trim($boletobancario['paidAmount']) : "";
394 395 396 397
                }
            }
        }

398
        if ($additionalData && is_array($additionalData)) {
399 400

            // check if the payment is in status manual review
401 402 403
            $fraudManualReview = isset($additionalData['fraudManualReview']) ?
                $additionalData['fraudManualReview'] : "";
            if ($fraudManualReview == "true") {
404 405 406 407 408
                $this->_fraudManualReview = true;
            } else {
                $this->_fraudManualReview = false;
            }

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

416
            $modification = isset($additionalData['modification']) ? $additionalData['modification'] : null;
417
            if ($modification && is_array($modification)) {
418
                $this->_modificationResult = isset($modification['action']) ? trim($modification['action']) : "";
419 420
            }
            $additionalData2 = isset($additionalData['additionalData']) ? $additionalData['additionalData'] : null;
421
            if ($additionalData2 && is_array($additionalData2)) {
422 423 424 425 426 427 428 429 430 431 432 433 434
                $this->_klarnaReservationNumber = isset($additionalData2['acquirerReference']) ? trim($additionalData2['acquirerReference']) : "";
            }
        }
    }

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

435 436 437 438 439
    /**
     * @return mixed
     */
    protected function _getPaymentMethodType()
    {
rikterbeek's avatar
rikterbeek committed
440 441 442
        return $this->_order->getPayment()->getPaymentMethodType();
    }

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

453
        if ($this->_eventCode == Notification::REFUND || $this->_eventCode == Notification::CAPTURE) {
454 455 456 457 458 459 460

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

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

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

465 466 467 468
            if ($amount == $orderAmount) {
                $this->_order->setData(
                    'adyen_notification_event_code', $this->_eventCode . " : " . strtoupper($successResult)
                );
469
            } else {
470 471 472 473
                $this->_order->setData(
                    'adyen_notification_event_code', "(PARTIAL) " .
                    $this->_eventCode . " : " . strtoupper($successResult)
                );
474 475
            }
        } else {
476 477 478
            $this->_order->setData(
                'adyen_notification_event_code', $this->_eventCode . " : " . strtoupper($successResult)
            );
479 480
        }

Rik ter Beek's avatar
Rik ter Beek committed
481
        // if payment method is klarna, ratepay or openinvoice/afterpay show the reservartion number
482
        if (($this->_paymentMethod == "klarna" || $this->_paymentMethod == "afterpay_default" ||
Rik ter Beek's avatar
Rik ter Beek committed
483 484
                $this->_paymentMethod == "openinvoice" || $this->_paymentMethod == "ratepay"
            ) && ($this->_klarnaReservationNumber != null &&
485
                $this->_klarnaReservationNumber != "")) {
486 487 488 489 490
            $klarnaReservationNumberText = "<br /> reservationNumber: " . $this->_klarnaReservationNumber;
        } else {
            $klarnaReservationNumberText = "";
        }

491
        if ($this->_boletoPaidAmount != null && $this->_boletoPaidAmount != "") {
492 493 494 495 496 497
            $boletoPaidAmountText = "<br /> Paid amount: " . $this->_boletoPaidAmount;
        } else {
            $boletoPaidAmountText = "";
        }

        $type = 'Adyen HTTP Notification(s):';
498 499 500
        $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);
501 502

        // If notification is pending status and pending status is set add the status change to the comment history
503
        if ($this->_eventCode == Notification::PENDING) {
504 505 506
            $pendingStatus = $this->_getConfigData(
                'pending_status', 'adyen_abstract', $this->_order->getStoreId()
            );
507
            if ($pendingStatus != "") {
508
                $this->_order->addStatusHistoryComment($comment, $pendingStatus);
509 510 511
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Created comment history for this notification with status change to: ' . $pendingStatus
                );
512 513 514 515 516
                return;
            }
        }

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

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

529 530 531
    /**
     * @param $notification
     */
532 533
    protected function _updateAdyenAttributes($notification)
    {
534
        $this->_adyenLogger->addAdyenNotificationCronjob('Updating the Adyen attributes of the order');
535 536 537 538 539 540

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

        if ($this->_eventCode == Notification::AUTHORISATION
            || $this->_eventCode == Notification::HANDLED_EXTERNALLY
541 542 543 544 545 546 547 548 549 550
            || ($this->_eventCode == Notification::CAPTURE && $_paymentCode == "adyen_pos")) {

            /*
             * if current notification is authorisation : false and
             * the  previous notification was authorisation : true do not update pspreference
             */
            if (strcmp($this->_success, 'false') == 0 ||
                strcmp($this->_success, '0') == 0 ||
                strcmp($this->_success, '') == 0) {

551 552 553 554 555 556 557 558 559 560
                $previousAdyenEventCode = $this->_order->getData('adyen_notification_event_code');
                if ($previousAdyenEventCode != "AUTHORISATION : TRUE") {
                    $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
                }
            } else {
                $this->_updateOrderPaymentWithAdyenAttributes($additionalData);
            }
        }
    }

561 562 563
    /**
     * @param $additionalData
     */
564 565 566 567 568 569 570 571
    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'] : "";
572 573
            $acquirerReference = (isset($additionalData['acquirerReference'])) ?
                $additionalData['acquirerReference'] : "";
574 575 576 577 578 579 580 581 582 583 584 585
            $authCode = (isset($additionalData['authCode'])) ? $additionalData['authCode'] : "";
        }

        // 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 != "") {
586 587 588
            $this->_order->getPayment()->setAdditionalInformation(
                'adyen_klarna_number', $this->_klarnaReservationNumber
            );
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
        }
        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);
        }
    }

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

626
        if ($reason != "") {
627
            $reasonArray = explode(":", $reason);
628 629
            if ($reasonArray != null && is_array($reasonArray)) {
                if (isset($reasonArray[1])) {
630 631 632 633 634 635 636
                    $result = $reasonArray[1];
                }
            }
        }
        return $result;
    }

637
    /**
638 639
     * @param $ignoreHasInvoice
     * @throws \Magento\Framework\Exception\LocalizedException
640 641 642
     */
    protected function _holdCancelOrder($ignoreHasInvoice)
    {
643 644 645
        $orderStatus = $this->_getConfigData(
            'payment_cancelled', 'adyen_abstract', $this->_order->getStoreId()
        );
646 647 648 649

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

650
            if ($orderStatus == \Magento\Sales\Model\Order::STATE_HOLDED) {
651 652 653 654

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

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

665 666 667
                if ($this->_order->canCancel()) {
                    $this->_order->cancel();
                } else {
668
                    $this->_adyenLogger->addAdyenNotificationCronjob('Order can not be canceled');
669 670 671 672
                    return;
                }
            }
        } else {
673
            $this->_adyenLogger->addAdyenNotificationCronjob('Order has already an invoice so cannot be canceled');
674 675 676 677
        }
    }

    /**
678
     * Process the Notification
679 680 681
     */
    protected function _processNotification()
    {
682
        $this->_adyenLogger->addAdyenNotificationCronjob('Processing the notification');
683 684 685 686 687 688 689
        $_paymentCode = $this->_paymentMethodCode();

        switch ($this->_eventCode) {
            case Notification::REFUND_FAILED:
                // do nothing only inform the merchant with order comment history
                break;
            case Notification::REFUND:
690 691 692 693
                $ignoreRefundNotification = $this->_getConfigData(
                    'ignore_refund_notification', 'adyen_abstract', $this->_order->getStoreId()
                );
                if ($ignoreRefundNotification != true) {
rikterbeek's avatar
rikterbeek committed
694
                    $this->_refundOrder();
695
                    //refund completed
rikterbeek's avatar
rikterbeek committed
696
                    $this->_setRefundAuthorized();
697
                } else {
698 699 700
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'Setting to ignore refund notification is enabled so ignore this notification'
                    );
701 702 703
                }
                break;
            case Notification::PENDING:
704 705 706
                if ($this->_getConfigData(
                    'send_email_bank_sepa_on_pending', 'adyen_abstract', $this->_order->getStoreId())
                ) {
707
                    // Check if payment is banktransfer or sepa if true then send out order confirmation email
rikterbeek's avatar
rikterbeek committed
708
                    $isBankTransfer = $this->_isBankTransfer();
709 710
                    if ($isBankTransfer || $this->_paymentMethod == 'sepadirectdebit') {
                        if (!$this->_order->getEmailSent()) {
711
                            $this->_sendOrderMail();
712
                        }
713 714 715 716 717 718
                    }
                }
                break;
            case Notification::HANDLED_EXTERNALLY:
            case Notification::AUTHORISATION:
                // for POS don't do anything on the AUTHORIZATION
719
                if ($_paymentCode != "adyen_pos") {
720 721 722 723 724 725 726
                    $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:
727 728 729 730
                /*
                 * only process this if you are on auto capture.
                 * On manual capture you will always get Capture or CancelOrRefund notification
                 */
731
                if ($this->_isAutoCapture()) {
732
                    $this->_setPaymentAuthorized(false);
733 734 735
                }
                break;
            case Notification::CAPTURE:
736 737 738 739 740
                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
                     */
741
                    if (!$this->_isAutoCapture()) {
742
                        $this->_setPaymentAuthorized(false, true);
743 744 745
                    }
                } else {
                    // FOR POS authorize the payment on the CAPTURE notification
rikterbeek's avatar
rikterbeek committed
746
                    $this->_authorizePayment();
747 748 749 750 751 752 753 754
                }
                break;
            case Notification::CAPTURE_FAILED:
            case Notification::CANCELLATION:
            case Notification::CANCELLED:
                $this->_holdCancelOrder(true);
                break;
            case Notification::CANCEL_OR_REFUND:
755 756
                if (isset($this->_modificationResult) && $this->_modificationResult != "") {
                    if ($this->_modificationResult == "cancel") {
757
                        $this->_holdCancelOrder(true);
758
                    } elseif ($this->_modificationResult == "refund") {
rikterbeek's avatar
rikterbeek committed
759
                        $this->_refundOrder();
760
                        //refund completed
rikterbeek's avatar
rikterbeek committed
761
                        $this->_setRefundAuthorized();
762 763
                    }
                } else {
764 765 766 767 768 769
                    if ($this->_order->isCanceled() ||
                        $this->_order->getState() === \Magento\Sales\Model\Order::STATE_HOLDED) {

                        $this->_adyenLogger->addAdyenNotificationCronjob(
                            'Order is already cancelled or holded so do nothing'
                        );
rikterbeek's avatar
rikterbeek committed
770
                    } else if ($this->_order->canCancel() || $this->_order->canHold()) {
771
                        $this->_adyenLogger->addAdyenNotificationCronjob('try to cancel the order');
rikterbeek's avatar
rikterbeek committed
772
                        $this->_holdCancelOrder(true);
773
                    } else {
774
                        $this->_adyenLogger->addAdyenNotificationCronjob('try to refund the order');
775
                        // refund
rikterbeek's avatar
rikterbeek committed
776
                        $this->_refundOrder();
777
                        //refund completed
rikterbeek's avatar
rikterbeek committed
778
                        $this->_setRefundAuthorized();
779 780 781
                    }
                }
                break;
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
            case Notification::RECURRING_CONTRACT:

                // storedReferenceCode
                $recurringDetailReference = $this->_pspReference;

                // check if there is already a BillingAgreement
                $billingAgreement = $this->_billingAgreementFactory->create();
                $billingAgreement->load($recurringDetailReference, 'reference_id');


                if ($billingAgreement && $billingAgreement->getAgreementId() > 0 && $billingAgreement->isValid()) {

                    try {
                        $billingAgreement->addOrderRelation($this->_order);
                        $billingAgreement->setStatus($billingAgreement::STATUS_ACTIVE);
                        $billingAgreement->setIsObjectChanged(true);
                        $this->_order->addRelatedObject($billingAgreement);
                        $message = __('Used existing billing agreement #%s.', $billingAgreement->getReferenceId());
                    } catch (Exception $e) {
                        // could be that it is already linked to this order
                        $message = __('Used existing billing agreement #%s.', $billingAgreement->getReferenceId());
                    }
                } else {

                    $this->_order->getPayment()->setBillingAgreementData(
                        [
                            'billing_agreement_id' => $recurringDetailReference,
                            'method_code' => $this->_order->getPayment()->getMethodCode(),
                        ]
                    );

                    // create new object
                    $billingAgreement = $this->_billingAgreementFactory->create();
                    $billingAgreement->setStoreId($this->_order->getStoreId());
                    $billingAgreement->importOrderPayment($this->_order->getPayment());

                    // get all data for this contract by doing a listRecurringCall
                    $customerReference = $billingAgreement->getCustomerReference();
                    $storeId = $billingAgreement->getStoreId();

822 823 824 825 826
                    /*
                     * for quest checkout users we can't save this in the billing agreement
                     * because it is linked to customer
                     */
                    if ($customerReference && $storeId) {
827

828 829
                        $listRecurringContracts = null;
                        try {
830 831 832
                            $listRecurringContracts = $this->_adyenPaymentRequest->getRecurringContractsForShopper(
                                $customerReference, $storeId
                            );
833 834
                        } catch(\Exception $exception) {
                            $this->_adyenLogger->addAdyenNotificationCronjob($exception->getMessage());
835 836
                        }

837
                        $contractDetail = null;
838
                        // get current Contract details and get list of all current ones
839
                        $recurringReferencesList = [];
840

841
                        if ($listRecurringContracts) {
842 843
                            foreach ($listRecurringContracts as $rc) {
                                $recurringReferencesList[] = $rc['recurringDetailReference'];
844 845
                                if (isset($rc['recurringDetailReference']) &&
                                    $rc['recurringDetailReference'] == $recurringDetailReference) {
846 847 848 849
                                    $contractDetail = $rc;
                                }
                            }
                        }
850

851
                        if ($contractDetail != null) {
852 853 854 855 856 857
                            // update status of all the current saved agreements in magento
                            $billingAgreements = $this->_billingAgreementCollectionFactory->create();
                            $billingAgreements->addFieldToFilter('customer_id', $customerReference);

                            // get collection

858 859 860 861 862
                            foreach ($billingAgreements as $updateBillingAgreement) {
                                if (!in_array($updateBillingAgreement->getReferenceId(), $recurringReferencesList)) {
                                    $updateBillingAgreement->setStatus(
                                        \Adyen\Payment\Model\Billing\Agreement::STATUS_CANCELED
                                    );
863 864
                                    $updateBillingAgreement->save();
                                } else {
865 866 867
                                    $updateBillingAgreement->setStatus(
                                        \Adyen\Payment\Model\Billing\Agreement::STATUS_ACTIVE
                                    );
868 869
                                    $updateBillingAgreement->save();
                                }
870 871
                            }

872 873 874 875
                            // add this billing agreement
                            $billingAgreement->parseRecurringContractData($contractDetail);
                            if ($billingAgreement->isValid()) {
                                $message = __('Created billing agreement #%1.', $billingAgreement->getReferenceId());
876

877 878
                                // save into sales_billing_agreement_order
                                $billingAgreement->addOrderRelation($this->_order);
879

880 881 882 883 884
                                // add to order to save agreement
                                $this->_order->addRelatedObject($billingAgreement);
                            } else {
                                $message = __('Failed to create billing agreement for this order.');
                            }
885 886


887 888 889 890 891 892
                        } else {
                            $this->_adyenLogger->addAdyenNotificationCronjob(
                                'Failed to create billing agreement for this order ' .
                                '(listRecurringCall did not contain contract)'
                            );
                            $this->_adyenLogger->addAdyenNotificationCronjob(
893
                                __('recurringDetailReference in notification is %1', $recurringDetailReference)
894 895
                            );
                            $this->_adyenLogger->addAdyenNotificationCronjob(
896
                                __('CustomerReference is: %1 and storeId is %2', $customerReference, $storeId)
897
                            );
898
                            $this->_adyenLogger->addAdyenNotificationCronjob(print_r($listRecurringContracts, 1));
899 900 901 902
                            $message = __(
                                'Failed to create billing agreement for this order ' .
                                '(listRecurringCall did not contain contract)'
                            );
903
                        }
904

905 906 907
                        $comment = $this->_order->addStatusHistoryComment($message);
                        $this->_order->addRelatedObject($comment);
                    }
908 909
                }
                break;
910
            default:
911 912 913
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    sprintf('This notification event: %s is not supported so will be ignored', $this->_eventCode)
                );
914 915 916 917
                break;
        }
    }

rikterbeek's avatar
rikterbeek committed
918 919 920 921 922 923
    /**
     * Not implemented
     * @return bool
     */
    protected function _refundOrder()
    {
924 925
        $this->_adyenLogger->addAdyenNotificationCronjob('Refunding the order');

926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
        // check if it is a split payment if so save the refunded data
        if ($this->_originalReference != "") {

            $this->_adyenLogger->addAdyenNotificationCronjob('Going to update the refund to split payments table');

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

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

949 950 951 952
        /*
         * Don't create a credit memo if refund is initialize in Magento
         * because in this case the credit memo already exists
         */
953
        $lastTransactionId = $this->_order->getPayment()->getLastTransId();
954
        if ($lastTransactionId != $this->_pspReference) {
955 956 957 958 959 960

            // refund is done through adyen backoffice so create an invoice
            $order = $this->_order;
            if ($order->canCreditmemo()) {

                // there is a bug in this function of Magento see #2656 magento\magento2 repo
961 962 963 964 965 966
                // Invalid method Magento\Sales\Model\Order\Creditmemo::register
                /*
                $currency = $this->_order->getOrderCurrencyCode();
                $amount = $this->_adyenHelper->originalAmount($this->_value, $currency);
                $order->getPayment()->registerRefundNotification($amount);
                */
967

968
                $this->_adyenLogger->addAdyenNotificationCronjob('Please create your credit memo inside magento');
969 970 971 972
            } else {
                $this->_adyenLogger->addAdyenNotificationCronjob('Could not create a credit memo for order');
            }
        } else {
973 974 975
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Did not create a credit memo for this order becasue refund is done through Magento'
            );
976
        }
rikterbeek's avatar
rikterbeek committed
977 978 979 980 981 982 983
    }

    /**
     * @param $order
     */
    protected function _setRefundAuthorized()
    {
984 985 986
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Status update to default status or refund_authorized status if this is set'
        );
rikterbeek's avatar
rikterbeek committed
987 988 989
        $this->_order->addStatusHistoryComment(__('Adyen Refund Successfully completed'));
    }

990
    /**
991
     * authorize payment
992 993 994
     */
    protected function _authorizePayment()
    {
995
        $this->_adyenLogger->addAdyenNotificationCronjob('Authorisation of the order');
996 997 998
        $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();

        // If manual review is active and a seperate status is used then ignore the pre authorized status
999
        if ($this->_fraudManualReview != true || $fraudManualReviewStatus == "") {
1000 1001
            $this->_setPrePaymentAuthorized();
        } else {
1002 1003 1004 1005
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Ignore the pre authorized status because the order is ' .
                'under manual review and use the Manual review status'
            );
1006 1007 1008 1009 1010 1011
        }

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

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

1018 1019
        }

1020 1021 1022 1023 1024
        if (($this->_paymentMethod == "c_cash" &&
                $this->_getConfigData('create_shipment', 'adyen_cash', $this->_order->getStoreId())) ||
            ($this->_getConfigData('create_shipment', 'adyen_pos', $this->_order->getStoreId()) &&
                $_paymentCode == "adyen_pos")) {

rikterbeek's avatar
rikterbeek committed
1025
            $this->_createShipment();
1026 1027 1028
        }
    }

1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
    /**
     * Send order Mail
     *
     * @return void
     */
    private function _sendOrderMail()
    {
        try {
            $this->_orderSender->send($this->_order);
            $this->_adyenLogger->addAdyenNotificationCronjob('Send orderconfirmation email to shopper');
        }  catch(\Exception $exception) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                "Exception in Send Mail in Magento. This is an issue in the the core of Magento" .
                $exception->getMessage()
            );
        }
    }

1047 1048
    /**
     * Set status on authorisation
1049 1050
     *
     * @return void
1051
     */
1052 1053
    private function _setPrePaymentAuthorized()
    {
1054 1055 1056
        $status = $this->_getConfigData(
            'payment_pre_authorized', 'adyen_abstract', $this->_order->getStoreId()
        );
1057 1058

        // only do this if status in configuration is set
1059
        if (!empty($status)) {
rikterbeek's avatar
rikterbeek committed
1060
            $this->_order->addStatusHistoryComment(__('Payment is authorised waiting for capture'), $status);
1061 1062 1063
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'Order status is changed to Pre-authorised status, status is ' . $status
            );
1064
        } else {
1065
            $this->_adyenLogger->addAdyenNotificationCronjob('No pre-authorised status is used so ignore');
1066 1067 1068 1069
        }
    }

    /**
1070
     * @throws Exception
1071
     * @return void
1072 1073 1074
     */
    protected function _prepareInvoice()
    {
1075
        $this->_adyenLogger->addAdyenNotificationCronjob('Prepare invoice for order');
1076 1077 1078 1079 1080

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

1082 1083 1084 1085 1086 1087 1088 1089 1090
        $paymentObj = $this->_order->getPayment();

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

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

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

            // show message if order is in manual review
1097
            if ($this->_fraudManualReview) {
1098 1099
                // check if different status is selected
                $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
1100
                if ($fraudManualReviewStatus != "") {
1101 1102 1103 1104 1105 1106
                    $status = $fraudManualReviewStatus;
                    $comment = "Adyen Payment is in Manual Review check the Adyen platform";
                    $this->_order->addStatusHistoryComment(__($comment), $status);
                }
            }

1107 1108 1109 1110 1111 1112 1113 1114
            $createPendingInvoice = (bool) $this->_getConfigData(
                'create_pending_invoice', 'adyen_abstract', $this->_order->getStoreId()
            );

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

        // validate if amount is total amount
        $orderCurrencyCode = $this->_order->getOrderCurrencyCode();
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
        $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)) {
1138
            $this->_createInvoice();
1139
        } else {
1140 1141 1142
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'This is a partial AUTHORISATION and the full amount is not reached'
            );
1143 1144 1145 1146 1147 1148 1149 1150
        }
    }

    /**
     * @return bool
     */
    protected function _isAutoCapture()
    {
1151
        // validate if payment methods allowes manual capture
1152
        if ($this->_manualCaptureAllowed()) {
1153 1154 1155 1156 1157 1158
            $captureMode = trim($this->_getConfigData(
                'capture_mode', 'adyen_abstract', $this->_order->getStoreId())
            );
            $sepaFlow = trim($this->_getConfigData(
                'sepa_flow', 'adyen_abstract', $this->_order->getStoreId())
            );
1159
            $_paymentCode = $this->_paymentMethodCode();
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
            $captureModeOpenInvoice = $this->_getConfigData(
                'auto_capture_openinvoice', 'adyen_abstract', $this->_order->getStoreId()
            );
            $captureModePayPal = trim($this->_getConfigData(
                'paypal_capture_mode', 'adyen_abstract', $this->_order->getStoreId())
            );

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

1179
            // payment method ideal, cash adyen_boleto or adyen_pos has direct capture
1180 1181 1182 1183
            if ($_paymentCode == "adyen_pos" || (($_paymentCode == "adyen_sepa" ||
                        $this->_paymentMethod == "sepadirectdebit") && $sepaFlow != "authcap")) {
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method does not allow manual capture.(2) paymentCode:' .
1184
                    $_paymentCode . ' paymentMethod:' . $this->_paymentMethod . ' sepaFLow:'.$sepaFlow
1185
                );
1186 1187
                return true;
            }
1188

1189
            // if auto capture mode for openinvoice is turned on then use auto capture
1190
            if ($captureModeOpenInvoice == true &&
1191 1192
                $this->_adyenHelper->isPaymentMethodOpenInvoiceMethod($this->_paymentMethod)
            ) {
1193 1194 1195
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'This payment method is configured to be working as auto capture '
                );
1196
                return true;
1197 1198
            }
            // if PayPal capture modues is different from the default use this one
1199 1200 1201 1202 1203
            if (strcmp($this->_paymentMethod, 'paypal' ) === 0 && $captureModePayPal != "") {
                if (strcmp($captureModePayPal, 'auto') === 0 ) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as auto capture'
                    );
1204
                    return true;
1205 1206 1207 1208
                } elseif (strcmp($captureModePayPal, 'manual') === 0 ) {
                    $this->_adyenLogger->addAdyenNotificationCronjob(
                        'This payment method is paypal and configured to work as manual capture'
                    );
1209 1210 1211 1212
                    return false;
                }
            }
            if (strcmp($captureMode, 'manual') === 0) {
1213
                $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode for this payment is set to manual');
1214 1215
                return false;
            }
1216 1217 1218 1219 1220

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

            $this->_adyenLogger->addAdyenNotificationCronjob('Capture mode is set to auto capture');
1227 1228 1229 1230
            return true;

        } else {
            // does not allow manual capture so is always immediate capture
1231
            $this->_adyenLogger->addAdyenNotificationCronjob('This payment method does not allow manual capture');
1232
            return true;
1233
        }
1234 1235 1236 1237 1238 1239

    }

    /**
     * Validate if this payment methods allows manual capture
     * This is a default can be forced differently to overrule on acquirer level
1240 1241
     *
     * @return bool|null
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
     */
    protected function _manualCaptureAllowed()
    {
        $manualCaptureAllowed = null;
        $paymentMethod = $this->_paymentMethod;

        switch($paymentMethod) {
            case 'cup':
            case 'cartebancaire':
            case 'visa':
            case 'mc':
            case 'uatp':
            case 'amex':
            case 'bcmc':
            case 'maestro':
rikterbeek's avatar
rikterbeek committed
1257
            case 'maestrouk':
1258 1259 1260 1261 1262 1263 1264
            case 'diners':
            case 'discover':
            case 'jcb':
            case 'laser':
            case 'paypal':
            case 'klarna':
            case 'afterpay_default':
Rik ter Beek's avatar
Rik ter Beek committed
1265
            case 'ratepay':
1266 1267 1268 1269 1270
            case 'sepadirectdebit':
                $manualCaptureAllowed = true;
                break;
            default:
                // To be sure check if it payment method starts with afterpay_ then manualCapture is allowed
1271
                if (strlen($this->_paymentMethod) >= 9 && substr($this->_paymentMethod, 0, 9) == "afterpay_") {
1272 1273 1274
                    $manualCaptureAllowed = true;
                }
                $manualCaptureAllowed = false;
1275
        }
1276 1277

        return $manualCaptureAllowed;
1278 1279 1280 1281 1282
    }

    /**
     * @return bool
     */
1283 1284
    protected function _isBankTransfer()
    {
1285
        if (strlen($this->_paymentMethod) >= 12 && substr($this->_paymentMethod, 0, 12) == "bankTransfer") {
1286 1287 1288 1289 1290 1291 1292
            $isBankTransfer = true;
        } else {
            $isBankTransfer = false;
        }
        return $isBankTransfer;
    }

1293 1294 1295
    /**
     * @return mixed
     */
1296 1297
    protected function _getFraudManualReviewStatus()
    {
1298 1299 1300
        return $this->_getConfigData(
            'fraud_manual_review_status', 'adyen_abstract', $this->_order->getStoreId()
        );
1301 1302
    }

1303 1304 1305
    /**
     * @return mixed
     */
1306 1307
    protected function _getFraudManualReviewAcceptStatus()
    {
1308 1309 1310
        return $this->_getConfigData(
            'fraud_manual_review_accept_status', 'adyen_abstract', $this->_order->getStoreId()
        );
1311 1312
    }

1313
    /**
1314 1315
     * @param int $paymentId
     * @param string $orderCurrencyCode
1316 1317
     * @return bool
     */
1318
    protected function _isTotalAmount($paymentId, $orderCurrencyCode)
1319 1320 1321 1322
    {
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Validate if AUTHORISATION notification has the total amount of the order'
        );
1323

1324 1325 1326 1327 1328 1329 1330
        // get total amount of the order
        $grandTotal = (int) $this->_adyenHelper->formatAmount($this->_order->getGrandTotal(), $orderCurrencyCode);

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

1332
        if ($res && isset($res[0]) && is_array($res[0])) {
1333 1334
            $amount = $res[0]['total_amount'];
            $orderAmount = $this->_adyenHelper->formatAmount($amount, $orderCurrencyCode);
1335 1336 1337 1338 1339 1340
            $this->_adyenLogger->addAdyenNotificationCronjob(
                sprintf('The grandtotal amount is %s and the total order amount that is authorised is: %s',
                    $grandTotal,
                    $orderAmount
                )
            );
1341 1342 1343 1344 1345 1346 1347 1348 1349 1350

            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;
            }
1351
        }
1352
        return false;
1353 1354
    }

1355 1356 1357
    /**
     * @throws Exception
     * @throws \Magento\Framework\Exception\LocalizedException
1358
     * @return void
1359
     */
1360 1361
    protected function _createInvoice()
    {
1362
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating invoice for order');
1363 1364 1365

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

1366 1367
            /* We do not use this inside a transaction because order->save()
             * is always done on the end of the notification
1368 1369 1370 1371 1372 1373 1374
             * 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
1375
                $invoice->setTransactionId($this->_pspReference);
1376

1377

1378
                $autoCapture = $this->_isAutoCapture();
1379 1380 1381
                $createPendingInvoice = (bool) $this->_getConfigData(
                    'create_pending_invoice', 'adyen_abstract', $this->_order->getStoreId()
                );
1382

1383
                if ((!$autoCapture) && ($createPendingInvoice)) {
1384 1385 1386

                    // if amount is zero create a offline invoice
                    $value = (int)$this->_value;
1387
                    if ($value == 0) {
rikterbeek's avatar
rikterbeek committed
1388
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
1389
                    } else {
rikterbeek's avatar
rikterbeek committed
1390
                        $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::NOT_CAPTURE);
1391 1392 1393 1394 1395 1396 1397 1398
                    }

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

                $invoice->save();
1399
                $this->_adyenLogger->addAdyenNotificationCronjob('Created invoice');
1400
            } catch (Exception $e) {
1401 1402 1403
                $this->_adyenLogger->addAdyenNotificationCronjob(
                    'Error saving invoice. The error message is: ' . $e->getMessage()
                );
1404 1405 1406 1407 1408
                throw new Exception(sprintf('Error saving invoice. The error message is:', $e->getMessage()));
            }

            $this->_setPaymentAuthorized();

1409 1410 1411 1412
            $invoiceAutoMail = (bool) $this->_getConfigData(
                'send_invoice_update_mail', 'adyen_abstract', $this->_order->getStoreId()
            );

1413 1414 1415 1416
            if ($invoiceAutoMail) {
                $invoice->sendEmail();
            }
        } else {
1417
            $this->_adyenLogger->addAdyenNotificationCronjob('It is not possible to create invoice for this order');
1418 1419 1420 1421
        }
    }

    /**
1422 1423 1424
     * @param bool $manualReviewComment
     * @param bool $createInvoice
     * @throws Exception
1425 1426 1427
     */
    protected function _setPaymentAuthorized($manualReviewComment = true, $createInvoice = false)
    {
1428
        $this->_adyenLogger->addAdyenNotificationCronjob('Set order to authorised');
1429 1430 1431 1432 1433 1434 1435

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

        // create invoice for the capture notification if you are on manual capture
1436 1437 1438 1439
        if ($createInvoice == true && $amount == $orderAmount) {
            $this->_adyenLogger->addAdyenNotificationCronjob(
                'amount notification:'.$amount . ' amount order:'.$orderAmount
            );
rikterbeek's avatar
rikterbeek committed
1440
            $this->_createInvoice();
1441
        }
1442
        
1443 1444 1445
        $status = $this->_getConfigData(
            'payment_authorized', 'adyen_abstract', $this->_order->getStoreId()
        );
1446 1447

        // virtual order can have different status
1448
        if ($this->_order->getIsVirtual()) {
1449
            $this->_adyenLogger->addAdyenNotificationCronjob('Product is a virtual product');
1450 1451 1452
            $virtualStatus = $this->_getConfigData(
                'payment_authorized_virtual', 'adyen_abstract', $this->_order->getStoreId()
            );
1453 1454
            if ($virtualStatus != "") {
                $status = $virtualStatus;
1455 1456 1457 1458
            }
        }

        // check for boleto if payment is totally paid
1459
        if ($this->_paymentMethodCode() == "adyen_boleto") {
1460 1461 1462 1463 1464

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

1465
            if ($orginalAmount != $paidAmount) {
1466 1467 1468

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

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

1475
                if ($paidAmount > $orginalAmount) {
1476 1477 1478
                    $overpaidStatus =  $this->_getConfigData(
                        'order_overpaid_status', 'adyen_boleto', $this->_order->getStoreId()
                    );
1479 1480 1481
                    // check if there is selected a status if not fall back to the default
                    $status = (!empty($overpaidStatus)) ? $overpaidStatus : $status;
                } else {
1482 1483 1484
                    $underpaidStatus = $this->_getConfigData(
                        'order_underpaid_status', 'adyen_boleto', $this->_order->getStoreId()
                    );
1485 1486 1487 1488 1489 1490 1491 1492 1493
                    // 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
1494
        if ($manualReviewComment == true && $this->_fraudManualReview) {
1495 1496
            // check if different status is selected
            $fraudManualReviewStatus = $this->_getFraudManualReviewStatus();
1497
            if ($fraudManualReviewStatus != "") {
1498 1499 1500 1501 1502 1503 1504
                $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);
1505 1506 1507
        $this->_adyenLogger->addAdyenNotificationCronjob(
            'Order status is changed to authorised status, status is ' . $status
        );
1508 1509
    }

1510

rikterbeek's avatar
rikterbeek committed
1511
    /**
1512
     * Create shipment
rikterbeek's avatar
rikterbeek committed
1513
     *
1514
     * @throws bool
rikterbeek's avatar
rikterbeek committed
1515
     */
1516 1517
    protected function _createShipment()
    {
1518
        $this->_adyenLogger->addAdyenNotificationCronjob('Creating shipment for order');
rikterbeek's avatar
rikterbeek committed
1519 1520
        // create shipment for cash payment
        $payment = $this->_order->getPayment()->getMethodInstance();
1521 1522
        if ($this->_order->canShip()) {
            $itemQty = [];
rikterbeek's avatar
rikterbeek committed
1523
            $shipment = $this->_order->prepareShipment($itemQty);
1524
            if ($shipment) {
rikterbeek's avatar
rikterbeek committed
1525 1526 1527 1528
                $shipment->register();
                $shipment->getOrder()->setIsInProcess(true);
                $comment = __('Shipment created by Adyen');
                $shipment->addComment($comment);
rikterbeek's avatar
rikterbeek committed
1529 1530 1531 1532

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

1536
                $this->_adyenLogger->addAdyenNotificationCronjob('Order is shipped');
rikterbeek's avatar
rikterbeek committed
1537 1538
            }
        } else {
1539
            $this->_adyenLogger->addAdyenNotificationCronjob('Order can\'t be shipped');
rikterbeek's avatar
rikterbeek committed
1540 1541 1542
        }
    }

1543 1544 1545
    /**
     * Retrieve information from payment configuration
     *
1546 1547 1548
     * @param $field
     * @param string $paymentMethodCode
     * @param $storeId
1549 1550 1551 1552 1553 1554 1555 1556
     * @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);
    }
}