Passed
Pull Request — master (#42)
by Raúl
02:30
created

PagantisNotifyModuleFrontController   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 1
Metric Value
eloc 171
c 7
b 0
f 1
dl 0
loc 395
rs 8.48
wmc 49

How to fix   Complexity   

Complex Class

Complex classes like PagantisNotifyModuleFrontController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PagantisNotifyModuleFrontController, and based on these observations, apply Extract Interface, too.

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