Passed
Pull Request — master (#35)
by Raúl
03:04
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
0 ignored issues
show
Bug introduced by
The type AbstractController 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...
30
{
31
    /**
32
     * @var bool $error
33
     */
34
    protected $error;
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
            $response = $this->jsonResponse->toJson();
91
            return $this->cancelProcess($response);
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
            $response = $this->jsonResponse->toJson();
106
            return $this->cancelProcess($response);
107
        }
108
109
        try {
110
            $this->unblockConcurrency();
111
        } catch (\Exception $exception) {
112
            // Do nothing
113
        }
114
115
        return $this->finishProcess(false);
116
    }
117
118
    /**
119
     * Find and init variables needed to process payment
120
     *
121
     * @throws Exception
122
     */
123
    public function prepareVariables()
124
    {
125
        $this->error = false;
126
        $callbackOkUrl = $this->context->link->getPageLink(
127
            'order-confirmation',
128
            null,
129
            null
130
        );
131
        $callbackKoUrl = $this->context->link->getPageLink(
132
            'order',
133
            null,
134
            null,
135
            array('step'=>3)
136
        );
137
        try {
138
            $this->config = array(
139
                'urlOK' => (getenv('PAGANTIS_URL_OK') !== '') ? getenv('PAGANTIS_URL_OK') : $callbackOkUrl,
140
                'urlKO' => (getenv('PAGANTIS_URL_KO') !== '') ? getenv('PAGANTIS_URL_KO') : $callbackKoUrl,
141
                '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...
142
                'privateKey' => Configuration::get('pagantis_private_key'),
143
                '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...
144
            );
145
        } catch (\Exception $exception) {
146
            throw new ConfigurationNotFoundException();
147
        }
148
149
        $this->merchantOrderId = Tools::getValue('id_cart');
150
        if ($this->merchantOrderId == '') {
151
            throw new QuoteNotFoundException();
152
        }
153
154
155
        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...
156
            // This exception is only for Prestashop
157
            throw new UnknownException('Module may not be enabled');
158
        }
159
    }
160
161
    /**
162
     * Check the concurrency of the purchase
163
     *
164
     * @throws Exception
165
     */
166
    public function checkConcurrency()
167
    {
168
        $this->prepareVariables();
169
        $this->unblockConcurrency();
170
        $this->blockConcurrency($this->merchantOrderId);
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)((string) (100 * $this->merchantOrder->getOrderTotal(true)));
262
        if ($totalAmount != $merchantAmount) {
263
            $this->error = 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
                array('transaction_id' => $this->pagantisOrderId),
283
                null,
284
                false,
285
                $this->config['secureKey']
286
            );
287
        } catch (\Exception $exception) {
288
            throw new UnknownException($exception->getMessage());
289
        }
290
    }
291
292
    /**
293
     * Confirm the order in PAGANTIS
294
     *
295
     * @throws Exception
296
     */
297
    private function confirmPagantisOrder()
298
    {
299
        try {
300
            $this->orderClient->confirmOrder($this->pagantisOrderId);
301
        } catch (\Exception $exception) {
302
            throw new UnknownException($exception->getMessage());
303
        }
304
    }
305
306
    /**
307
     * Leave the merchant order as it was previously
308
     *
309
     * @throws Exception
310
     */
311
    public function rollbackMerchantOrder()
312
    {
313
        // Do nothing because the order is created only when the purchase was successfully
314
    }
315
316
317
    /**
318
     * Lock the concurrency to prevent duplicated inputs
319
     *
320
     * @param $orderId
321
     * @throws Exception
322
     */
323
    protected function blockConcurrency($orderId)
324
    {
325
        try {
326
            Db::getInstance()->insert('pagantis_cart_process', array('id' => $orderId, 'timestamp' => (time())));
327
        } catch (\Exception $exception) {
328
            throw new ConcurrencyException();
329
        }
330
    }
331
332
    /**
333
     * Unlock the concurrency
334
     *
335
     * @throws Exception
336
     */
337
    protected function unblockConcurrency()
338
    {
339
        try {
340
            Db::getInstance()->delete('pagantis_cart_process', 'timestamp < ' . (time() - 6));
341
        } catch (\Exception $exception) {
342
            throw new ConcurrencyException();
343
        }
344
    }
345
346
    /**
347
     * Do all the necessary actions to cancel the confirmation process in case of error
348
     * 1. Unblock concurrency
349
     * 2. Save log
350
     *
351
     * @param String|null $response Response as json
352
     *
353
     */
354
    public function cancelProcess($response = null)
355
    {
356
        if ($this->merchantOrder && $this->error === true) {
357
            sleep(5);
358
            $id = (!is_null($this->pagantisOrder))?$this->pagantisOrder->getId():null;
359
            $this->module->validateOrder(
360
                $this->merchantOrderId,
361
                Configuration::get('PS_OS_ERROR'),
362
                $this->merchantOrder->getOrderTotal(true),
363
                $this->module->displayName,
364
                'pagantisOrderId: ' . $id,
365
                null,
366
                null,
367
                false,
368
                $this->config['secureKey']
369
            );
370
        }
371
372
        $debug = debug_backtrace();
373
        $method = $debug[1]['function'];
374
        $line = $debug[1]['line'];
375
        $this->saveLog(array(
376
            'message' => $response,
377
            'method' => $method,
378
            'file' => __FILE__,
379
            'line' => $line,
380
            'code' => 200
381
        ));
382
        return $this->finishProcess(true);
383
    }
384
385
    /**
386
     * Redirect the request to the e-commerce or show the output in json
387
     *
388
     * @param bool $error
389
     */
390
    public function finishProcess($error = true)
391
    {
392
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
393
            $this->jsonResponse->printResponse();
394
        }
395
396
        $parameters = array(
397
            'id_cart' => $this->merchantOrderId,
398
            'key' => $this->config['secureKey'],
399
            'id_module' => $this->module->id,
400
            'id_order' => ($this->pagantisOrder)?$this->pagantisOrder->getId(): null,
401
        );
402
        $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
403
        return $this->redirect($url, $parameters);
404
    }
405
}
406