Completed
Push — master ( 1137b9...97c77e )
by
unknown
20s queued 11s
created

Controller/Notify/Index.php (2 issues)

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
19
/**
20
 * Class Index
21
 * @package DigitalOrigin\Pmt\Controller\Notify
22
 */
23
class Index extends Action
24
{
25
    /** Orders tablename */
26
    const ORDERS_TABLE = 'cart_process';
27
28
    /** Concurrency tablename */
29
    const CONCURRENCY_TABLE = 'pmt_orders';
30
31
    /** Concurrency tablename */
32
    const LOGS_TABLE = 'pmt_logs';
33
34
    /** Payment code */
35
    const PAYMENT_METHOD = 'paylater';
36
37
    /**
38
     * EXCEPTION RESPONSES
39
     */
40
    const CC_ERR_MSG = 'Unable to block resource';
41
    const CC_NO_QUOTE = 'QuoteId not found';
42
    const CC_NO_VALIDATE ='Validation in progress, try again later';
43
    const GMO_ERR_MSG = 'Merchant Order Not Found';
44
    const GPOI_ERR_MSG = 'Pmt Order Not Found';
45
    const GPOI_NO_ORDERID = 'We can not get the PagaMasTarde identification in database.';
46
    const GPO_ERR_MSG = 'Unable to get Order';
47
    const COS_ERR_MSG = 'Order status is not authorized';
48
    const COS_WRONG_STATUS = 'Invalid Pmt status';
49
    const CMOS_ERR_MSG = 'Merchant Order status is invalid';
50
    const CMOS_ALREADY_PROCESSED = 'Cart already processed.';
51
    const VA_ERR_MSG = 'Amount conciliation error';
52
    const VA_WRONG_AMOUNT = 'Wrong order amount';
53
    const PMO_ERR_MSG = 'Unknown Error';
54
    const CPO_ERR_MSG = 'Order not confirmed';
55
    const CPO_OK_MSG = 'Order confirmed';
56
57
    /** @var QuoteManagement */
58
    protected $quoteManagement;
59
60
    /** @var PaymentInterface $paymentInterface */
61
    protected $paymentInterface;
62
63
    /** @var OrderRepositoryInterface $orderRepositoryInterface */
64
    protected $orderRepositoryInterface;
65
66
    /** @var Quote $quote */
67
    protected $quote;
68
69
    /** @var QuoteRepository $quoteRepository */
70
    protected $quoteRepository;
71
72
    /** @var mixed $config */
73
    protected $config;
74
75
    /** @var mixed $quoteId */
76
    protected $quoteId;
77
78
    /** @var Array_ $notifyResult */
79
    protected $notifyResult;
80
81
    /** @var mixed $magentoOrderId */
82
    protected $magentoOrderId;
83
84
    /** @var mixed $pmtOrder */
85
    protected $pmtOrder;
86
87
    /** @var ResourceConnection $dbObject */
88
    protected $dbObject;
89
90
    /** @var Session $checkoutSession */
91
    protected $checkoutSession;
92
93
    /** @var Client $orderClient */
94
    protected $orderClient;
95
96
    /** @var mixed $pmtOrderId */
97
    protected $pmtOrderId;
98
99
    /** @var  OrderInterface $magentoOrder */
100
    protected $magentoOrder;
101
102
    /**
103
     * Index constructor.
104
     *
105
     * @param Context                  $context
106
     * @param Quote                    $quote
107
     * @param QuoteManagement          $quoteManagement
108
     * @param PaymentInterface         $paymentInterface
109
     * @param Config                   $config
110
     * @param QuoteRepository          $quoteRepository
111
     * @param OrderRepositoryInterface $orderRepositoryInterface
112
     * @param ResourceConnection       $dbObject
113
     * @param Session                  $checkoutSession
114
     */
115
    public function __construct(
116
        Context $context,
117
        Quote $quote,
118
        QuoteManagement $quoteManagement,
119
        PaymentInterface $paymentInterface,
120
        Config $config,
121
        QuoteRepository $quoteRepository,
122
        OrderRepositoryInterface $orderRepositoryInterface,
123
        ResourceConnection $dbObject,
124
        Session $checkoutSession
125
    ) {
126
        parent::__construct($context);
127
        $this->quote = $quote;
128
        $this->quoteManagement = $quoteManagement;
129
        $this->paymentInterface = $paymentInterface;
130
        $this->config = $config->getConfig();
131
        $this->quoteRepository = $quoteRepository;
132
        $this->orderRepositoryInterface = $orderRepositoryInterface;
133
        $this->dbObject = $dbObject;
134
        $this->checkoutSession = $checkoutSession;
135
    }
136
137
    //MAIN FUNCTION
138
    public function execute()
139
    {
140
        try {
141
            $this->checkConcurrency();
142
            $this->getMerchantOrder();
143
            $this->getPmtOrderId();
144
            $this->getPmtOrder();
145
            $this->checkOrderStatus();
146
            $this->checkMerchantOrderStatus();
147
            $this->validateAmount();
148
            $this->processMerchantOrder();
149
        } catch (\Exception $exception) {
150
            $this->insertLog($exception);
151
            $exception = unserialize($exception->getMessage());
152
            $status = $exception->status;
153
            $response = array();
154
            $response['timestamp'] = time();
155
            $response['order_id']= $this->magentoOrderId;
156
            $response['result'] = $exception->result;
157
            $response['result_description'] = $exception->result_description;
158
            $response = json_encode($response);
159
        }
160
161
        try {
162
            if (!isset($response)) {
163
                $response = $this->confirmPmtOrder();
164
            }
165
        } catch (\Exception $exception) {
166
            $this->insertLog($exception);
167
            $this->rollbackMerchantOrder();
168
            $exception = unserialize($exception->getMessage());
169
            $status = $exception->status;
170
            $response = array();
171
            $response['timestamp'] = time();
172
            $response['order_id']= $this->magentoOrderId;
173
            $response['result'] = self::CPO_ERR_MSG;
174
            $response['result_description'] = $exception->result_description;
175
            $response = json_encode($response);
176
        }
177
178
        $this->unblockConcurrency(true);
179
180
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
181
            header("HTTP/1.1 $status", true, $status);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $status does not seem to be defined for all execution paths leading up to this point.
Loading history...
182
            header('Content-Type: application/json', true);
183
            header('Content-Length: ' . strlen($response));
184
            echo ($response);
185
            exit();
186
        } else {
187
            $returnUrl = $this->getRedirectUrl();
188
            $this->_redirect($returnUrl);
189
        }
190
    }
191
192
    /**
193
     * COMMON FUNCTIONS
194
     */
195
196
    private function checkConcurrency()
197
    {
198
        try {
199
            $this->getQuoteId();
200
            $this->checkDbTable();
201
            $this->unblockConcurrency();
202
            $this->blockConcurrency();
203
        } catch (\Exception $e) {
204
            $exceptionObject = new \stdClass();
205
            $exceptionObject->method= __FUNCTION__;
206
            $exceptionObject->status='429';
207
            $exceptionObject->result= self::CC_ERR_MSG;
208
            $exceptionObject->result_description = $e->getMessage();
209
            throw new \Exception(serialize($exceptionObject));
210
        }
211
    }
212
213
    private function getMerchantOrder()
214
    {
215
        try {
216
            $this->quote = $this->quoteRepository->get($this->quoteId);
217
        } catch (\Exception $e) {
218
            $exceptionObject = new \stdClass();
219
            $exceptionObject->method= __FUNCTION__;
220
            $exceptionObject->status='404';
221
            $exceptionObject->result= self::GMO_ERR_MSG;
222
            $exceptionObject->result_description = $e->getMessage();
223
            throw new \Exception(serialize($exceptionObject));
224
        }
225
    }
226
227
    private function getPmtOrderId()
228
    {
229
        try {
230
            $this->getPmtOrderIdDb();
231
            $this->getMagentoOrderId();
232
        } catch (\Exception $e) {
233
            $exceptionObject = new \stdClass();
234
            $exceptionObject->method= __FUNCTION__;
235
            $exceptionObject->status='404';
236
            $exceptionObject->result= self::GPOI_ERR_MSG;
237
            $exceptionObject->result_description = $e->getMessage();
238
            throw new \Exception(serialize($exceptionObject));
239
        }
240
    }
241
242
    private function getPmtOrder()
243
    {
244
        try {
245
            $this->orderClient = new Client($this->config['public_key'], $this->config['secret_key']);
246
            $this->pmtOrder = $this->orderClient->getOrder($this->pmtOrderId);
247
        } catch (\Exception $e) {
248
            $exceptionObject = new \stdClass();
249
            $exceptionObject->method= __FUNCTION__;
250
            $exceptionObject->status='400';
251
            $exceptionObject->result= self::GPO_ERR_MSG;
252
            $exceptionObject->result_description = $e->getMessage();
253
            throw new \Exception(serialize($exceptionObject));
254
        }
255
    }
256
257
    private function checkOrderStatus()
258
    {
259
        try {
260
            $this->checkPmtStatus(array('AUTHORIZED'));
261
        } catch (\Exception $e) {
262
            $exceptionObject = new \stdClass();
263
            $exceptionObject->method= __FUNCTION__;
264
            $exceptionObject->status='403';
265
            $exceptionObject->result= self::COS_ERR_MSG;
266
            $exceptionObject->result_description = $e->getMessage();
267
            throw new \Exception(serialize($exceptionObject));
268
        }
269
    }
270
271
    private function checkMerchantOrderStatus()
272
    {
273
        try {
274
            $this->checkCartStatus();
275
        } catch (\Exception $e) {
276
            $exceptionObject = new \stdClass();
277
            $exceptionObject->method= __FUNCTION__;
278
            $exceptionObject->status='409';
279
            $exceptionObject->result= self::CMOS_ERR_MSG;
280
            $exceptionObject->result_description = $e->getMessage();
281
            throw new \Exception(serialize($exceptionObject));
282
        }
283
    }
284
285
    private function validateAmount()
286
    {
287
        try {
288
            $this->comparePrices();
289
        } catch (\Exception $e) {
290
            $exceptionObject = new \stdClass();
291
            $exceptionObject->method= __FUNCTION__;
292
            $exceptionObject->status='409';
293
            $exceptionObject->result= self::VA_ERR_MSG;
294
            $exceptionObject->result_description = $e->getMessage();
295
            throw new \Exception(serialize($exceptionObject));
296
        }
297
    }
298
299
    private function processMerchantOrder()
300
    {
301
        try {
302
            $this->saveOrder();
303
            $this->updateBdInfo();
304
        } catch (\Exception $e) {
305
            $exceptionObject = new \stdClass();
306
            $exceptionObject->method= __FUNCTION__;
307
            $exceptionObject->status='500';
308
            $exceptionObject->result= self::PMO_ERR_MSG;
309
            $exceptionObject->result_description = $e->getMessage();
310
            throw new \Exception(serialize($exceptionObject));
311
        }
312
    }
313
314
    private function confirmPmtOrder()
315
    {
316
        try {
317
            $this->pmtOrder = $this->orderClient->confirmOrder($this->pmtOrderId);
318
        } catch (\Exception $e) {
319
            $exceptionObject = new \stdClass();
320
            $exceptionObject->method= __FUNCTION__;
321
            $exceptionObject->status='500';
322
            $exceptionObject->result= self::CPO_ERR_MSG;
323
            $exceptionObject->result_description = $e->getMessage();
324
            throw new \Exception(serialize($exceptionObject));
325
        }
326
327
        $response = array();
328
        $response['status'] = '200';
329
        $response['timestamp'] = time();
330
        $response['order_id']= $this->magentoOrderId;
331
        $response['result'] = self::CPO_OK_MSG;
332
        $response = json_encode($response);
333
        return $response;
334
    }
335
336
    /**
337
     * UTILS FUNCTIONS
338
     */
339
340
    /** STEP 1 CC - Check concurrency */
341
    /**
342
     * @throws \Exception
343
     */
344
    private function getQuoteId()
345
    {
346
        $this->quoteId = $this->getRequest()->getParam('quoteId');
347
        if ($this->quoteId == '') {
348
            throw new \Exception(self::CC_NO_QUOTE);
349
        }
350
    }
351
352
    /**
353
     * @return \Zend_Db_Statement_Interface
354
     */
355
    private function checkDbTable()
356
    {
357
        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
358
        $dbConnection = $this->dbObject->getConnection();
359
        $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
360
        $query = "CREATE TABLE IF NOT EXISTS $tableName(`id` int not null,`timestamp` int not null,PRIMARY KEY (`id`))";
361
        return $dbConnection->query($query);
362
    }
363
364
    /**
365
     * @return void|\Zend_Db_Statement_Interface
366
     * @throws \Zend_Db_Exception
367
     */
368
    private function checkDbLogTable()
369
    {
370
        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
371
        $dbConnection = $this->dbObject->getConnection();
372
        $tableName = $this->dbObject->getTableName(self::LOGS_TABLE);
373
        if (!$dbConnection->isTableExists($tableName)) {
374
            $table = $dbConnection
375
                ->newTable($tableName)
376
                ->addColumn('id', Table::TYPE_SMALLINT, null, array('nullable'=>false, 'auto_increment'=>true, 'primary'=>true))
377
                ->addColumn('log', Table::TYPE_TEXT, null, array('nullable'=>false))
378
                ->addColumn('createdAt', Table::TYPE_TIMESTAMP, null, array('nullable'=>false, 'default'=>Table::TIMESTAMP_INIT));
379
            return $dbConnection->createTable($table);
380
        }
381
382
        return;
383
    }
384
385
    /**
386
     * @param bool $mode
387
     *
388
     * @throws \Exception
389
     */
390
    private function unblockConcurrency($mode = false)
391
    {
392
        try {
393
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
394
            $dbConnection = $this->dbObject->getConnection();
395
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
396
            if ($mode == false) {
397
                $dbConnection->delete($tableName, "timestamp<".(time() - 5));
398
            } elseif ($this->quoteId!='') {
399
                $dbConnection->delete($tableName, "id=".$this->quoteId);
400
            }
401
        } catch (Exception $exception) {
402
            throw new \Exception($exception->getMessage());
403
        }
404
    }
405
406
    /**
407
     * @throws \Exception
408
     */
409
    private function blockConcurrency()
410
    {
411
        try {
412
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
413
            $dbConnection = $this->dbObject->getConnection();
414
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
415
            $dbConnection->insert($tableName, array('id'=>$this->quoteId, 'timestamp'=>time()));
416
        } catch (Exception $exception) {
417
            throw new \Exception(self::CC_NO_VALIDATE);
418
        }
419
    }
420
421
    /** STEP 2 GMO - Get Merchant Order */
422
    /** STEP 3 GPOI - Get Pmt OrderId */
423
    /**
424
     * @throws \Exception
425
     */
426
    private function getPmtOrderIdDb()
427
    {
428
        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
429
        $dbConnection = $this->dbObject->getConnection();
430
        $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
431
        $query        = "select order_id from $tableName where id='$this->quoteId'";
432
        $queryResult  = $dbConnection->fetchRow($query);
433
        $this->pmtOrderId = $queryResult['order_id'];
434
        if ($this->pmtOrderId == '') {
435
            throw new \Exception(self::GPOI_NO_ORDERID);
436
        }
437
    }
438
439
    /** STEP 4 GPO - Get Pmt Order */
440
    /** STEP 5 COS - Check Order Status */
441
    /**
442
     * @param $statusArray
443
     *
444
     * @throws \Exception
445
     */
446
    private function checkPmtStatus($statusArray)
447
    {
448
        $pmtStatus = array();
449
        foreach ($statusArray as $status) {
450
            $pmtStatus[] = constant("\PagaMasTarde\OrdersApiClient\Model\Order::STATUS_$status");
451
        }
452
453
        $payed = in_array($this->pmtOrder->getStatus(), $pmtStatus);
454
        if (!$payed) {
455
            throw new \Exception(self::CMOS_ERR_MSG."=>".$this->pmtOrder->getStatus());
456
        }
457
    }
458
459
    /** STEP 6 CMOS - Check Merchant Order Status */
460
    /**
461
     * @throws \Exception
462
     */
463
    private function checkCartStatus()
464
    {
465
        if ($this->quote->getIsActive()=='0') {
466
            $this->getMagentoOrderId();
467
            throw new \Exception(self::CMOS_ALREADY_PROCESSED);
468
        }
469
    }
470
471
    private function getMagentoOrderId()
472
    {
473
        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
474
        $dbConnection = $this->dbObject->getConnection();
475
        $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
476
        $pmtOrderId   = $this->pmtOrderId;
477
478
        $query        = "select mg_order_id from $tableName where id='$this->quoteId' and order_id='$pmtOrderId'";
479
        $queryResult  = $dbConnection->fetchRow($query);
480
        $this->magentoOrderId = $queryResult['mg_order_id'];
481
    }
482
483
    /** STEP 7 VA - Validate Amount */
484
    /**
485
     * @throws \Exception
486
     */
487
    private function comparePrices()
488
    {
489
        $grandTotal = $this->quote->getGrandTotal();
490
        if ($this->pmtOrder->getShoppingCart()->getTotalAmount() != intval(strval(100 * $grandTotal))) {
491
            throw new \Exception(self::VA_ERR_MSG);
492
        }
493
    }
494
495
    /** STEP 8 PMO - Process Merchant Order */
496
    /**
497
     * @throws \Magento\Framework\Exception\CouldNotSaveException
498
     */
499
    private function saveOrder()
500
    {
501
        $this->paymentInterface->setMethod(self::PAYMENT_METHOD);
502
        $this->magentoOrderId = $this->quoteManagement->placeOrder($this->quoteId, $this->paymentInterface);
503
        /** @var \Magento\Sales\Api\Data\OrderInterface magentoOrder */
504
        $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
505
506
        if ($this->magentoOrderId == '') {
507
            throw new \Exception(self::PMO_ERR_MSG);
508
        }
509
    }
510
511
    private function updateBdInfo()
512
    {
513
        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
514
        $dbConnection = $this->dbObject->getConnection();
515
        $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
516
        $pmtOrderId   = $this->pmtOrder->getId();
517
        $dbConnection->update(
518
            $tableName,
519
            array('mg_order_id'=>$this->magentoOrderId),
520
            "order_id='$pmtOrderId' and id='$this->quoteId'"
521
        );
522
    }
523
524
    /** STEP 9 CPO - Confirmation Pmt Order */
525
    private function rollbackMerchantOrder()
526
    {
527
        $this->magentoOrder->setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT, true);
0 ignored issues
show
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

527
        $this->magentoOrder->/** @scrutinizer ignore-call */ 
528
                             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...
528
        $this->magentoOrder->setStatus(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT);
529
        $this->magentoOrder->save();
530
    }
531
532
    /**
533
     * @return string
534
     */
535
    private function getRedirectUrl()
536
    {
537
        $returnUrl = 'checkout/#payment';
538
        if ($this->magentoOrderId!='') {
539
            /** @var Order $this->magentoOrder */
540
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
541
            if (!$this->_objectManager->get(\Magento\Checkout\Model\Session\SuccessValidator::class)->isValid()) {
542
                $this->checkoutSession
543
                    ->setLastOrderId($this->magentoOrderId)
544
                    ->setLastRealOrderId($this->magentoOrder->getIncrementId())
545
                    ->setLastQuoteId($this->quoteId)
546
                    ->setLastSuccessQuoteId($this->quoteId)
547
                    ->setLastOrderStatus($this->magentoOrder->getStatus());
548
            }
549
550
            //Magento status flow => https://docs.magento.com/m2/ce/user_guide/sales/order-status-workflow.html
551
            //Order Workflow => https://docs.magento.com/m2/ce/user_guide/sales/order-workflow.html
552
            $orderStatus    = strtolower($this->magentoOrder->getStatus());
553
            $acceptedStatus = array('processing', 'completed');
554
            if (in_array($orderStatus, $acceptedStatus)) {
555
                if ($this->config['ok_url'] != '') {
556
                    $returnUrl = $this->config['ok_url'];
557
                } else {
558
                    $returnUrl = 'checkout/onepage/success';
559
                }
560
            } else {
561
                if ($this->config['ko_url'] != '') {
562
                    $returnUrl = $this->config['ko_url'];
563
                } else {
564
                    $returnUrl = 'checkout/#payment';
565
                }
566
            }
567
        }
568
        return $returnUrl;
569
    }
570
571
    /**
572
     * @param $exceptionMessage
573
     *
574
     * @throws \Zend_Db_Exception
575
     */
576
    private function insertLog($exceptionMessage)
577
    {
578
        if ($exceptionMessage instanceof \Exception) {
579
            $this->checkDbLogTable();
580
            $logObject          = new \stdClass();
581
            $logObject->message = $exceptionMessage->getMessage();
582
            $logObject->code    = $exceptionMessage->getCode();
583
            $logObject->line    = $exceptionMessage->getLine();
584
            $logObject->file    = $exceptionMessage->getFile();
585
            $logObject->trace   = $exceptionMessage->getTraceAsString();
586
587
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
588
            $dbConnection = $this->dbObject->getConnection();
589
            $tableName    = $this->dbObject->getTableName(self::LOGS_TABLE);
590
            $dbConnection->insert($tableName, array('log' => json_encode($logObject)));
591
        }
592
    }
593
}
594