Passed
Pull Request — master (#13)
by Raúl
06:32
created

Index::getPmtOrder()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 3
nop 0
dl 0
loc 7
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\OrdersApiClient\Client;
14
use DigitalOrigin\Pmt\Helper\Config;
15
use Magento\Framework\App\ResourceConnection;
16
use Magento\Checkout\Model\Session;
17
use Magento\Framework\DB\Ddl\Table;
18
use PagaMasTarde\ModuleUtils\Exception\AmountMismatchException;
19
use PagaMasTarde\ModuleUtils\Exception\ConcurrencyException;
20
use PagaMasTarde\ModuleUtils\Exception\MerchantOrderNotFoundException;
21
use PagaMasTarde\ModuleUtils\Exception\NoIdentificationException;
22
use PagaMasTarde\ModuleUtils\Exception\OrderNotFoundException;
23
use PagaMasTarde\ModuleUtils\Exception\QuoteNotFoundException;
24
use PagaMasTarde\ModuleUtils\Exception\UnknownException;
25
use PagaMasTarde\ModuleUtils\Exception\WrongStatusException;
26
use PagaMasTarde\ModuleUtils\Model\Response\JsonSuccessResponse;
27
use PagaMasTarde\ModuleUtils\Model\Response\JsonExceptionResponse;
28
use PagaMasTarde\ModuleUtils\Exception\AlreadyProcessedException;
29
use PagaMasTarde\ModuleUtils\Model\Log\LogEntry;
30
31
/**
32
 * Class Index
33
 * @package DigitalOrigin\Pmt\Controller\Notify
34
 */
35
class Index extends Action
36
{
37
    /** Orders tablename */
38
    const ORDERS_TABLE = 'cart_process';
39
40
    /** Concurrency tablename */
41
    const CONCURRENCY_TABLE = 'pmt_orders';
42
43
    /** Concurrency tablename */
44
    const LOGS_TABLE = 'pmt_logs';
45
46
    /** Payment code */
47
    const PAYMENT_METHOD = 'paylater';
48
49
    /**
50
     * EXCEPTION RESPONSES
51
     */
52
    const CPO_ERR_MSG = 'Order not confirmed';
53
    const CPO_OK_MSG = 'Order confirmed';
54
55
    /** @var QuoteManagement */
56
    protected $quoteManagement;
57
58
    /** @var PaymentInterface $paymentInterface */
59
    protected $paymentInterface;
60
61
    /** @var OrderRepositoryInterface $orderRepositoryInterface */
62
    protected $orderRepositoryInterface;
63
64
    /** @var Quote $quote */
65
    protected $quote;
66
67
    /** @var QuoteRepository $quoteRepository */
68
    protected $quoteRepository;
69
70
    /** @var mixed $config */
71
    protected $config;
72
73
    /** @var mixed $quoteId */
74
    protected $quoteId;
75
76
    /** @var array $notifyResult */
77
    protected $notifyResult;
78
79
    /** @var mixed $magentoOrderId */
80
    protected $magentoOrderId;
81
82
    /** @var mixed $pmtOrder */
83
    protected $pmtOrder;
84
85
    /** @var ResourceConnection $dbObject */
86
    protected $dbObject;
87
88
    /** @var Session $checkoutSession */
89
    protected $checkoutSession;
90
91
    /** @var Client $orderClient */
92
    protected $orderClient;
93
94
    /** @var mixed $pmtOrderId */
95
    protected $pmtOrderId;
96
97
    /** @var  OrderInterface $magentoOrder */
98
    protected $magentoOrder;
99
100
    /**
101
     * Index constructor.
102
     *
103
     * @param Context                  $context
104
     * @param Quote                    $quote
105
     * @param QuoteManagement          $quoteManagement
106
     * @param PaymentInterface         $paymentInterface
107
     * @param Config                   $config
108
     * @param QuoteRepository          $quoteRepository
109
     * @param OrderRepositoryInterface $orderRepositoryInterface
110
     * @param ResourceConnection       $dbObject
111
     * @param Session                  $checkoutSession
112
     */
113
    public function __construct(
114
        Context $context,
115
        Quote $quote,
116
        QuoteManagement $quoteManagement,
117
        PaymentInterface $paymentInterface,
118
        Config $config,
119
        QuoteRepository $quoteRepository,
120
        OrderRepositoryInterface $orderRepositoryInterface,
121
        ResourceConnection $dbObject,
122
        Session $checkoutSession
123
    ) {
124
        parent::__construct($context);
125
        $this->quote = $quote;
126
        $this->quoteManagement = $quoteManagement;
127
        $this->paymentInterface = $paymentInterface;
128
        $this->config = $config->getConfig();
129
        $this->quoteRepository = $quoteRepository;
130
        $this->orderRepositoryInterface = $orderRepositoryInterface;
131
        $this->dbObject = $dbObject;
132
        $this->checkoutSession = $checkoutSession;
133
    }
134
135
    //MAIN FUNCTION
136
    public function execute()
137
    {
138
        try {
139
            $this->checkConcurrency();
140
            $this->getMerchantOrder();
141
            $this->getPmtOrderId();
142
            $this->getPmtOrder();
143
            $this->checkOrderStatus();
144
            $this->checkMerchantOrderStatus();
145
            $this->validateAmount();
146
            $this->processMerchantOrder();
147
        } catch (\Exception $exception) {
148
            $jsonResponse = new JsonExceptionResponse();
149
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
150
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
151
            $jsonResponse->setException($exception);
152
            $response = $jsonResponse->toJson();
153
            $this->insertLog($exception);
154
        }
155
156
        try {
157
            if (!isset($response)) {
158
                $this->confirmPmtOrder();
159
                $jsonResponse = new JsonSuccessResponse();
160
                $jsonResponse->setMerchantOrderId($this->magentoOrderId);
161
                $jsonResponse->setPmtOrderId($this->pmtOrderId);
162
            }
163
        } catch (\Exception $exception) {
164
            $this->rollbackMerchantOrder();
165
            $jsonResponse = new JsonExceptionResponse();
166
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
167
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
168
            $jsonResponse->setException($exception);
169
            $jsonResponse->toJson();
170
            $this->insertLog($exception);
171
        }
172
173
        $this->unblockConcurrency(true);
174
175
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
176
            $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...
177
        } else {
178
            $returnUrl = $this->getRedirectUrl();
179
            $this->_redirect($returnUrl);
180
        }
181
    }
182
183
    /**
184
     * COMMON FUNCTIONS
185
     */
186
187
    private function checkConcurrency()
188
    {
189
        $this->getQuoteId();
190
        $this->checkDbTable();
191
        $this->unblockConcurrency();
192
        $this->blockConcurrency();
193
    }
194
195
    private function getMerchantOrder()
196
    {
197
        try {
198
            $this->quote = $this->quoteRepository->get($this->quoteId);
199
        } catch (\Exception $e) {
200
            throw new MerchantOrderNotFoundException();
201
        }
202
    }
203
204
    private function getPmtOrderId()
205
    {
206
        try {
207
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
208
            $dbConnection     = $this->dbObject->getConnection();
209
            $tableName        = $this->dbObject->getTableName(self::ORDERS_TABLE);
210
            $query            = "select order_id from $tableName where id='$this->quoteId'";
211
            $queryResult      = $dbConnection->fetchRow($query);
212
            $this->pmtOrderId = $queryResult['order_id'];
213
            if ($this->pmtOrderId == '') {
214
                throw new NoIdentificationException();
215
            }
216
        } catch (\Exception $e) {
217
            throw new UnknownException($e->getMessage());
218
        }
219
    }
220
221
    private function getPmtOrder()
222
    {
223
        try {
224
            $this->orderClient = new Client($this->config['public_key'], $this->config['secret_key']);
225
            $this->pmtOrder = $this->orderClient->getOrder($this->pmtOrderId);
226
        } catch (\Exception $e) {
227
            throw new OrderNotFoundException();
228
        }
229
    }
230
231
    private function checkOrderStatus()
232
    {
233
        try {
234
            $this->checkPmtStatus(array('AUTHORIZED'));
235
        } catch (\Exception $e) {
236
            $this->getMagentoOrderId();
237
            if ($this->magentoOrderId!='') {
238
                throw new AlreadyProcessedException();
239
            } else {
240
                throw new WrongStatusException($this->pmtOrder->getStatus());
241
            }
242
        }
243
    }
244
245
    private function checkMerchantOrderStatus()
246
    {
247
        if ($this->quote->getIsActive()=='0') {
248
            $this->getMagentoOrderId();
249
            throw new AlreadyProcessedException();
250
        }
251
    }
252
253
    private function validateAmount()
254
    {
255
        $pmtAmount = $this->pmtOrder->getShoppingCart()->getTotalAmount();
256
        $merchantAmount = intval(strval(100 * $this->quote->getGrandTotal()));
257
        if ($pmtAmount != $merchantAmount) {
258
            throw new AmountMismatchException($pmtAmount, $merchantAmount);
259
        }
260
    }
261
262
    private function processMerchantOrder()
263
    {
264
        try {
265
            $this->saveOrder();
266
            $this->updateBdInfo();
267
        } catch (\Exception $e) {
268
            throw new UnknownException($e->getMessage());
269
        }
270
    }
271
272
    private function confirmPmtOrder()
273
    {
274
        try {
275
            $this->pmtOrder = $this->orderClient->confirmOrder($this->pmtOrderId);
276
        } catch (\Exception $e) {
277
            throw new UnknownException($e->getMessage());
278
        }
279
280
        $jsonResponse = new JsonSuccessResponse();
281
        $jsonResponse->setStatusCode(200);
282
        $jsonResponse->setMerchantOrderId($this->magentoOrderId);
283
        $jsonResponse->setPmtOrderId($this->pmtOrderId);
284
        $jsonResponse->setResult(self::CPO_OK_MSG);
285
        return $jsonResponse->toJson();
286
    }
287
288
    /**
289
     * UTILS FUNCTIONS
290
     */
291
292
    /** STEP 1 CC - Check concurrency */
293
    /**
294
     * @throws QuoteNotFoundException
295
     */
296
    private function getQuoteId()
297
    {
298
        $this->quoteId = $this->getRequest()->getParam('quoteId');
299
        if ($this->quoteId == '') {
300
            throw new QuoteNotFoundException();
301
        }
302
    }
303
304
    /**
305
     * @return \Zend_Db_Statement_Interface
306
     * @throws UnknownException
307
     */
308
    private function checkDbTable()
309
    {
310
        try {
311
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
312
            $dbConnection = $this->dbObject->getConnection();
313
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
314
            $query        = "CREATE TABLE IF NOT EXISTS $tableName(`id` int not null,`timestamp` int not null,PRIMARY KEY (`id`))";
315
316
            return $dbConnection->query($query);
317
        } catch (\Exception $e) {
318
            throw new UnknownException($e->getMessage());
319
        }
320
    }
321
322
    /**
323
     * @return void|\Zend_Db_Statement_Interface
324
     * @throws UnknownException
325
     */
326
    private function checkDbLogTable()
327
    {
328
        try {
329
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
330
            $dbConnection = $this->dbObject->getConnection();
331
            $tableName = $this->dbObject->getTableName(self::LOGS_TABLE);
332
            if (!$dbConnection->isTableExists($tableName)) {
333
                $table = $dbConnection
334
                    ->newTable($tableName)
335
                    ->addColumn('id', Table::TYPE_SMALLINT, null, array('nullable'=>false, 'auto_increment'=>true, 'primary'=>true))
336
                    ->addColumn('log', Table::TYPE_TEXT, null, array('nullable'=>false))
337
                    ->addColumn('createdAt', Table::TYPE_TIMESTAMP, null, array('nullable'=>false, 'default'=>Table::TIMESTAMP_INIT));
338
                return $dbConnection->createTable($table);
339
            }
340
341
            return;
342
        } catch (\Exception $e) {
343
            throw new UnknownException($e->getMessage());
344
        }
345
    }
346
347
    /**
348
     * @param bool $mode
349
     *
350
     * @throws \Exception
351
     */
352
    private function unblockConcurrency($mode = false)
353
    {
354
        try {
355
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
356
            $dbConnection = $this->dbObject->getConnection();
357
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
358
            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...
359
                $dbConnection->delete($tableName, "timestamp<".(time() - 5));
360
            } elseif ($this->quoteId!='') {
361
                $dbConnection->delete($tableName, "id=".$this->quoteId);
362
            }
363
        } 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...
364
            throw new ConcurrencyException();
365
        }
366
    }
367
368
    /**
369
     * @throws \Exception
370
     */
371
    private function blockConcurrency()
372
    {
373
        try {
374
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
375
            $dbConnection = $this->dbObject->getConnection();
376
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
377
            $dbConnection->insert($tableName, array('id'=>$this->quoteId, 'timestamp'=>time()));
378
        } catch (Exception $exception) {
379
            throw new ConcurrencyException();
380
        }
381
    }
382
383
    /** STEP 2 GMO - Get Merchant Order */
384
    /** STEP 3 GPOI - Get Pmt OrderId */
385
    /** STEP 4 GPO - Get Pmt Order */
386
    /** STEP 5 COS - Check Order Status */
387
    /**
388
     * @param $statusArray
389
     *
390
     * @throws \Exception
391
     */
392
    private function checkPmtStatus($statusArray)
393
    {
394
        $pmtStatus = array();
395
        foreach ($statusArray as $status) {
396
            $pmtStatus[] = constant("\PagaMasTarde\OrdersApiClient\Model\Order::STATUS_$status");
397
        }
398
399
        $payed = in_array($this->pmtOrder->getStatus(), $pmtStatus);
400
        if (!$payed) {
401
            throw new WrongStatusException($this->pmtOrder->getStatus());
402
        }
403
    }
404
405
    /** STEP 6 CMOS - Check Merchant Order Status */
406
    /**
407
     * @throws \Exception
408
     */
409
    private function getMagentoOrderId()
410
    {
411
        try {
412
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
413
            $dbConnection = $this->dbObject->getConnection();
414
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
415
            $pmtOrderId   = $this->pmtOrderId;
416
417
            $query        = "select mg_order_id from $tableName where id='$this->quoteId' and order_id='$pmtOrderId'";
418
            $queryResult  = $dbConnection->fetchRow($query);
419
            $this->magentoOrderId = $queryResult['mg_order_id'];
420
        } catch (\Exception $e) {
421
            throw new UnknownException($e->getMessage());
422
        }
423
    }
424
425
    /** STEP 7 VA - Validate Amount */
426
    /** STEP 8 PMO - Process Merchant Order */
427
    /**
428
     * @throws UnknownException
429
     */
430
    private function saveOrder()
431
    {
432
        try {
433
            $this->paymentInterface->setMethod(self::PAYMENT_METHOD);
434
            $this->magentoOrderId = $this->quoteManagement->placeOrder($this->quoteId, $this->paymentInterface);
435
            /** @var \Magento\Sales\Api\Data\OrderInterface magentoOrder */
436
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
437
438
            if ($this->magentoOrderId == '') {
439
                throw new UnknownException('Order can not be saved');
440
            }
441
        } catch (\Exception $e) {
442
            throw new UnknownException($e->getMessage());
443
        }
444
    }
445
446
    /**
447
     * @throws UnknownException
448
     */
449
    private function updateBdInfo()
450
    {
451
        try {
452
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
453
            $dbConnection = $this->dbObject->getConnection();
454
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
455
            $pmtOrderId   = $this->pmtOrder->getId();
456
            $dbConnection->update(
457
                $tableName,
458
                array('mg_order_id' => $this->magentoOrderId),
459
                "order_id='$pmtOrderId' and id='$this->quoteId'"
460
            );
461
        } catch (\Exception $e) {
462
            throw new UnknownException($e->getMessage());
463
        }
464
    }
465
466
    /** STEP 9 CPO - Confirmation Pmt Order */
467
    /**
468
     * @throws UnknownException
469
     */
470
    private function rollbackMerchantOrder()
471
    {
472
        try {
473
            $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

473
            $this->magentoOrder->/** @scrutinizer ignore-call */ 
474
                                 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...
474
            $this->magentoOrder->setStatus(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT);
475
            $this->magentoOrder->save();
476
        } catch (\Exception $e) {
477
            throw new UnknownException($e->getMessage());
478
        }
479
    }
480
481
    /**
482
     * @return string
483
     */
484
    private function getRedirectUrl()
485
    {
486
        $returnUrl = 'checkout/#payment';
487
        if ($this->magentoOrderId!='') {
488
            /** @var Order $this->magentoOrder */
489
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
490
            if (!$this->_objectManager->get(\Magento\Checkout\Model\Session\SuccessValidator::class)->isValid()) {
491
                $this->checkoutSession
492
                    ->setLastOrderId($this->magentoOrderId)
493
                    ->setLastRealOrderId($this->magentoOrder->getIncrementId())
494
                    ->setLastQuoteId($this->quoteId)
495
                    ->setLastSuccessQuoteId($this->quoteId)
496
                    ->setLastOrderStatus($this->magentoOrder->getStatus());
497
            }
498
499
            //Magento status flow => https://docs.magento.com/m2/ce/user_guide/sales/order-status-workflow.html
500
            //Order Workflow => https://docs.magento.com/m2/ce/user_guide/sales/order-workflow.html
501
            $orderStatus    = strtolower($this->magentoOrder->getStatus());
502
            $acceptedStatus = array('processing', 'completed');
503
            if (in_array($orderStatus, $acceptedStatus)) {
504
                if ($this->config['ok_url'] != '') {
505
                    $returnUrl = $this->config['ok_url'];
506
                } else {
507
                    $returnUrl = 'checkout/onepage/success';
508
                }
509
            } else {
510
                if ($this->config['ko_url'] != '') {
511
                    $returnUrl = $this->config['ko_url'];
512
                } else {
513
                    $returnUrl = 'checkout/#payment';
514
                }
515
            }
516
        }
517
        return $returnUrl;
518
    }
519
520
    /**
521
     * @param $exceptionMessage
522
     *
523
     * @throws UnknownException
524
     */
525
    private function insertLog($exceptionMessage)
526
    {
527
        try {
528
            if ($exceptionMessage instanceof \Exception) {
529
                $this->checkDbLogTable();
530
                $logEntry = new LogEntry();
531
                $logEntryJson = $logEntry->error($exceptionMessage)->toJson();
532
533
                /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
534
                $dbConnection = $this->dbObject->getConnection();
535
                $tableName    = $this->dbObject->getTableName(self::LOGS_TABLE);
536
                $dbConnection->insert($tableName, array('log' => $logEntryJson));
537
            }
538
        } catch (\Exception $e) {
539
            throw new UnknownException($e->getMessage());
540
        }
541
    }
542
}
543