Completed
Push — master ( 7cc2c2...10410a )
by Cesar
11s
created

PaylaterNotifyModuleFrontController::postProcess()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 45
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

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