Passed
Pull Request — master (#34)
by Raúl
05:11
created

getPagantisOrderId()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 0
dl 0
loc 12
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_AUTHORIZED) {
233
            $status = '-';
234
            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...
235
                $status = $this->pagantisOrder->getStatus();
236
            }
237
            throw new WrongStatusException($status);
238
        }
239
    }
240
241
    /**
242
     * Check that the merchant order was not previously processes and is ready to be paid
243
     *
244
     * @throws Exception
245
     */
246
    public function checkMerchantOrderStatus()
247
    {
248
        if ($this->merchantOrder->orderExists() !== false) {
249
            throw new WrongStatusException('already_processed');
250
        }
251
    }
252
253
    /**
254
     * Check that the merchant order and the order in PAGANTIS have the same amount to prevent hacking
255
     *
256
     * @throws Exception
257
     */
258
    public function validateAmount()
259
    {
260
        $totalAmount = $this->pagantisOrder->getShoppingCart()->getTotalAmount();
261
        $merchantAmount = (int) (100 * $this->merchantOrder->getOrderTotal(true));
262
        if ($totalAmount != $merchantAmount) {
263
            $this->processError = true;
264
            throw new AmountMismatchException($totalAmount, $merchantAmount);
265
        }
266
    }
267
268
    /**
269
     * Process the merchant order and notify client
270
     *
271
     * @throws Exception
272
     */
273
    public function processMerchantOrder()
274
    {
275
        try {
276
            $this->module->validateOrder(
277
                $this->merchantOrderId,
278
                Configuration::get('PS_OS_PAYMENT'),
279
                $this->merchantOrder->getOrderTotal(true),
280
                $this->module->displayName,
281
                'pagantisOrderId: ' . $this->pagantisOrder->getId().
282
                'pagantisOrderStatus: '. $this->pagantisOrder->getStatus(),
283
                array('transaction_id' => $this->pagantisOrderId),
284
                null,
285
                false,
286
                $this->config['secureKey']
287
            );
288
        } catch (\Exception $exception) {
289
            throw new UnknownException($exception->getMessage());
290
        }
291
    }
292
293
    /**
294
     * Confirm the order in PAGANTIS
295
     *
296
     * @throws Exception
297
     */
298
    private function confirmPagantisOrder()
299
    {
300
        try {
301
            $this->orderClient->confirmOrder($this->pagantisOrderId);
302
        } catch (\Exception $exception) {
303
            throw new UnknownException($exception->getMessage());
304
        }
305
    }
306
307
    /**
308
     * Leave the merchant order as it was previously
309
     *
310
     * @throws Exception
311
     */
312
    public function rollbackMerchantOrder()
313
    {
314
        // Do nothing because the order is created only when the purchase was successfully
315
    }
316
317
318
    /**
319
     * Lock the concurrency to prevent duplicated inputs
320
     *
321
     * @param $orderId
322
     * @throws Exception
323
     */
324
    protected function blockConcurrency($orderId)
325
    {
326
        try {
327
            Db::getInstance()->insert('pagantis_cart_process', array('id' => $orderId, 'timestamp' => (time())));
328
        } catch (\Exception $exception) {
329
            throw new ConcurrencyException();
330
        }
331
    }
332
333
    /**
334
     * Unlock the concurrency
335
     *
336
     * @throws Exception
337
     */
338
    protected function unblockConcurrency()
339
    {
340
        try {
341
            Db::getInstance()->delete('pagantis_cart_process', 'timestamp < ' . (time() - 6));
342
        } catch (\Exception $exception) {
343
            throw new ConcurrencyException();
344
        }
345
    }
346
347
    /**
348
     * Do all the necessary actions to cancel the confirmation process in case of error
349
     * 1. Unblock concurrency
350
     * 2. Save log
351
     *
352
     * @param String|null $response Response as json
353
     *
354
     */
355
    public function cancelProcess($response = null)
356
    {
357
        if ($this->merchantOrder && $this->processError === true) {
358
            sleep(5);
359
            $id = (!is_null($this->pagantisOrder))?$this->pagantisOrder->getId():null;
360
            $status = (!is_null($this->pagantisOrder))?$this->pagantisOrder->getStatus():null;
361
            $this->module->validateOrder(
362
                $this->merchantOrderId,
363
                Configuration::get('PS_OS_ERROR'),
364
                $this->merchantOrder->getOrderTotal(true),
365
                $this->module->displayName,
366
                ' pagantisOrderId: ' . $id.
367
                ' pagantisOrderStatus: '. $status,
368
                null,
369
                null,
370
                false,
371
                $this->config['secureKey']
372
            );
373
        }
374
375
        $debug = debug_backtrace();
376
        $method = $debug[1]['function'];
377
        $line = $debug[1]['line'];
378
        $this->saveLog(array(
379
            'message' => $response,
380
            'method' => $method,
381
            'file' => __FILE__,
382
            'line' => $line,
383
            'code' => 200
384
        ));
385
        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...
386
    }
387
388
    /**
389
     * Redirect the request to the e-commerce or show the output in json
390
     *
391
     * @param bool $error
392
     */
393
    public function finishProcess($error = true)
394
    {
395
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
396
            $this->jsonResponse->printResponse();
397
        }
398
399
        $parameters = array(
400
            'id_cart' => $this->merchantOrderId,
401
            'key' => $this->config['secureKey'],
402
            'id_module' => $this->module->id,
403
            'id_order' => ($this->pagantisOrder)?$this->pagantisOrder->getId(): null,
404
        );
405
        $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
406
        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...
407
    }
408
}
409