Passed
Pull Request — master (#23)
by Cesar
02:46
created

prepareVariables()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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