PagantisNotifyModuleFrontController::getOrigin()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * This file is part of the official Pagantis module for PrestaShop.
4
 *
5
 * @author    Pagantis <[email protected]>
6
 * @copyright 2019 Pagantis
7
 * @license   proprietary
8
 */
9
10
require_once('AbstractController.php');
11
12
use Pagantis\OrdersApiClient\Client as PagantisClient;
13
use Pagantis\OrdersApiClient\Model\Order as PagantisModelOrder;
14
use Pagantis\ModuleUtils\Exception\AmountMismatchException;
15
use Pagantis\ModuleUtils\Exception\ConcurrencyException;
16
use Pagantis\ModuleUtils\Exception\MerchantOrderNotFoundException;
17
use Pagantis\ModuleUtils\Exception\NoIdentificationException;
18
use Pagantis\ModuleUtils\Exception\OrderNotFoundException;
19
use Pagantis\ModuleUtils\Exception\QuoteNotFoundException;
20
use Pagantis\ModuleUtils\Exception\UnknownException;
21
use Pagantis\ModuleUtils\Exception\WrongStatusException;
22
use Pagantis\ModuleUtils\Model\Response\JsonSuccessResponse;
23
use Pagantis\ModuleUtils\Model\Response\JsonExceptionResponse;
24
25
/**
26
 * Class PagantisNotifyModuleFrontController
27
 */
28
class PagantisNotifyModuleFrontController extends AbstractController
29
{
30
    /** Cart tablename */
31
    const CART_TABLE = 'pagantis_cart_process';
32
33
    /** Pagantis orders tablename */
34
    const ORDERS_TABLE = 'pagantis_order';
35
36
    /**
37
     * Seconds to expire a locked request
38
     */
39
    const CONCURRENCY_TIMEOUT = 10;
40
41
    /**
42
     * mismatch amount threshold in cents
43
     */
44
    const MISMATCH_AMOUNT_THRESHOLD = 5;
45
46
    /**
47
     * @var string $token
48
     */
49
    protected $token;
50
51
    /**
52
     * @var string $productName
53
     */
54
    protected $productName;
55
56
    /**
57
     * @var int $requestId
58
     */
59
    protected $requestId = null;
60
61
    /**
62
     * @var int $merchantOrderId
63
     */
64
    protected $merchantOrderId = null;
65
66
    /**
67
     * @var \Order $merchantOrder
0 ignored issues
show
Bug introduced by
The type Order was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
68
     */
69
    protected $merchantOrder;
70
71
    /**
72
     * @var int $merchantCartId
73
     */
74
    protected $merchantCartId;
75
76
    /**
77
     * @var \Cart $merchantCart
0 ignored issues
show
Bug introduced by
The type Cart was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
78
     */
79
    protected $merchantCart;
80
81
    /**
82
     * @var string $pagantisOrderId
83
     */
84
    protected $pagantisOrderId;
85
86
    /**
87
     * @var string $amountMismatchError
88
     */
89
    protected $amountMismatchError = '';
90
91
    /**
92
     * @var \Pagantis\OrdersApiClient\Model\Order $pagantisOrder
93
     */
94
    protected $pagantisOrder;
95
96
    /**
97
     * @var Pagantis\OrdersApiClient\Client $orderClient
98
     */
99
    protected $orderClient;
100
101
    /**
102
     * @var mixed $config
103
     */
104
    protected $config;
105
106
    /**
107
     * @var Object $jsonResponse
108
     */
109
    protected $jsonResponse;
110
111
    /** @var mixed $origin */
112
    protected $origin;
113
114
    /**
115
     * @throws Exception
116
     */
117
    public function postProcess()
118
    {
119
        $thrownException = false;
120
        $this->origin = ($this->isPost() || Tools::getValue('origin') === 'notification') ? 'Notification' : 'Order';
0 ignored issues
show
Bug introduced by
The type Tools was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
121
        $this->requestId = rand(1, 999999999);
122
123
        // Validations
124
        try {
125
            //Avoiding notifications via GET
126
            if ($this->isGet() && $this->isNotification()) {
127
                echo 'OK';
128
                die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
129
            }
130
131
            $redirectMessage = sprintf(
132
                "Request [origin=%s][cartId=%s]",
133
                $this->getOrigin(),
134
                Tools::getValue('id_cart')
135
            );
136
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $redirectMessage));
137
138
            $this->prepareVariables();
139
            $this->checkConcurrency();
140
            $this->getMerchantOrder();
141
            $this->getPagantisOrderId();
142
            $this->getPagantisOrder();
143
            if ($this->checkOrderStatus()) {
144
                $thrownException = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $thrownException is dead and can be removed.
Loading history...
145
                return $this->finishProcess(false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(false) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
146
            }
147
            $this->validateAmount();
148
            $this->checkMerchantOrderStatus();
149
        } catch (\Exception $exception) {
150
            $thrownException = true;
151
            $this->getMerchantOrderId();
152
            $theId = ($this->merchantOrderId)? $this->merchantOrderId : $this->merchantCartId;
153
            if ($this->isPost()) {
154
                $this->jsonResponse = new JsonExceptionResponse();
155
                $this->jsonResponse->setMerchantOrderId($theId);
156
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
157
                $this->jsonResponse->setException($exception);
158
            }
159
            return $this->cancelProcess($exception);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($exception) targeting PagantisNotifyModuleFron...roller::cancelProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
160
        }
161
162
        // Proccess Pagantis Order
163
        try {
164
            if (!$thrownException) {
0 ignored issues
show
introduced by
The condition $thrownException is always false.
Loading history...
165
                $this->jsonResponse = new JsonSuccessResponse();
166
                $this->getMerchantOrderId();
167
                $theId = ($this->merchantOrderId)? $this->merchantOrderId : $this->merchantCartId;
168
                $this->jsonResponse->setMerchantOrderId($theId);
169
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
170
                $this->confirmPagantisOrder();
171
            }
172
        } catch (\Exception $exception) {
173
            $this->rollbackMerchantOrder();
174
            if ($this->isNotification()) {
175
                $this->getMerchantOrderId();
176
                $theId = ($this->merchantOrderId)? $this->merchantOrderId : $this->merchantCartId;
177
                $this->jsonResponse = new JsonExceptionResponse();
178
                $this->jsonResponse->setMerchantOrderId($theId);
179
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
180
                $this->jsonResponse->setException($exception);
181
            }
182
            return $this->cancelProcess($exception);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($exception) targeting PagantisNotifyModuleFron...roller::cancelProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
183
        }
184
185
        // Process Merchant Order
186
        try {
187
            if (!$thrownException) {
0 ignored issues
show
introduced by
The condition $thrownException is always false.
Loading history...
188
                $this->processMerchantOrder();
189
            }
190
        } catch (\Exception $exception) {
191
            $thrownException = true;
192
            $this->getMerchantOrderId();
193
            $theId = ($this->merchantOrderId)? $this->merchantOrderId : $this->merchantCartId;
194
            if ($this->isPost()) {
195
                $this->jsonResponse = new JsonExceptionResponse();
196
                $this->jsonResponse->setMerchantOrderId($theId);
197
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
198
                $this->jsonResponse->setException($exception);
199
            }
200
            return $this->cancelProcess($exception);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($exception) targeting PagantisNotifyModuleFron...roller::cancelProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
201
        }
202
203
        try {
204
            $this->unblockConcurrency($this->merchantCartId);
205
        } catch (\Exception $exception) {
206
            $exceptionMessage = sprintf(
207
                "unblocking exception[origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s][%s]",
208
                $this->getOrigin(),
209
                $this->merchantCartId,
210
                $this->merchantOrderId,
211
                $this->pagantisOrderId,
212
                $exception->getMessage()
213
            );
214
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $exceptionMessage));
215
        }
216
217
        return $this->finishProcess(false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(false) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
218
    }
219
220
    /**
221
     * Check the concurrency of the purchase
222
     *
223
     * @throws Exception
224
     */
225
    public function checkConcurrency()
226
    {
227
        $this->unblockConcurrency();
228
        $this->blockConcurrency($this->merchantCartId);
229
    }
230
231
    /**
232
     * Find and init variables needed to process payment
233
     *
234
     * @throws Exception
235
     */
236
    public function prepareVariables()
237
    {
238
        $this->getMerchantOrderId();
239
        if (!empty($this->merchantOrderId)) {
240
            $exceptionMessage = sprintf(
241
                "The order %s already exists in %s table",
242
                $this->merchantOrderId,
243
                self::ORDERS_TABLE
244
            );
245
            throw new UnknownException($exceptionMessage);
246
        }
247
        $callbackOkUrl = $this->context->link->getPageLink('order-confirmation', null, null);
248
        $callbackKoUrl = $this->context->link->getPageLink('order', null, null, array('step'=>3));
249
250
        $this->config = array(
251
            'urlOK' => (Pagantis::getExtraConfig('URL_OK') !== '') ?
252
                Pagantis::getExtraConfig('URL_OK') : $callbackOkUrl,
253
            'urlKO' => (Pagantis::getExtraConfig('URL_KO') !== '') ?
254
                Pagantis::getExtraConfig('URL_KO') : $callbackKoUrl,
255
            'secureKey' => Tools::getValue('key'),
256
        );
257
        $productCode = Tools::getValue('product');
258
        $this->token = Tools::getValue('token');
259
        $products = explode(',', Pagantis::getExtraConfig('PRODUCTS', null));
260
        if (!in_array(Tools::strtoupper($productCode), $products)) {
261
            throw new UnknownException(
262
                'No valid Pagantis product provided in the url: ' . Tools::getValue('product')
263
            );
264
        }
265
        $this->productName = "Pagantis " . Tools::strtolower($productCode);
266
267
        $this->config['publicKey'] = trim(Configuration::get(Tools::strtolower($productCode) . '_public_key'));
0 ignored issues
show
Bug introduced by
The type Configuration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
268
        $this->config['privateKey'] = trim(Configuration::get(Tools::strtolower($productCode) . '_private_key'));
269
270
        $this->merchantCartId = Tools::getValue('id_cart');
271
272
        if ($this->merchantCartId == '') {
273
            throw new QuoteNotFoundException();
274
        }
275
276
        if (!($this->config['secureKey'] && Module::isEnabled(self::CODE))) {
0 ignored issues
show
Bug introduced by
The type Module was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
277
            // This exception is only for Prestashop
278
            throw new UnknownException('Module may not be enabled');
279
        }
280
    }
281
282
    /**
283
     * Find prestashop Order Id
284
     */
285
    public function getMerchantOrderId()
286
    {
287
        try {
288
            $sql = 'select ps_order_id from ' . _DB_PREFIX_ .self::ORDERS_TABLE .' where id = '
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
289
                .(int)$this->merchantCartId . ' and token = \'' . $this->token . '\'';
290
            $this->merchantOrderId = Db::getInstance()->getValue($sql);
0 ignored issues
show
Bug introduced by
The type Db was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
291
        } catch (\Exception $exception) {
292
            $exceptionMessage = sprintf(
293
                "getMerchantOrderId exception[origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s][%s]",
294
                $this->getOrigin(),
295
                $this->merchantCartId,
296
                $this->merchantOrderId,
297
                $this->pagantisOrderId,
298
                $exception->getMessage()
299
            );
300
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $exceptionMessage));
301
        }
302
    }
303
304
    /**
305
     * Retrieve the merchant order by id
306
     *
307
     * @throws Exception
308
     */
309
    public function getMerchantOrder()
310
    {
311
        try {
312
            $this->merchantCart = new Cart($this->merchantCartId);
313
            if (!Validate::isLoadedObject($this->merchantCart)) {
0 ignored issues
show
Bug introduced by
The type Validate was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
314
                // This exception is only for Prestashop
315
                throw new UnknownException('Unable to load cart');
316
            }
317
            if ($this->merchantCart->secure_key != $this->config['secureKey']) {
318
                throw new UnknownException('Secure Key is not valid');
319
            }
320
        } catch (\Exception $exception) {
321
            throw new MerchantOrderNotFoundException();
322
        }
323
    }
324
325
    /**
326
     * Find PAGANTIS Order Id
327
     *
328
     * @throws Exception
329
     */
330
    private function getPagantisOrderId()
331
    {
332
        try {
333
            $sql = 'select order_id from ' . _DB_PREFIX_.self::ORDERS_TABLE .' where id = '
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
334
                .(int)$this->merchantCartId . ' and token = \'' . $this->token . '\'';
335
            $this->pagantisOrderId= Db::getInstance()->getValue($sql);
336
337
            if (is_null($this->pagantisOrderId)) {
338
                throw new NoIdentificationException();
339
            }
340
        } catch (\Exception $exception) {
341
            throw new NoIdentificationException();
342
        }
343
    }
344
345
    /**
346
     * Find PAGANTIS Order in Orders Server using Pagantis\OrdersApiClient
347
     *
348
     * @throws Exception
349
     */
350
    private function getPagantisOrder()
351
    {
352
        $this->orderClient = new PagantisClient($this->config['publicKey'], $this->config['privateKey']);
353
        $this->pagantisOrder = $this->orderClient->getOrder($this->pagantisOrderId);
354
        if (!($this->pagantisOrder instanceof PagantisModelOrder)) {
0 ignored issues
show
introduced by
$this->pagantisOrder is always a sub-type of Pagantis\OrdersApiClient\Model\Order.
Loading history...
355
            throw new OrderNotFoundException();
356
        }
357
    }
358
359
    /**
360
     * Compare statuses of merchant order and PAGANTIS order, witch have to be the same.
361
     *
362
     * @throws Exception
363
     */
364
    public function checkOrderStatus()
365
    {
366
        if ($this->pagantisOrder->getStatus() === PagantisModelOrder::STATUS_CONFIRMED) {
367
            $this->getMerchantOrderId();
368
            $theId = ($this->merchantOrderId)? $this->merchantOrderId : $this->merchantCartId;
369
            $this->jsonResponse = new JsonSuccessResponse();
370
            $this->jsonResponse->setMerchantOrderId($theId);
371
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
372
            return true;
373
        }
374
375
        if ($this->pagantisOrder->getStatus() !== PagantisModelOrder::STATUS_AUTHORIZED) {
376
            $status = '-';
377
            if ($this->pagantisOrder instanceof \Pagantis\OrdersApiClient\Model\Order) {
0 ignored issues
show
introduced by
$this->pagantisOrder is always a sub-type of Pagantis\OrdersApiClient\Model\Order.
Loading history...
378
                $status = $this->pagantisOrder->getStatus();
379
            }
380
            throw new WrongStatusException($status);
381
        }
382
        return false;
383
    }
384
385
    /**
386
     * Check that the merchant order and the order in PAGANTIS have the same amount to prevent hacking
387
     *
388
     * @throws Exception
389
     */
390
    public function validateAmount()
391
    {
392
        $totalAmount = (string) $this->pagantisOrder->getShoppingCart()->getTotalAmount();
393
        $merchantAmount = (string) (100 * $this->merchantCart->getOrderTotal(true));
394
        $merchantAmount = explode('.', explode(',', $merchantAmount)[0])[0];
395
        if ($totalAmount != $merchantAmount) {
396
            $psTotalAmount = substr_replace(
397
                $merchantAmount,
398
                '.',
399
                (Tools::strlen($merchantAmount) -2),
400
                0
401
            );
402
403
            $pgTotalAmountInCents = (string) $this->pagantisOrder->getShoppingCart()->getTotalAmount();
404
            $pgTotalAmount = substr_replace(
405
                $pgTotalAmountInCents,
406
                '.',
407
                (Tools::strlen($pgTotalAmountInCents) -2),
408
                0
409
            );
410
411
            $this->amountMismatchError = '. Amount mismatch in PrestaShop Cart #'. $this->merchantCartId .
412
                ' compared with Pagantis Order: ' . $this->pagantisOrderId .
413
                '. The Cart in PrestaShop has an amount of ' . $psTotalAmount . ' and in Pagantis ' .
414
                $pgTotalAmount . ' PLEASE REVIEW THE ORDER';
415
416
            $this->saveLog(array(
417
                'requestId' => $this->requestId,
418
                'message' => $this->amountMismatchError
419
            ));
420
            $numberPagantisAmount = (integer) $this->pagantisOrder->getShoppingCart()->getTotalAmount();
421
            $numberMerchantAmount = (integer) (100 * $this->merchantCart->getOrderTotal(true));
422
            $amountDff =  $numberMerchantAmount - $numberPagantisAmount;
423
            if (abs($amountDff) > self::MISMATCH_AMOUNT_THRESHOLD) {
424
                throw new AmountMismatchException($totalAmount, $merchantAmount);
425
            }
426
        }
427
    }
428
429
    /**
430
     * Check that the merchant order was not previously processes and is ready to be paid
431
     *
432
     * @throws Exception
433
     */
434
    public function checkMerchantOrderStatus()
435
    {
436
        try {
437
            if ($this->merchantCart->orderExists() !== false) {
438
                $exceptionMessage = sprintf(
439
                    "Existing Order[origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s]",
440
                    $this->getOrigin(),
441
                    $this->merchantCartId,
442
                    $this->merchantOrderId,
443
                    $this->pagantisOrderId
444
                );
445
                throw new UnknownException($exceptionMessage);
446
            }
447
448
            // Double check
449
            $tableName = _DB_PREFIX_ . self::ORDERS_TABLE;
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
450
            $fieldName = 'ps_order_id';
451
            $sql = ('select ' . $fieldName . ' from `' . $tableName . '` where `id` = ' . (int)$this->merchantCartId
452
                . ' and `order_id` = \'' . $this->pagantisOrderId . '\''
453
                . ' and `token` = \'' . $this->token . '\''
454
                . ' and `' . $fieldName . '` is not null');
455
            $results = Db::getInstance()->ExecuteS($sql);
456
            if (is_array($results) && count($results) === 1) {
457
                $this->getMerchantOrderId();
458
                $exceptionMessage = sprintf(
459
                    "Order was already created [origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s]",
460
                    $this->getOrigin(),
461
                    $this->merchantCartId,
462
                    $this->merchantOrderId,
463
                    $this->pagantisOrderId
464
                );
465
                throw new UnknownException($exceptionMessage);
466
            }
467
        } catch (\Exception $exception) {
468
            throw new UnknownException($exception->getMessage());
469
        }
470
        return true;
471
    }
472
473
    /**
474
     * Process the merchant order and notify client
475
     *
476
     * @throws Exception
477
     */
478
    public function processMerchantOrder()
479
    {
480
        try {
481
            $metadataOrder = $this->pagantisOrder->getMetadata();
482
            $metadataInfo = '';
483
            foreach ($metadataOrder as $metadataKey => $metadataValue) {
484
                if ($metadataKey == 'promotedProduct') {
485
                    $metadataInfo .= $metadataValue;
486
                }
487
            }
488
489
            $this->module->validateOrder(
490
                $this->merchantCartId,
491
                Configuration::get('PS_OS_PAYMENT'),
492
                $this->merchantCart->getOrderTotal(true),
493
                $this->productName,
494
                'pagantisOrderId: ' . $this->pagantisOrder->getId() . ' ' .
495
                'pagantisOrderStatus: '. $this->pagantisOrder->getStatus() .
496
                $this->amountMismatchError .
497
                $metadataInfo,
498
                array('transaction_id' => $this->pagantisOrderId),
499
                null,
500
                false,
501
                $this->config['secureKey']
502
            );
503
        } catch (\Exception $exception) {
504
            throw new UnknownException($exception->getMessage());
505
        }
506
        try {
507
            Db::getInstance()->update(
508
                self::ORDERS_TABLE,
509
                array('ps_order_id' => $this->module->currentOrder),
510
                'id = '. (int)$this->merchantCartId
511
                    . ' and order_id = \'' . $this->pagantisOrderId . '\''
512
                    . ' and token = \'' . $this->token . '\''
513
            );
514
515
        } catch (\Exception $exception) {
516
            $exceptionMessage = sprintf(
517
                "processMerchantOrder exception[origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s][%s]",
518
                $this->getOrigin(),
519
                $this->merchantCartId,
520
                $this->merchantOrderId,
521
                $this->pagantisOrderId,
522
                $exception->getMessage()
523
            );
524
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $exceptionMessage));
525
        }
526
    }
527
528
    /**
529
     * Confirm the order in PAGANTIS
530
     *
531
     * @throws Exception
532
     */
533
    private function confirmPagantisOrder()
534
    {
535
        try {
536
            $this->orderClient->confirmOrder($this->pagantisOrderId);
537
            try {
538
                $mode = ($this->isPost()) ? 'NOTIFICATION' : 'REDIRECTION';
539
                $message = 'Order CONFIRMED. The order was confirmed by a ' . $mode .
540
                    '. Pagantis OrderId=' . $this->pagantisOrderId .
541
                    '. Prestashop OrderId=' . $this->module->currentOrder;
542
                $this->saveLog(array('requestId' => $this->requestId, 'message' => $message));
543
            } catch (\Exception $exception) {
544
                $exceptionMessage = sprintf(
545
                    "confirmPagantisOrder exception[origin=%s][cartId=%s][merchantOrderId=%s][pagantisOrderId=%s][%s]",
546
                    $this->getOrigin(),
547
                    $this->merchantCartId,
548
                    $this->merchantOrderId,
549
                    $this->pagantisOrderId,
550
                    $exception->getMessage()
551
                );
552
                $this->saveLog(array('requestId' => $this->requestId, 'message' => $exceptionMessage));
553
            }
554
        } catch (\Exception $exception) {
555
            throw new UnknownException(sprintf("[%s]%s", $this->getOrigin(), $exception->getMessage()));
556
        }
557
    }
558
559
    /**
560
     * Leave the merchant order as it was previously
561
     *
562
     * @throws Exception
563
     */
564
    public function rollbackMerchantOrder()
565
    {
566
        try {
567
            $this->getMerchantOrderId();
568
            $message = 'Roolback method: ' .
569
                '. Pagantis OrderId=' . $this->pagantisOrderId .
570
                '. Prestashop CartId=' . $this->merchantCartId .
571
                '. Prestashop OrderId=' . $this->merchantOrderId;
572
            if ($this->module->currentOrder) {
573
                $objOrder = new Order($this->module->currentOrder);
574
                $history = new OrderHistory();
0 ignored issues
show
Bug introduced by
The type OrderHistory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
575
                $history->id_order = (int)$objOrder->id;
576
                $history->changeIdOrderState(8, (int)($objOrder->id));
577
                $message .= ' Prestashop OrderId=' . $this->merchantCartId;
578
            }
579
            $this->saveLog(array(
580
                'requestId' => $this->requestId,
581
                'message' => $message
582
            ));
583
        } catch (\Exception $exception) {
584
            $this->saveLog(array(
585
                'requestId' => $this->requestId,
586
                'message' => $exception->getMessage()
587
            ));
588
        }
589
    }
590
591
    /**
592
     * Lock the concurrency to prevent duplicated inputs
593
     * @param $orderId
594
     *
595
     * @return bool
596
     * @throws UnknownException
597
     */
598
    protected function blockConcurrency($orderId)
599
    {
600
        try {
601
            $table = self::CART_TABLE;
602
            $insertBlock = Db::getInstance()->insert($table, array('id' =>(int)$orderId, 'timestamp' =>(time())));
603
            if ($insertBlock === false) {
604
                if ($this->isNotification()) {
605
                    throw new ConcurrencyException();
606
                } else {
607
                    $query = sprintf(
608
                        "SELECT TIMESTAMPDIFF(SECOND,NOW()-INTERVAL %s SECOND, FROM_UNIXTIME(timestamp)) 
609
                              as rest FROM %s WHERE %s",
610
                        self::CONCURRENCY_TIMEOUT,
611
                        _DB_PREFIX_.$table,
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
612
                        'id='.(int)$orderId
613
                    );
614
                    $resultSeconds = Db::getInstance()->getValue($query);
615
                    $restSeconds = isset($resultSeconds) ? ($resultSeconds) : 0;
616
                    $secondsToExpire = ($restSeconds>self::CONCURRENCY_TIMEOUT) ?
617
                        self::CONCURRENCY_TIMEOUT : $restSeconds;
618
                    if ($secondsToExpire > 0) {
619
                        sleep($secondsToExpire + 1);
620
                    }
621
622
                    $this->getMerchantOrderId();
623
                    $this->getPagantisOrderId();
624
625
                    $logMessage  = sprintf(
626
                        "User has waited %s seconds, default %s, bd time to expire %s [cartId=%s][origin=%s]",
627
                        $secondsToExpire,
628
                        self::CONCURRENCY_TIMEOUT,
629
                        $restSeconds,
630
                        $this->merchantCartId,
631
                        $this->getOrigin()
632
                    );
633
634
                    $this->saveLog(array('requestId' => $this->requestId, 'message' => $logMessage));
635
636
                    // After waiting...user continue the confirmation, hoping that previous call have finished.
637
                    return true;
638
                }
639
            }
640
        } catch (\Exception $exception) {
641
            throw new UnknownException($exception->getMessage());
642
        }
643
    }
644
645
    /**
646
     * @param null $orderId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $orderId is correct as it would always require null to be passed?
Loading history...
647
     *
648
     * @throws ConcurrencyException
649
     */
650
    private function unblockConcurrency($orderId = null)
651
    {
652
        try {
653
            if (is_null($orderId)) {
0 ignored issues
show
introduced by
The condition is_null($orderId) is always true.
Loading history...
654
                Db::getInstance()->delete(self::CART_TABLE, 'timestamp < ' . (time() - self::CONCURRENCY_TIMEOUT));
655
                return;
656
            }
657
            Db::getInstance()->delete(self::CART_TABLE, 'id = ' . (int)$orderId);
658
        } catch (\Exception $exception) {
659
            throw new ConcurrencyException();
660
        }
661
    }
662
663
    /**
664
     * Do all the necessary actions to cancel the confirmation process in case of error
665
     * 1. Unblock concurrency
666
     * 2. Save log
667
     *
668
     * @param null $exception
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $exception is correct as it would always require null to be passed?
Loading history...
669
     * @return mixed
670
     */
671
    public function cancelProcess($exception = null)
672
    {
673
        $debug = debug_backtrace();
674
        $method = $debug[1]['function'];
675
        $line = $debug[1]['line'];
676
        $this->getMerchantOrderId();
677
        $data = array(
678
            'requestId' => $this->requestId,
679
            'merchantCartId' => $this->merchantCartId,
680
            'merchantOrderId' => $this->merchantOrderId,
681
            'pagantisOrderId' => $this->pagantisOrderId,
682
            'message' => ($exception)? $exception->getMessage() : 'Unable to get Exception message',
0 ignored issues
show
introduced by
$exception is of type null, thus it always evaluated to false.
Loading history...
683
            'statusCode' => ($exception)? $exception->getCode() : 'Unable to get Exception statusCode',
0 ignored issues
show
introduced by
$exception is of type null, thus it always evaluated to false.
Loading history...
684
            'method' => $method,
685
            'file' => __FILE__,
686
            'line' => $line,
687
        );
688
        $this->saveLog($data);
689
        return $this->finishProcess(true);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(true) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
690
    }
691
692
    /**
693
     * Redirect the request to the e-commerce or show the output in json
694
     *
695
     * @param bool $error
696
     * @return mixed
697
     */
698
    public function finishProcess($error = true)
699
    {
700
        $this->getMerchantOrderId();
701
        if ($this->isPost()) {
702
            $returnMessage = sprintf(
703
                "[origin=%s][cartId=%s][prestashopOrderId=%s][pagantisOrderId=%s][message=%s]",
704
                $this->getOrigin(),
705
                $this->merchantCartId,
706
                $this->merchantOrderId,
707
                $this->pagantisOrderId,
708
                $this->jsonResponse->getResult()
709
            );
710
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $returnMessage));
711
            $this->jsonResponse->printResponse();
712
        } else {
713
            $parameters = array(
714
                'id_cart' => $this->merchantCartId,
715
                'key' => $this->config['secureKey'],
716
                'id_module' => $this->module->id,
717
                'id_order' => ($this->pagantisOrder) ? $this->pagantisOrder->getId() : null,
718
            );
719
            $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
720
            $returnMessage = sprintf(
721
                "[origin=%s][cartId=%s][prestashopOrderId=%s][pagantisOrderId=%s][returnUrl=%s]",
722
                $this->getOrigin(),
723
                $this->merchantCartId,
724
                $this->merchantOrderId,
725
                $this->pagantisOrderId,
726
                $url
727
            );
728
            $this->saveLog(array('requestId' => $this->requestId, 'message' => $returnMessage));
729
730
            return $this->redirect($url, $parameters);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->redirect($url, $parameters) targeting AbstractController::redirect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
731
        }
732
    }
733
734
    /**
735
     * @return bool
736
     */
737
    private function isNotification()
738
    {
739
        return ($this->getOrigin() == 'Notification');
740
    }
741
742
    /**
743
     * @return bool
744
     */
745
    private function isRedirect()
0 ignored issues
show
Unused Code introduced by
The method isRedirect() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
746
    {
747
        return ($this->getOrigin() == 'Order');
748
    }
749
750
    /**
751
     * @return bool
752
     */
753
    private function isPost()
754
    {
755
        return $_SERVER['REQUEST_METHOD'] == 'POST';
756
    }
757
758
    /**
759
     * @return bool
760
     */
761
    private function isGet()
762
    {
763
        return $_SERVER['REQUEST_METHOD'] == 'GET';
764
    }
765
766
    /**
767
     * @return mixed
768
     */
769
    public function getOrigin()
770
    {
771
        return $this->origin;
772
    }
773
774
    /**
775
     * @param mixed $origin
776
     */
777
    public function setOrigin($origin)
778
    {
779
        $this->origin = $origin;
780
    }
781
}
782