Passed
Pull Request — master (#23)
by Raúl
02:46
created

unblockConcurrency()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 6
rs 10
1
<?php
2
/**
3
 * This file is part of the official Paylater module for PrestaShop.
4
 *
5
 * @author    Paga+Tarde <[email protected]>
6
 * @copyright 2019 Paga+Tarde
7
 * @license   proprietary
8
 */
9
10
require_once('AbstractController.php');
11
12
use PagaMasTarde\OrdersApiClient\Client as PmtClient;
13
use PagaMasTarde\OrdersApiClient\Model\Order as PmtModelOrder;
14
use PagaMasTarde\ModuleUtils\Exception\AmountMismatchException;
15
use PagaMasTarde\ModuleUtils\Exception\ConcurrencyException;
16
use PagaMasTarde\ModuleUtils\Exception\MerchantOrderNotFoundException;
17
use PagaMasTarde\ModuleUtils\Exception\NoIdentificationException;
18
use PagaMasTarde\ModuleUtils\Exception\NoOrderFoundException;
19
use PagaMasTarde\ModuleUtils\Exception\NoQuoteFoundException;
20
use PagaMasTarde\ModuleUtils\Exception\UnknownException;
21
use PagaMasTarde\ModuleUtils\Exception\WrongStatusException;
22
use PagaMasTarde\ModuleUtils\Model\Response\JsonSuccessResponse;
23
use PagaMasTarde\ModuleUtils\Model\Response\JsonExceptionResponse;
24
25
/**
26
 * Class PaylaterNotifyModuleFrontController
27
 */
28
class PaylaterNotifyModuleFrontController extends AbstractController
29
{
30
31
    /**
32
     * @var string $merchantOrderId
33
     */
34
    protected $merchantOrderId;
35
36
    /**
37
     * @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...
38
     */
39
    protected $merchantOrder;
40
41
    /**
42
     * @var string $pmtOrderId
43
     */
44
    protected $pmtOrderId;
45
46
    /**
47
     * @var \PagaMasTarde\OrdersApiClient\Model\Order $pmtOrder
48
     */
49
    protected $pmtOrder;
50
51
    /**
52
     * @var PagaMasTarde\OrdersApiClient\Client $orderClient
53
     */
54
    protected $orderClient;
55
56
    /**
57
     * @var mixed $config
58
     */
59
    protected $config;
60
61
    /**
62
     * @var Object $jsonResponse
63
     */
64
    protected $jsonResponse;
65
66
    /**
67
     * @throws Exception
68
     */
69
    public function postProcess()
70
    {
71
        $response = null;
72
        try {
73
            $this->checkConcurrency();
74
            $this->getMerchantOrder();
75
            $this->getPmtOrderId();
76
            $this->getPmtOrder();
77
            $this->checkOrderStatus();
78
            $this->checkMerchantOrderStatus();
79
            $this->validateAmount();
80
            $this->processMerchantOrder();
81
        } catch (\Exception $exception) {
82
            $this->jsonResponse = new JsonExceptionResponse();
83
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
84
            $this->jsonResponse->setPmtOrderId($this->pmtOrderId);
85
            $this->jsonResponse->setException($exception);
86
            $response = $this->jsonResponse->toJson();
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
87
            return $this->cancelProcess();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess() targeting PaylaterNotifyModuleFron...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...
88
        }
89
90
        try {
91
            if (!isset($response)) {
92
                $this->jsonResponse = new JsonSuccessResponse();
93
                $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
94
                $this->jsonResponse->setPmtOrderId($this->pmtOrderId);
95
                $this->confirmPmtOrder();
96
            }
97
        } catch (\Exception $exception) {
98
            $this->rollbackMerchantOrder();
99
            $this->jsonResponse = new JsonExceptionResponse();
100
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
101
            $this->jsonResponse->setPmtOrderId($this->pmtOrderId);
102
            $this->jsonResponse->setException($exception);
103
            $this->jsonResponse->toJson();
104
            return $this->cancelProcess();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess() targeting PaylaterNotifyModuleFron...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 PaylaterNotifyModuleFron...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
     * Find and init variables needed to process payment
118
     *
119
     * @throws Exception
120
     */
121
    public function prepareVariables()
122
    {
123
        $callbackOkUrl = $this->context->link->getPageLink(
124
            'order-confirmation',
125
            null,
126
            null
127
        );
128
        $callbackKoUrl = $this->context->link->getPageLink(
129
            'order',
130
            null,
131
            null,
132
            array('step'=>3)
133
        );
134
        try {
135
            $this->config = array(
136
                'urlOK' => (getenv('PMT_URL_OK') !== '') ? getenv('PMT_URL_OK') : $callbackOkUrl,
137
                'urlKO' => (getenv('PMT_URL_KO') !== '') ? getenv('PMT_URL_KO') : $callbackKoUrl,
138
                'publicKey' => Configuration::get('pmt_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...
139
                'privateKey' => Configuration::get('pmt_private_key'),
140
                '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...
141
            );
142
        } catch (\Exception $exception) {
143
            throw new UnknownException(self::CC_NO_CONFIG);
144
        }
145
146
        $this->merchantOrderId = Tools::getValue('id_cart');
147
        if ($this->merchantOrderId == '') {
148
            throw new NoQuoteFoundException();
149
        }
150
151
152
        if (!($this->config['secureKey'] && $this->merchantOrderId && Module::isEnabled(self::PMT_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...
153
            throw new UnknownException(self::CC_MALFORMED);
154
        }
155
    }
156
157
    /**
158
     * Check the concurrency of the purchase
159
     *
160
     * @throws Exception
161
     */
162
    public function checkConcurrency()
163
    {
164
        $this->prepareVariables();
165
        $this->unblockConcurrency();
166
        $this->blockConcurrency($this->merchantOrderId);
167
    }
168
169
    /**
170
     * Retrieve the merchant order by id
171
     *
172
     * @throws Exception
173
     */
174
    public function getMerchantOrder()
175
    {
176
        try {
177
            $this->merchantOrder = new Cart($this->merchantOrderId);
178
            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...
179
                throw new \Exception(self::GMO_CART_NOT_LOADED, 500);
180
            }
181
        } catch (\Exception $exception) {
182
            throw new MerchantOrderNotFoundException();
183
        }
184
    }
185
186
    /**
187
     * Find PMT Order Id in AbstractController::PMT_ORDERS_TABLE
188
     *
189
     * @throws Exception
190
     */
191
    private function getPmtOrderId()
192
    {
193
        try {
194
            $this->pmtOrderId= 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...
195
                'select order_id from '._DB_PREFIX_.'pmt_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...
196
            );
197
198
            if (is_null($this->pmtOrderId)) {
199
                throw new NoIdentificationException();
200
            }
201
        } catch (\Exception $exception) {
202
            throw new NoIdentificationException();
203
        }
204
    }
205
206
    /**
207
     * Find PMT Order in Orders Server using PagaMasTarde\OrdersApiClient
208
     *
209
     * @throws Exception
210
     */
211
    private function getPmtOrder()
212
    {
213
        $this->orderClient = new PmtClient($this->config['publicKey'], $this->config['privateKey']);
214
        $this->pmtOrder = $this->orderClient->getOrder($this->pmtOrderId);
215
        if (!($this->pmtOrder instanceof PmtModelOrder)) {
0 ignored issues
show
introduced by
$this->pmtOrder is always a sub-type of PagaMasTarde\OrdersApiClient\Model\Order.
Loading history...
216
            throw new NoOrderFoundException();
217
        }
218
    }
219
220
    /**
221
     * Compare statuses of merchant order and PMT order, witch have to be the same.
222
     *
223
     * @throws Exception
224
     */
225
    public function checkOrderStatus()
226
    {
227
        if ($this->pmtOrder->getStatus() !== PmtModelOrder::STATUS_AUTHORIZED) {
228
            $status = '-';
229
            if ($this->pmtOrder instanceof \PagaMasTarde\OrdersApiClient\Model\Order) {
0 ignored issues
show
introduced by
$this->pmtOrder is always a sub-type of PagaMasTarde\OrdersApiClient\Model\Order.
Loading history...
230
                $status = $this->pmtOrder->getStatus();
231
            }
232
            throw new WrongStatusException($status);
233
        }
234
    }
235
236
    /**
237
     * Check that the merchant order was not previously processes and is ready to be paid
238
     *
239
     * @throws Exception
240
     */
241
    public function checkMerchantOrderStatus()
242
    {
243
        if ($this->merchantOrder->orderExists() !== false) {
244
            throw new WrongStatusException('already_processed');
245
        }
246
    }
247
248
    /**
249
     * Check that the merchant order and the order in PMT have the same amount to prevent hacking
250
     *
251
     * @throws Exception
252
     */
253
    public function validateAmount()
254
    {
255
        $totalAmount = $this->pmtOrder->getShoppingCart()->getTotalAmount();
256
        $merchantAmount = (int)((string) (100 * $this->merchantOrder->getOrderTotal(true)));
257
        if ($totalAmount != $merchantAmount) {
258
            throw new AmountMismatchException($totalAmount, $merchantAmount);
259
        }
260
    }
261
262
    /**
263
     * Process the merchant order and notify client
264
     *
265
     * @throws Exception
266
     */
267
    public function processMerchantOrder()
268
    {
269
        try {
270
            $this->module->validateOrder(
271
                $this->merchantOrderId,
272
                Configuration::get('PS_OS_PAYMENT'),
273
                $this->merchantOrder->getOrderTotal(true),
274
                $this->module->displayName,
275
                'pmtOrderId: ' . $this->pmtOrder->getId(),
276
                array('transaction_id' => $this->pmtOrderId),
277
                null,
278
                false,
279
                $this->config['secureKey']
280
            );
281
        } catch (\Exception $exception) {
282
            throw new UnknownException($exception->getMessage());
283
        }
284
    }
285
286
    /**
287
     * Confirm the order in PMT
288
     *
289
     * @throws Exception
290
     */
291
    private function confirmPmtOrder()
292
    {
293
        try {
294
            $this->orderClient->confirmOrder($this->pmtOrderId);
295
        } catch (\Exception $exception) {
296
            throw new UnknownException($exception->getMessage());
297
        }
298
    }
299
300
    /**
301
     * Leave the merchant order as it was previously
302
     *
303
     * @throws Exception
304
     */
305
    public function rollbackMerchantOrder()
306
    {
307
        // Do nothing because the order is created only when the purchase was successfully
308
    }
309
310
311
    /**
312
     * Lock the concurrency to prevent duplicated inputs
313
     *
314
     * @param $orderId
315
     * @throws Exception
316
     */
317
    protected function blockConcurrency($orderId)
318
    {
319
        try {
320
            Db::getInstance()->insert('pmt_cart_process', array('id' => $orderId, 'timestamp' => (time())));
321
        } catch (\Exception $exception) {
322
            throw new ConcurrencyException();
323
        }
324
    }
325
326
    /**
327
     * Unlock the concurrency
328
     *
329
     * @throws Exception
330
     */
331
    protected function unblockConcurrency()
332
    {
333
        try {
334
            Db::getInstance()->delete('pmt_cart_process', 'timestamp < ' . (time() - 6));
335
        } catch (\Exception $exception) {
336
            throw new ConcurrencyException();
337
        }
338
    }
339
340
    /**
341
     * Do all the necessary actions to cancel the confirmation process in case of error
342
     * 1. Unblock concurrency
343
     * 2. Save log
344
     *
345
     */
346
    public function cancelProcess()
347
    {
348
        if ($this->merchantOrder) {
349
            $id = (!is_null($this->pmtOrder))?$this->pmtOrder->getId():null;
350
            $this->module->validateOrder(
351
                $this->merchantOrderId,
352
                Configuration::get('PS_OS_ERROR'),
353
                $this->merchantOrder->getOrderTotal(true),
354
                $this->module->displayName,
355
                'pmtOrderId: ' . $id,
356
                null,
357
                null,
358
                false,
359
                $this->config['secureKey']
360
            );
361
        }
362
363
        $debug = debug_backtrace();
364
        $method = $debug[1]['function'];
365
        $line = $debug[1]['line'];
366
        $this->saveLog(array(
367
            'pmtCode' => $this->statusCode,
368
            'pmtMessage' => $this->errorMessage,
369
            'pmtMessageDetail' => $this->errorDetail,
370
            'pmtOrderId' => $this->pmtOrderId,
371
            'merchantOrderId' => $this->merchantOrderId,
372
            'method' => $method,
373
            'line' => $line,
374
        ));
375
        return $this->finishProcess(true);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(true) targeting PaylaterNotifyModuleFron...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...
376
    }
377
378
    /**
379
     * Redirect the request to the e-commerce or show the output in json
380
     *
381
     * @param bool $error
382
     */
383
    public function finishProcess($error = true)
384
    {
385
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
386
            $this->jsonResponse->printResponse();
387
        }
388
389
        $parameters = array(
390
            'id_cart' => $this->merchantOrderId,
391
            'key' => $this->config['secureKey'],
392
            'id_module' => $this->module->id,
393
            'id_order' => ($this->pmtOrder)?$this->pmtOrder->getId(): null,
394
        );
395
        $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
396
        return $this->redirect($error, $url, $parameters);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->redirect($error, $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...
397
    }
398
}
399