Passed
Pull Request — master (#34)
by Raúl
03:39 queued 01:18
created

checkMerchantOrderStatus()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\ConfigurationNotFoundException;
21
use Pagantis\ModuleUtils\Exception\UnknownException;
22
use Pagantis\ModuleUtils\Exception\WrongStatusException;
23
use Pagantis\ModuleUtils\Model\Response\JsonSuccessResponse;
24
use Pagantis\ModuleUtils\Model\Response\JsonExceptionResponse;
25
26
/**
27
 * Class PagantisNotifyModuleFrontController
28
 */
29
class PagantisNotifyModuleFrontController extends AbstractController
30
{
31
    /**
32
     * @var bool $processError
33
     */
34
    protected $processError;
35
36
    /**
37
     * @var string $merchantOrderId
38
     */
39
    protected $merchantOrderId;
40
41
    /**
42
     * @var \Cart $merchantOrder
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...
43
     */
44
    protected $merchantOrder;
45
46
    /**
47
     * @var string $pagantisOrderId
48
     */
49
    protected $pagantisOrderId;
50
51
    /**
52
     * @var \Pagantis\OrdersApiClient\Model\Order $pagantisOrder
53
     */
54
    protected $pagantisOrder;
55
56
    /**
57
     * @var Pagantis\OrdersApiClient\Client $orderClient
58
     */
59
    protected $orderClient;
60
61
    /**
62
     * @var mixed $config
63
     */
64
    protected $config;
65
66
    /**
67
     * @var Object $jsonResponse
68
     */
69
    protected $jsonResponse;
70
71
    /**
72
     * @throws Exception
73
     */
74
    public function postProcess()
75
    {
76
        try {
77
            $this->checkConcurrency();
78
            $this->getMerchantOrder();
79
            $this->getPagantisOrderId();
80
            $this->getPagantisOrder();
81
            $this->checkOrderStatus();
82
            $this->checkMerchantOrderStatus();
83
            $this->validateAmount();
84
            $this->processMerchantOrder();
85
        } catch (\Exception $exception) {
86
            $this->jsonResponse = new JsonExceptionResponse();
87
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
88
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
89
            $this->jsonResponse->setException($exception);
90
            return $this->cancelProcess($this->jsonResponse);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($this->jsonResponse) 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...
Bug introduced by
$this->jsonResponse of type Pagantis\ModuleUtils\Mod...e\JsonExceptionResponse is incompatible with the type null|string expected by parameter $response of PagantisNotifyModuleFron...roller::cancelProcess(). ( Ignorable by Annotation )

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

90
            return $this->cancelProcess(/** @scrutinizer ignore-type */ $this->jsonResponse);
Loading history...
91
        }
92
93
        try {
94
            $this->jsonResponse = new JsonSuccessResponse();
95
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
96
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
97
            $this->confirmPagantisOrder();
98
        } catch (\Exception $exception) {
99
            $this->rollbackMerchantOrder();
100
            $this->jsonResponse = new JsonExceptionResponse();
101
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
102
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
103
            $this->jsonResponse->setException($exception);
104
            return $this->cancelProcess($this->jsonResponse);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($this->jsonResponse) 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...
105
        }
106
107
        try {
108
            $this->unblockConcurrency();
109
        } catch (\Exception $exception) {
110
            // Do nothing
111
        }
112
113
        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...
114
    }
115
116
    /**
117
     * Check the concurrency of the purchase
118
     *
119
     * @throws Exception
120
     */
121
    public function checkConcurrency()
122
    {
123
        $this->prepareVariables();
124
        $this->unblockConcurrency();
125
        $this->blockConcurrency($this->merchantOrderId);
126
    }
127
128
    /**
129
     * Find and init variables needed to process payment
130
     *
131
     * @throws Exception
132
     */
133
    public function prepareVariables()
134
    {
135
        $this->processError = false;
136
        $callbackOkUrl = $this->context->link->getPageLink(
137
            'order-confirmation',
138
            null,
139
            null
140
        );
141
        $callbackKoUrl = $this->context->link->getPageLink(
142
            'order',
143
            null,
144
            null,
145
            array('step'=>3)
146
        );
147
        try {
148
            $this->config = array(
149
                'urlOK' => (Pagantis::getExtraConfig('PAGANTIS_URL_OK') !== '') ?
150
                    Pagantis::getExtraConfig('PAGANTIS_URL_OK') : $callbackOkUrl,
151
                'urlKO' => (Pagantis::getExtraConfig('PAGANTIS_URL_KO') !== '') ?
152
                    Pagantis::getExtraConfig('PAGANTIS_URL_KO') : $callbackKoUrl,
153
                'publicKey' => Configuration::get('pagantis_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...
154
                'privateKey' => Configuration::get('pagantis_private_key'),
155
                'secureKey' => Tools::getValue('key'),
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...
156
            );
157
        } catch (\Exception $exception) {
158
            throw new ConfigurationNotFoundException();
159
        }
160
161
        $this->merchantOrderId = Tools::getValue('id_cart');
162
        if ($this->merchantOrderId == '') {
163
            throw new QuoteNotFoundException();
164
        }
165
166
167
        if (!($this->config['secureKey'] && $this->merchantOrderId && Module::isEnabled(self::PAGANTIS_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...
168
            // This exception is only for Prestashop
169
            throw new UnknownException('Module may not be enabled');
170
        }
171
    }
172
173
    /**
174
     * Retrieve the merchant order by id
175
     *
176
     * @throws Exception
177
     */
178
    public function getMerchantOrder()
179
    {
180
        try {
181
            $this->merchantOrder = new Cart($this->merchantOrderId);
182
            if (!Validate::isLoadedObject($this->merchantOrder)) {
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...
183
                // This exception is only for Prestashop
184
                throw new UnknownException('Unable to load cart');
185
            }
186
        } catch (\Exception $exception) {
187
            throw new MerchantOrderNotFoundException();
188
        }
189
    }
190
191
    /**
192
     * Find PAGANTIS Order Id in AbstractController::PAGANTIS_ORDERS_TABLE
193
     *
194
     * @throws Exception
195
     */
196
    private function getPagantisOrderId()
197
    {
198
        try {
199
            $this->pagantisOrderId= Db::getInstance()->getValue(
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...
200
                'select order_id from '._DB_PREFIX_.'pagantis_order where id = '.$this->merchantOrderId
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...
201
            );
202
203
            if (is_null($this->pagantisOrderId)) {
204
                throw new NoIdentificationException();
205
            }
206
        } catch (\Exception $exception) {
207
            throw new NoIdentificationException();
208
        }
209
    }
210
211
    /**
212
     * Find PAGANTIS Order in Orders Server using Pagantis\OrdersApiClient
213
     *
214
     * @throws Exception
215
     */
216
    private function getPagantisOrder()
217
    {
218
        $this->orderClient = new PagantisClient($this->config['publicKey'], $this->config['privateKey']);
219
        $this->pagantisOrder = $this->orderClient->getOrder($this->pagantisOrderId);
220
        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...
221
            throw new OrderNotFoundException();
222
        }
223
    }
224
225
    /**
226
     * Compare statuses of merchant order and PAGANTIS order, witch have to be the same.
227
     *
228
     * @throws Exception
229
     */
230
    public function checkOrderStatus()
231
    {
232
        if ($this->pagantisOrder->getStatus() === PagantisModelOrder::STATUS_CONFIRMED) {
233
            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...
234
        }
235
236
        if ($this->pagantisOrder->getStatus() !== PagantisModelOrder::STATUS_AUTHORIZED) {
237
            $status = '-';
238
            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...
239
                $status = $this->pagantisOrder->getStatus();
240
            }
241
            throw new WrongStatusException($status);
242
        }
243
    }
244
245
    /**
246
     * Check that the merchant order was not previously processes and is ready to be paid
247
     *
248
     * @throws Exception
249
     */
250
    public function checkMerchantOrderStatus()
251
    {
252
        if ($this->merchantOrder->orderExists() !== false) {
253
            throw new WrongStatusException('already_processed');
254
        }
255
    }
256
257
    /**
258
     * Check that the merchant order and the order in PAGANTIS have the same amount to prevent hacking
259
     *
260
     * @throws Exception
261
     */
262
    public function validateAmount()
263
    {
264
        $totalAmount = $this->pagantisOrder->getShoppingCart()->getTotalAmount();
265
        $merchantAmount = (int) (100 * $this->merchantOrder->getOrderTotal(true));
266
        if ($totalAmount != $merchantAmount) {
267
            $this->processError = true;
268
            throw new AmountMismatchException($totalAmount, $merchantAmount);
269
        }
270
    }
271
272
    /**
273
     * Process the merchant order and notify client
274
     *
275
     * @throws Exception
276
     */
277
    public function processMerchantOrder()
278
    {
279
        try {
280
            $this->module->validateOrder(
281
                $this->merchantOrderId,
282
                Configuration::get('PS_OS_PAYMENT'),
283
                $this->merchantOrder->getOrderTotal(true),
284
                $this->module->displayName,
285
                'pagantisOrderId: ' . $this->pagantisOrder->getId().
286
                'pagantisOrderStatus: '. $this->pagantisOrder->getStatus(),
287
                array('transaction_id' => $this->pagantisOrderId),
288
                null,
289
                false,
290
                $this->config['secureKey']
291
            );
292
        } catch (\Exception $exception) {
293
            throw new UnknownException($exception->getMessage());
294
        }
295
    }
296
297
    /**
298
     * Confirm the order in PAGANTIS
299
     *
300
     * @throws Exception
301
     */
302
    private function confirmPagantisOrder()
303
    {
304
        try {
305
            $this->orderClient->confirmOrder($this->pagantisOrderId);
306
            try {
307
                $this->saveLog(array(
308
                    'message' => "Order CONFIRMED. " .
309
                    'The order was confirmed by a ' .
310
                    ($_SERVER['REQUEST_METHOD'] == 'POST') ? 'NOTIFICATION' : 'REDIRECTION'
311
                ));
312
            } catch (\Exception $e) {
313
                // Do nothing
314
            }
315
        } catch (\Exception $exception) {
316
            throw new UnknownException($exception->getMessage());
317
        }
318
    }
319
320
    /**
321
     * Leave the merchant order as it was previously
322
     *
323
     * @throws Exception
324
     */
325
    public function rollbackMerchantOrder()
326
    {
327
        // Do nothing because the order is created only when the purchase was successfully
328
    }
329
330
331
    /**
332
     * Lock the concurrency to prevent duplicated inputs
333
     *
334
     * @param $orderId
335
     * @throws Exception
336
     */
337
    protected function blockConcurrency($orderId)
338
    {
339
        try {
340
            Db::getInstance()->insert('pagantis_cart_process', array('id' => $orderId, 'timestamp' => (time())));
341
        } catch (\Exception $exception) {
342
            throw new ConcurrencyException();
343
        }
344
    }
345
346
    /**
347
     * Unlock the concurrency
348
     *
349
     * @throws Exception
350
     */
351
    protected function unblockConcurrency()
352
    {
353
        try {
354
            Db::getInstance()->delete('pagantis_cart_process', 'timestamp < ' . (time() - 6));
355
        } catch (\Exception $exception) {
356
            throw new ConcurrencyException();
357
        }
358
    }
359
360
    /**
361
     * Do all the necessary actions to cancel the confirmation process in case of error
362
     * 1. Unblock concurrency
363
     * 2. Save log
364
     *
365
     * @param String|null $response Response as json
366
     *
367
     */
368
    public function cancelProcess($response = null)
369
    {
370
        if ($this->merchantOrder && $this->processError === true) {
371
            sleep(5);
372
            $id = (!is_null($this->pagantisOrder))?$this->pagantisOrder->getId():null;
373
            $status = (!is_null($this->pagantisOrder))?$this->pagantisOrder->getStatus():null;
374
            $this->module->validateOrder(
375
                $this->merchantOrderId,
376
                Configuration::get('PS_OS_ERROR'),
377
                $this->merchantOrder->getOrderTotal(true),
378
                $this->module->displayName,
379
                ' pagantisOrderId: ' . $id.
380
                ' pagantisOrderStatus: '. $status,
381
                null,
382
                null,
383
                false,
384
                $this->config['secureKey']
385
            );
386
        }
387
388
        $debug = debug_backtrace();
389
        $method = $debug[1]['function'];
390
        $line = $debug[1]['line'];
391
        $this->saveLog(array(
392
            'message' => $response,
393
            'method' => $method,
394
            'file' => __FILE__,
395
            'line' => $line,
396
            'code' => 200
397
        ));
398
        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...
399
    }
400
401
    /**
402
     * Redirect the request to the e-commerce or show the output in json
403
     *
404
     * @param bool $error
405
     */
406
    public function finishProcess($error = true)
407
    {
408
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
409
            $this->jsonResponse->printResponse();
410
        }
411
412
        $parameters = array(
413
            'id_cart' => $this->merchantOrderId,
414
            'key' => $this->config['secureKey'],
415
            'id_module' => $this->module->id,
416
            'id_order' => ($this->pagantisOrder)?$this->pagantisOrder->getId(): null,
417
        );
418
        $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
419
        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...
420
    }
421
}
422