Passed
Pull Request — master (#16)
by
unknown
03:26
created

Index::checkMerchantOrderStatus()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DigitalOrigin\Pmt\Controller\Notify;
4
5
use Magento\Quote\Model\QuoteManagement;
6
use Magento\Quote\Api\Data\PaymentInterface;
7
use Magento\Sales\Api\Data\OrderInterface;
8
use Magento\Sales\Api\OrderRepositoryInterface;
9
use Magento\Quote\Model\Quote;
10
use Magento\Quote\Model\QuoteRepository;
11
use Magento\Framework\App\Action\Context;
12
use Magento\Framework\App\Action\Action;
13
use PagaMasTarde\ModuleUtils\Exception\MerchantOrderNotFoundException;
14
use PagaMasTarde\OrdersApiClient\Client;
15
use DigitalOrigin\Pmt\Helper\Config;
16
use DigitalOrigin\Pmt\Helper\ExtraConfig;
17
use Magento\Framework\App\ResourceConnection;
18
use Magento\Checkout\Model\Session;
19
use Magento\Framework\DB\Ddl\Table;
20
use PagaMasTarde\ModuleUtils\Exception\AmountMismatchException;
21
use PagaMasTarde\ModuleUtils\Exception\ConcurrencyException;
22
use PagaMasTarde\ModuleUtils\Exception\NoIdentificationException;
23
use PagaMasTarde\ModuleUtils\Exception\OrderNotFoundException;
24
use PagaMasTarde\ModuleUtils\Exception\QuoteNotFoundException;
25
use PagaMasTarde\ModuleUtils\Exception\UnknownException;
26
use PagaMasTarde\ModuleUtils\Exception\WrongStatusException;
27
use PagaMasTarde\ModuleUtils\Model\Response\JsonSuccessResponse;
28
use PagaMasTarde\ModuleUtils\Model\Response\JsonExceptionResponse;
29
use PagaMasTarde\ModuleUtils\Exception\AlreadyProcessedException;
30
use PagaMasTarde\ModuleUtils\Model\Log\LogEntry;
31
use Magento\Framework\App\RequestInterface;
32
use Magento\Framework\App\Request\InvalidRequestException;
33
34
/**
35
 * Class Index
36
 * @package DigitalOrigin\Pmt\Controller\Notify
37
 */
38
class Index extends Action
39
{
40
    /** Orders tablename */
41
    const ORDERS_TABLE = 'cart_process';
42
43
    /** Concurrency tablename */
44
    const CONCURRENCY_TABLE = 'pmt_orders';
45
46
    /** Concurrency tablename */
47
    const LOGS_TABLE = 'pmt_logs';
48
49
    /** Payment code */
50
    const PAYMENT_METHOD = 'paylater';
51
52
    /**
53
     * EXCEPTION RESPONSES
54
     */
55
    const CPO_ERR_MSG = 'Order not confirmed';
56
    const CPO_OK_MSG = 'Order confirmed';
57
58
    /** @var QuoteManagement */
59
    protected $quoteManagement;
60
61
    /** @var PaymentInterface $paymentInterface */
62
    protected $paymentInterface;
63
64
    /** @var OrderRepositoryInterface $orderRepositoryInterface */
65
    protected $orderRepositoryInterface;
66
67
    /** @var Quote $quote */
68
    protected $quote;
69
70
    /** @var QuoteRepository $quoteRepository */
71
    protected $quoteRepository;
72
73
    /** @var mixed $config */
74
    protected $config;
75
76
    /** @var mixed $quoteId */
77
    protected $quoteId;
78
79
    /** @var array $notifyResult */
80
    protected $notifyResult;
81
82
    /** @var mixed $magentoOrderId */
83
    protected $magentoOrderId;
84
85
    /** @var mixed $pmtOrder */
86
    protected $pmtOrder;
87
88
    /** @var ResourceConnection $dbObject */
89
    protected $dbObject;
90
91
    /** @var Session $checkoutSession */
92
    protected $checkoutSession;
93
94
    /** @var Client $orderClient */
95
    protected $orderClient;
96
97
    /** @var mixed $pmtOrderId */
98
    protected $pmtOrderId;
99
100
    /** @var  OrderInterface $magentoOrder */
101
    protected $magentoOrder;
102
103
    /** @var ExtraConfig $extraConfig */
104
    protected $extraConfig;
105
106
    /**
107
     * Index constructor.
108
     *
109
     * @param Context                  $context
110
     * @param Quote                    $quote
111
     * @param QuoteManagement          $quoteManagement
112
     * @param PaymentInterface         $paymentInterface
113
     * @param Config                   $config
114
     * @param QuoteRepository          $quoteRepository
115
     * @param OrderRepositoryInterface $orderRepositoryInterface
116
     * @param ResourceConnection       $dbObject
117
     * @param Session                  $checkoutSession
118
     * @param ExtraConfig              $extraConfig
119
     */
120
    public function __construct(
121
        Context $context,
122
        Quote $quote,
123
        QuoteManagement $quoteManagement,
124
        PaymentInterface $paymentInterface,
125
        Config $config,
126
        QuoteRepository $quoteRepository,
127
        OrderRepositoryInterface $orderRepositoryInterface,
128
        ResourceConnection $dbObject,
129
        Session $checkoutSession,
130
        ExtraConfig $extraConfig
131
    ) {
132
        parent::__construct($context);
133
        $this->quote = $quote;
134
        $this->quoteManagement = $quoteManagement;
135
        $this->paymentInterface = $paymentInterface;
136
        $this->extraConfig = $extraConfig->getExtraConfig();
0 ignored issues
show
Documentation Bug introduced by
It seems like $extraConfig->getExtraConfig() of type array or array is incompatible with the declared type DigitalOrigin\Pmt\Helper\ExtraConfig of property $extraConfig.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
137
        $this->config = $config->getConfig();
138
        $this->quoteRepository = $quoteRepository;
139
        $this->orderRepositoryInterface = $orderRepositoryInterface;
140
        $this->dbObject = $dbObject;
141
        $this->checkoutSession = $checkoutSession;
142
    }
143
144
    /**
145
     * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void
146
     * @throws UnknownException
147
     */
148
    public function execute()
149
    {
150
        try {
151
            $this->checkConcurrency();
152
            $this->getMerchantOrder();
153
            $this->getPmtOrderId();
154
            $this->getPmtOrder();
155
            $this->checkOrderStatus();
156
            $this->checkMerchantOrderStatus();
157
            $this->validateAmount();
158
            $this->processMerchantOrder();
159
        } catch (\Exception $exception) {
160
            $jsonResponse = new JsonExceptionResponse();
161
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
162
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
163
            $jsonResponse->setException($exception);
164
            $response = $jsonResponse->toJson();
165
            $this->insertLog($exception);
166
        }
167
168
        try {
169
            if (!isset($response)) {
170
                $this->confirmPmtOrder();
171
                $jsonResponse = new JsonSuccessResponse();
172
                $jsonResponse->setMerchantOrderId($this->magentoOrderId);
173
                $jsonResponse->setPmtOrderId($this->pmtOrderId);
174
            }
175
        } catch (\Exception $exception) {
176
            $this->rollbackMerchantOrder();
177
            $jsonResponse = new JsonExceptionResponse();
178
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
179
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
180
            $jsonResponse->setException($exception);
181
            $jsonResponse->toJson();
182
            $this->insertLog($exception);
183
        }
184
185
        $this->unblockConcurrency(true);
186
187
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
188
            $jsonResponse->printResponse();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $jsonResponse does not seem to be defined for all execution paths leading up to this point.
Loading history...
189
        } else {
190
            $returnUrl = $this->getRedirectUrl();
191
            $this->_redirect($returnUrl);
192
        }
193
    }
194
195
    /**
196
     * COMMON FUNCTIONS
197
     */
198
199
    /**
200
     * @throws QuoteNotFoundException
201
     * @throws UnknownException
202
     */
203
    private function checkConcurrency()
204
    {
205
        $this->getQuoteId();
206
        $this->checkDbTable();
207
        $this->unblockConcurrency();
208
        $this->blockConcurrency();
209
    }
210
211
    /**
212
     * @throws MerchantOrderNotFoundException
213
     */
214
    private function getMerchantOrder()
215
    {
216
        try {
217
            /** @var Quote quote */
218
            $this->quote = $this->quoteRepository->get($this->quoteId);
219
        } catch (\Exception $e) {
220
            throw new MerchantOrderNotFoundException();
221
        }
222
    }
223
224
    /**
225
     * @throws UnknownException
226
     */
227
    private function getPmtOrderId()
228
    {
229
        try {
230
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
231
            $dbConnection     = $this->dbObject->getConnection();
232
            $tableName        = $this->dbObject->getTableName(self::ORDERS_TABLE);
233
            $query            = "select order_id from $tableName where id='$this->quoteId'";
234
            $queryResult      = $dbConnection->fetchRow($query);
235
            $this->pmtOrderId = $queryResult['order_id'];
236
            if ($this->pmtOrderId == '') {
237
                throw new NoIdentificationException();
238
            }
239
        } catch (\Exception $e) {
240
            throw new UnknownException($e->getMessage());
241
        }
242
    }
243
244
    /**
245
     * @throws OrderNotFoundException
246
     */
247
    private function getPmtOrder()
248
    {
249
        try {
250
            $this->orderClient = new Client($this->config['pmt_public_key'], $this->config['pmt_private_key']);
251
            $this->pmtOrder = $this->orderClient->getOrder($this->pmtOrderId);
252
        } catch (\Exception $e) {
253
            throw new OrderNotFoundException();
254
        }
255
    }
256
257
    /**
258
     * @throws AlreadyProcessedException
259
     * @throws WrongStatusException
260
     */
261
    private function checkOrderStatus()
262
    {
263
        try {
264
            $this->checkPmtStatus(array('AUTHORIZED'));
265
        } catch (\Exception $e) {
266
            $this->getMagentoOrderId();
267
            if ($this->magentoOrderId!='') {
268
                throw new AlreadyProcessedException();
269
            } else {
270
                throw new WrongStatusException($this->pmtOrder->getStatus());
271
            }
272
        }
273
    }
274
275
    /**
276
     * @throws AlreadyProcessedException
277
     */
278
    private function checkMerchantOrderStatus()
279
    {
280
        if ($this->quote->getIsActive()=='0') {
281
            $this->getMagentoOrderId();
282
            throw new AlreadyProcessedException();
283
        }
284
    }
285
286
    /**
287
     * @throws AmountMismatchException
288
     */
289
    private function validateAmount()
290
    {
291
        $pmtAmount = $this->pmtOrder->getShoppingCart()->getTotalAmount();
292
        $merchantAmount = intval(strval(100 * $this->quote->getGrandTotal()));
293
        if ($pmtAmount != $merchantAmount) {
294
            throw new AmountMismatchException($pmtAmount, $merchantAmount);
295
        }
296
    }
297
298
    /**
299
     * @throws UnknownException
300
     */
301
    private function processMerchantOrder()
302
    {
303
        try {
304
            $this->saveOrder();
305
            $this->updateBdInfo();
306
        } catch (\Exception $e) {
307
            throw new UnknownException($e->getMessage());
308
        }
309
    }
310
311
    /**
312
     * @return false|string
313
     * @throws UnknownException
314
     */
315
    private function confirmPmtOrder()
316
    {
317
        try {
318
            $this->pmtOrder = $this->orderClient->confirmOrder($this->pmtOrderId);
319
        } catch (\Exception $e) {
320
            throw new UnknownException($e->getMessage());
321
        }
322
323
        $jsonResponse = new JsonSuccessResponse();
324
        $jsonResponse->setStatusCode(200);
325
        $jsonResponse->setMerchantOrderId($this->magentoOrderId);
326
        $jsonResponse->setPmtOrderId($this->pmtOrderId);
327
        $jsonResponse->setResult(self::CPO_OK_MSG);
328
        return $jsonResponse->toJson();
329
    }
330
331
    /**
332
     * UTILS FUNCTIONS
333
     */
334
335
    /** STEP 1 CC - Check concurrency
336
     * @throws QuoteNotFoundException
337
     */
338
    private function getQuoteId()
339
    {
340
        $this->quoteId = $this->getRequest()->getParam('quoteId');
341
        if ($this->quoteId == '') {
342
            throw new QuoteNotFoundException();
343
        }
344
    }
345
346
    /**
347
     * @return \Zend_Db_Statement_Interface
348
     * @throws UnknownException
349
     */
350
    private function checkDbTable()
351
    {
352
        try {
353
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
354
            $dbConnection = $this->dbObject->getConnection();
355
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
356
            $query        = "CREATE TABLE IF NOT EXISTS $tableName(`id` int not null,`timestamp` int not null,PRIMARY KEY (`id`))";
357
358
            return $dbConnection->query($query);
359
        } catch (\Exception $e) {
360
            throw new UnknownException($e->getMessage());
361
        }
362
    }
363
364
    /**
365
     * @return void|\Zend_Db_Statement_Interface
366
     * @throws UnknownException
367
     */
368
    private function checkDbLogTable()
369
    {
370
        try {
371
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
372
            $dbConnection = $this->dbObject->getConnection();
373
            $tableName = $this->dbObject->getTableName(self::LOGS_TABLE);
374
            if (!$dbConnection->isTableExists($tableName)) {
375
                $table = $dbConnection
376
                    ->newTable($tableName)
377
                    ->addColumn('id', Table::TYPE_SMALLINT, null, array('nullable'=>false, 'auto_increment'=>true, 'primary'=>true))
378
                    ->addColumn('log', Table::TYPE_TEXT, null, array('nullable'=>false))
379
                    ->addColumn('createdAt', Table::TYPE_TIMESTAMP, null, array('nullable'=>false, 'default'=>Table::TIMESTAMP_INIT));
380
                return $dbConnection->createTable($table);
381
            }
382
383
            return;
384
        } catch (\Exception $e) {
385
            throw new UnknownException($e->getMessage());
386
        }
387
    }
388
389
    /**
390
     * @param bool $mode
391
     *
392
     * @throws \Exception
393
     */
394
    private function unblockConcurrency($mode = false)
395
    {
396
        try {
397
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
398
            $dbConnection = $this->dbObject->getConnection();
399
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
400
            if ($mode == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
401
                $dbConnection->delete($tableName, "timestamp<".(time() - 5));
402
            } elseif ($this->quoteId!='') {
403
                $dbConnection->delete($tableName, "id=".$this->quoteId);
404
            }
405
        } catch (Exception $exception) {
0 ignored issues
show
Bug introduced by
The type DigitalOrigin\Pmt\Controller\Notify\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
406
            throw new ConcurrencyException();
407
        }
408
    }
409
410
    /**
411
     * @throws \Exception
412
     */
413
    private function blockConcurrency()
414
    {
415
        try {
416
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
417
            $dbConnection = $this->dbObject->getConnection();
418
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
419
            $dbConnection->insert($tableName, array('id'=>$this->quoteId, 'timestamp'=>time()));
420
        } catch (Exception $exception) {
421
            throw new ConcurrencyException();
422
        }
423
    }
424
425
    /** STEP 2 GMO - Get Merchant Order */
426
    /** STEP 3 GPOI - Get Pmt OrderId */
427
    /** STEP 4 GPO - Get Pmt Order */
428
    /** STEP 5 COS - Check Order Status */
429
    /**
430
     * @param $statusArray
431
     *
432
     * @throws \Exception
433
     */
434
    private function checkPmtStatus($statusArray)
435
    {
436
        $pmtStatus = array();
437
        foreach ($statusArray as $status) {
438
            $pmtStatus[] = constant("\PagaMasTarde\OrdersApiClient\Model\Order::STATUS_$status");
439
        }
440
441
        $payed = in_array($this->pmtOrder->getStatus(), $pmtStatus);
442
        if (!$payed) {
443
            throw new WrongStatusException($this->pmtOrder->getStatus());
444
        }
445
    }
446
447
    /** STEP 6 CMOS - Check Merchant Order Status */
448
    /**
449
     * @throws \Exception
450
     */
451
    private function getMagentoOrderId()
452
    {
453
        try {
454
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
455
            $dbConnection = $this->dbObject->getConnection();
456
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
457
            $pmtOrderId   = $this->pmtOrderId;
458
459
            $query        = "select mg_order_id from $tableName where id='$this->quoteId' and order_id='$pmtOrderId'";
460
            $queryResult  = $dbConnection->fetchRow($query);
461
            $this->magentoOrderId = $queryResult['mg_order_id'];
462
        } catch (\Exception $e) {
463
            throw new UnknownException($e->getMessage());
464
        }
465
    }
466
467
    /** STEP 7 VA - Validate Amount */
468
    /** STEP 8 PMO - Process Merchant Order */
469
    /**
470
     * @throws UnknownException
471
     */
472
    private function saveOrder()
473
    {
474
        try {
475
            $this->paymentInterface->setMethod(self::PAYMENT_METHOD);
476
            $this->magentoOrderId = $this->quoteManagement->placeOrder($this->quoteId, $this->paymentInterface);
477
            /** @var \Magento\Sales\Api\Data\OrderInterface magentoOrder */
478
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
479
480
            if ($this->magentoOrderId == '') {
481
                throw new UnknownException('Order can not be saved');
482
            }
483
        } catch (\Exception $e) {
484
            throw new UnknownException($e->getMessage());
485
        }
486
    }
487
488
    /**
489
     * @throws UnknownException
490
     */
491
    private function updateBdInfo()
492
    {
493
        try {
494
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
495
            $dbConnection = $this->dbObject->getConnection();
496
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
497
            $pmtOrderId   = $this->pmtOrder->getId();
498
            $dbConnection->update(
499
                $tableName,
500
                array('mg_order_id' => $this->magentoOrderId),
501
                "order_id='$pmtOrderId' and id='$this->quoteId'"
502
            );
503
        } catch (\Exception $e) {
504
            throw new UnknownException($e->getMessage());
505
        }
506
    }
507
508
    /** STEP 9 CPO - Confirmation Pmt Order */
509
    /**
510
     * @throws UnknownException
511
     */
512
    private function rollbackMerchantOrder()
513
    {
514
        try {
515
            $this->magentoOrder->setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT, true);
0 ignored issues
show
Unused Code introduced by
The call to Magento\Sales\Api\Data\OrderInterface::setState() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

515
            $this->magentoOrder->/** @scrutinizer ignore-call */ 
516
                                 setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT, true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
516
            $this->magentoOrder->setStatus(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT);
517
            $this->magentoOrder->save();
518
        } catch (\Exception $e) {
519
            throw new UnknownException($e->getMessage());
520
        }
521
    }
522
523
    /**
524
     * @return string
525
     */
526
    private function getRedirectUrl()
527
    {
528
        //$returnUrl = 'checkout/#payment';
529
        $returnUrl = $this->_url->getUrl('checkout', ['_fragment' => 'payment']);
530
        if ($this->magentoOrderId!='') {
531
            /** @var Order $this->magentoOrder */
532
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
533
            if (!$this->_objectManager->get(\Magento\Checkout\Model\Session\SuccessValidator::class)->isValid()) {
534
                $this->checkoutSession
535
                    ->setLastOrderId($this->magentoOrderId)
536
                    ->setLastRealOrderId($this->magentoOrder->getIncrementId())
537
                    ->setLastQuoteId($this->quoteId)
538
                    ->setLastSuccessQuoteId($this->quoteId)
539
                    ->setLastOrderStatus($this->magentoOrder->getStatus());
540
            }
541
542
            //Magento status flow => https://docs.magento.com/m2/ce/user_guide/sales/order-status-workflow.html
543
            //Order Workflow => https://docs.magento.com/m2/ce/user_guide/sales/order-workflow.html
544
            $orderStatus    = strtolower($this->magentoOrder->getStatus());
545
            $acceptedStatus = array('processing', 'completed');
546
            if (in_array($orderStatus, $acceptedStatus)) {
547
                if (isset($this->extraConfig['PMT_OK_URL']) &&  $this->extraConfig['PMT_OK_URL']!= '') {
548
                    $returnUrl = $this->extraConfig['PMT_OK_URL'];
549
                } else {
550
                    $returnUrl = 'checkout/onepage/success';
551
                }
552
            } else {
553
                if (isset($this->extraConfig['PMT_KO_URL']) && $this->extraConfig['PMT_KO_URL'] != '') {
554
                    $returnUrl = $this->extraConfig['PMT_KO_URL'];
555
                } else {
556
                    //$returnUrl = 'checkout/#payment';
557
                    $returnUrl = $this->_url->getUrl('checkout', ['_fragment' => 'payment']);
558
                }
559
            }
560
        }
561
        return $returnUrl;
562
    }
563
564
    /**
565
     * @param $exceptionMessage
566
     *
567
     * @throws UnknownException
568
     */
569
    private function insertLog($exceptionMessage)
570
    {
571
        try {
572
            if ($exceptionMessage instanceof \Exception) {
573
                $this->checkDbLogTable();
574
                $logEntry = new LogEntry();
575
                $logEntryJson = $logEntry->error($exceptionMessage)->toJson();
576
577
                /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
578
                $dbConnection = $this->dbObject->getConnection();
579
                $tableName    = $this->dbObject->getTableName(self::LOGS_TABLE);
580
                $dbConnection->insert($tableName, array('log' => $logEntryJson));
581
            }
582
        } catch (\Exception $e) {
583
            throw new UnknownException($e->getMessage());
584
        }
585
    }
586
}
587