Passed
Pull Request — master (#16)
by
unknown
03:50
created

Index   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 513
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 61
eloc 232
dl 0
loc 513
rs 3.52
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A checkConcurrency() 0 6 1
A checkOrderStatus() 0 10 3
A checkPmtStatus() 0 10 3
A getMagentoOrderId() 0 13 2
A getMerchantOrder() 0 7 2
A __construct() 0 22 1
A validateAmount() 0 6 2
A getQuoteId() 0 5 2
A insertLog() 0 15 3
A rollbackMerchantOrder() 0 8 2
A checkMerchantOrderStatus() 0 5 2
A checkDbLogTable() 0 18 3
A checkDbTable() 0 11 2
A saveOrder() 0 13 3
A getPmtOrderId() 0 14 3
A updateBdInfo() 0 14 2
A unblockConcurrency() 0 13 4
A processMerchantOrder() 0 7 2
A getPmtOrder() 0 7 2
A blockConcurrency() 0 9 2
A confirmPmtOrder() 0 14 2
B execute() 0 44 5
B getRedirectUrl() 0 36 8

How to fix   Complexity   

Complex Class

Complex classes like Index 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 Index, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace DigitalOrigin\Pmt\Controller\Notify;
4
5
use Magento\Quote\Model\QuoteManagement;
6
use Magento\Quote\Api\Data\PaymentInterface;
7
use Magento\Sales\Api\Data\OrderInterface;
8
use Magento\Sales\Api\OrderRepositoryInterface;
9
use Magento\Quote\Model\Quote;
10
use Magento\Quote\Model\QuoteRepository;
11
use Magento\Framework\App\Action\Context;
12
use Magento\Framework\App\Action\Action;
13
use PagaMasTarde\ModuleUtils\Exception\MerchantOrderNotFoundException;
14
use PagaMasTarde\OrdersApiClient\Client;
15
use DigitalOrigin\Pmt\Helper\Config;
16
use DigitalOrigin\Pmt\Helper\ExtraConfig;
17
use Magento\Framework\App\ResourceConnection;
18
use Magento\Checkout\Model\Session;
19
use Magento\Framework\DB\Ddl\Table;
20
use PagaMasTarde\ModuleUtils\Exception\AmountMismatchException;
21
use PagaMasTarde\ModuleUtils\Exception\ConcurrencyException;
22
use PagaMasTarde\ModuleUtils\Exception\NoIdentificationException;
23
use PagaMasTarde\ModuleUtils\Exception\OrderNotFoundException;
24
use PagaMasTarde\ModuleUtils\Exception\QuoteNotFoundException;
25
use PagaMasTarde\ModuleUtils\Exception\UnknownException;
26
use PagaMasTarde\ModuleUtils\Exception\WrongStatusException;
27
use PagaMasTarde\ModuleUtils\Model\Response\JsonSuccessResponse;
28
use PagaMasTarde\ModuleUtils\Model\Response\JsonExceptionResponse;
29
use PagaMasTarde\ModuleUtils\Exception\AlreadyProcessedException;
30
use PagaMasTarde\ModuleUtils\Model\Log\LogEntry;
31
32
/**
33
 * Class Index
34
 * @package DigitalOrigin\Pmt\Controller\Notify
35
 */
36
class Index extends Action
37
{
38
    /** Orders tablename */
39
    const ORDERS_TABLE = 'cart_process';
40
41
    /** Concurrency tablename */
42
    const CONCURRENCY_TABLE = 'pmt_orders';
43
44
    /** Concurrency tablename */
45
    const LOGS_TABLE = 'pmt_logs';
46
47
    /** Payment code */
48
    const PAYMENT_METHOD = 'paylater';
49
50
    /**
51
     * EXCEPTION RESPONSES
52
     */
53
    const CPO_ERR_MSG = 'Order not confirmed';
54
    const CPO_OK_MSG = 'Order confirmed';
55
56
    /** @var QuoteManagement */
57
    protected $quoteManagement;
58
59
    /** @var PaymentInterface $paymentInterface */
60
    protected $paymentInterface;
61
62
    /** @var OrderRepositoryInterface $orderRepositoryInterface */
63
    protected $orderRepositoryInterface;
64
65
    /** @var Quote $quote */
66
    protected $quote;
67
68
    /** @var QuoteRepository $quoteRepository */
69
    protected $quoteRepository;
70
71
    /** @var mixed $config */
72
    protected $config;
73
74
    /** @var mixed $quoteId */
75
    protected $quoteId;
76
77
    /** @var array $notifyResult */
78
    protected $notifyResult;
79
80
    /** @var mixed $magentoOrderId */
81
    protected $magentoOrderId;
82
83
    /** @var mixed $pmtOrder */
84
    protected $pmtOrder;
85
86
    /** @var ResourceConnection $dbObject */
87
    protected $dbObject;
88
89
    /** @var Session $checkoutSession */
90
    protected $checkoutSession;
91
92
    /** @var Client $orderClient */
93
    protected $orderClient;
94
95
    /** @var mixed $pmtOrderId */
96
    protected $pmtOrderId;
97
98
    /** @var  OrderInterface $magentoOrder */
99
    protected $magentoOrder;
100
101
    /** @var ExtraConfig $extraConfig */
102
    protected $extraConfig;
103
104
    /**
105
     * Index constructor.
106
     *
107
     * @param Context                  $context
108
     * @param Quote                    $quote
109
     * @param QuoteManagement          $quoteManagement
110
     * @param PaymentInterface         $paymentInterface
111
     * @param Config                   $config
112
     * @param QuoteRepository          $quoteRepository
113
     * @param OrderRepositoryInterface $orderRepositoryInterface
114
     * @param ResourceConnection       $dbObject
115
     * @param Session                  $checkoutSession
116
     * @param ExtraConfig              $extraConfig
117
     */
118
    public function __construct(
119
        Context $context,
120
        Quote $quote,
121
        QuoteManagement $quoteManagement,
122
        PaymentInterface $paymentInterface,
123
        Config $config,
124
        QuoteRepository $quoteRepository,
125
        OrderRepositoryInterface $orderRepositoryInterface,
126
        ResourceConnection $dbObject,
127
        Session $checkoutSession,
128
        ExtraConfig $extraConfig
129
    ) {
130
        parent::__construct($context);
131
        $this->quote = $quote;
132
        $this->quoteManagement = $quoteManagement;
133
        $this->paymentInterface = $paymentInterface;
134
        $this->extraConfig = $extraConfig->getExtraConfig();
0 ignored issues
show
Documentation Bug introduced by
It seems like $extraConfig->getExtraConfig() of type array or array is incompatible with the declared type DigitalOrigin\Pmt\Helper\ExtraConfig of property $extraConfig.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
135
        $this->config = $config->getConfig();
136
        $this->quoteRepository = $quoteRepository;
137
        $this->orderRepositoryInterface = $orderRepositoryInterface;
138
        $this->dbObject = $dbObject;
139
        $this->checkoutSession = $checkoutSession;
140
    }
141
142
    //MAIN FUNCTION
143
    public function execute()
144
    {
145
        try {
146
            $this->checkConcurrency();
147
            $this->getMerchantOrder();
148
            $this->getPmtOrderId();
149
            $this->getPmtOrder();
150
            $this->checkOrderStatus();
151
            $this->checkMerchantOrderStatus();
152
            $this->validateAmount();
153
            $this->processMerchantOrder();
154
        } catch (\Exception $exception) {
155
            $jsonResponse = new JsonExceptionResponse();
156
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
157
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
158
            $jsonResponse->setException($exception);
159
            $response = $jsonResponse->toJson();
160
            $this->insertLog($exception);
161
        }
162
163
        try {
164
            if (!isset($response)) {
165
                $this->confirmPmtOrder();
166
                $jsonResponse = new JsonSuccessResponse();
167
                $jsonResponse->setMerchantOrderId($this->magentoOrderId);
168
                $jsonResponse->setPmtOrderId($this->pmtOrderId);
169
            }
170
        } catch (\Exception $exception) {
171
            $this->rollbackMerchantOrder();
172
            $jsonResponse = new JsonExceptionResponse();
173
            $jsonResponse->setMerchantOrderId($this->magentoOrderId);
174
            $jsonResponse->setPmtOrderId($this->pmtOrderId);
175
            $jsonResponse->setException($exception);
176
            $jsonResponse->toJson();
177
            $this->insertLog($exception);
178
        }
179
180
        $this->unblockConcurrency(true);
181
182
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
183
            $jsonResponse->printResponse();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $jsonResponse does not seem to be defined for all execution paths leading up to this point.
Loading history...
184
        } else {
185
            $returnUrl = $this->getRedirectUrl();
186
            $this->_redirect($returnUrl);
187
        }
188
    }
189
190
    /**
191
     * COMMON FUNCTIONS
192
     */
193
194
    private function checkConcurrency()
195
    {
196
        $this->getQuoteId();
197
        $this->checkDbTable();
198
        $this->unblockConcurrency();
199
        $this->blockConcurrency();
200
    }
201
202
    private function getMerchantOrder()
203
    {
204
        try {
205
            /** @var Quote quote */
206
            $this->quote = $this->quoteRepository->get($this->quoteId);
207
        } catch (\Exception $e) {
208
            throw new MerchantOrderNotFoundException();
209
        }
210
    }
211
212
    private function getPmtOrderId()
213
    {
214
        try {
215
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
216
            $dbConnection     = $this->dbObject->getConnection();
217
            $tableName        = $this->dbObject->getTableName(self::ORDERS_TABLE);
218
            $query            = "select order_id from $tableName where id='$this->quoteId'";
219
            $queryResult      = $dbConnection->fetchRow($query);
220
            $this->pmtOrderId = $queryResult['order_id'];
221
            if ($this->pmtOrderId == '') {
222
                throw new NoIdentificationException();
223
            }
224
        } catch (\Exception $e) {
225
            throw new UnknownException($e->getMessage());
226
        }
227
    }
228
229
    private function getPmtOrder()
230
    {
231
        try {
232
            $this->orderClient = new Client($this->config['pmt_public_key'], $this->config['pmt_private_key']);
233
            $this->pmtOrder = $this->orderClient->getOrder($this->pmtOrderId);
234
        } catch (\Exception $e) {
235
            throw new OrderNotFoundException();
236
        }
237
    }
238
239
    private function checkOrderStatus()
240
    {
241
        try {
242
            $this->checkPmtStatus(array('AUTHORIZED'));
243
        } catch (\Exception $e) {
244
            $this->getMagentoOrderId();
245
            if ($this->magentoOrderId!='') {
246
                throw new AlreadyProcessedException();
247
            } else {
248
                throw new WrongStatusException($this->pmtOrder->getStatus());
249
            }
250
        }
251
    }
252
253
    private function checkMerchantOrderStatus()
254
    {
255
        if ($this->quote->getIsActive()=='0') {
256
            $this->getMagentoOrderId();
257
            throw new AlreadyProcessedException();
258
        }
259
    }
260
261
    private function validateAmount()
262
    {
263
        $pmtAmount = $this->pmtOrder->getShoppingCart()->getTotalAmount();
264
        $merchantAmount = intval(strval(100 * $this->quote->getGrandTotal()));
265
        if ($pmtAmount != $merchantAmount) {
266
            throw new AmountMismatchException($pmtAmount, $merchantAmount);
267
        }
268
    }
269
270
    private function processMerchantOrder()
271
    {
272
        try {
273
            $this->saveOrder();
274
            $this->updateBdInfo();
275
        } catch (\Exception $e) {
276
            throw new UnknownException($e->getMessage());
277
        }
278
    }
279
280
    private function confirmPmtOrder()
281
    {
282
        try {
283
            $this->pmtOrder = $this->orderClient->confirmOrder($this->pmtOrderId);
284
        } catch (\Exception $e) {
285
            throw new UnknownException($e->getMessage());
286
        }
287
288
        $jsonResponse = new JsonSuccessResponse();
289
        $jsonResponse->setStatusCode(200);
290
        $jsonResponse->setMerchantOrderId($this->magentoOrderId);
291
        $jsonResponse->setPmtOrderId($this->pmtOrderId);
292
        $jsonResponse->setResult(self::CPO_OK_MSG);
293
        return $jsonResponse->toJson();
294
    }
295
296
    /**
297
     * UTILS FUNCTIONS
298
     */
299
300
    /** STEP 1 CC - Check concurrency
301
     * @throws QuoteNotFoundException
302
     */
303
    private function getQuoteId()
304
    {
305
        $this->quoteId = $this->getRequest()->getParam('quoteId');
306
        if ($this->quoteId == '') {
307
            throw new QuoteNotFoundException();
308
        }
309
    }
310
311
    /**
312
     * @return \Zend_Db_Statement_Interface
313
     * @throws UnknownException
314
     */
315
    private function checkDbTable()
316
    {
317
        try {
318
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
319
            $dbConnection = $this->dbObject->getConnection();
320
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
321
            $query        = "CREATE TABLE IF NOT EXISTS $tableName(`id` int not null,`timestamp` int not null,PRIMARY KEY (`id`))";
322
323
            return $dbConnection->query($query);
324
        } catch (\Exception $e) {
325
            throw new UnknownException($e->getMessage());
326
        }
327
    }
328
329
    /**
330
     * @return void|\Zend_Db_Statement_Interface
331
     * @throws UnknownException
332
     */
333
    private function checkDbLogTable()
334
    {
335
        try {
336
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
337
            $dbConnection = $this->dbObject->getConnection();
338
            $tableName = $this->dbObject->getTableName(self::LOGS_TABLE);
339
            if (!$dbConnection->isTableExists($tableName)) {
340
                $table = $dbConnection
341
                    ->newTable($tableName)
342
                    ->addColumn('id', Table::TYPE_SMALLINT, null, array('nullable'=>false, 'auto_increment'=>true, 'primary'=>true))
343
                    ->addColumn('log', Table::TYPE_TEXT, null, array('nullable'=>false))
344
                    ->addColumn('createdAt', Table::TYPE_TIMESTAMP, null, array('nullable'=>false, 'default'=>Table::TIMESTAMP_INIT));
345
                return $dbConnection->createTable($table);
346
            }
347
348
            return;
349
        } catch (\Exception $e) {
350
            throw new UnknownException($e->getMessage());
351
        }
352
    }
353
354
    /**
355
     * @param bool $mode
356
     *
357
     * @throws \Exception
358
     */
359
    private function unblockConcurrency($mode = false)
360
    {
361
        try {
362
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
363
            $dbConnection = $this->dbObject->getConnection();
364
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
365
            if ($mode == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
366
                $dbConnection->delete($tableName, "timestamp<".(time() - 5));
367
            } elseif ($this->quoteId!='') {
368
                $dbConnection->delete($tableName, "id=".$this->quoteId);
369
            }
370
        } catch (Exception $exception) {
0 ignored issues
show
Bug introduced by
The type DigitalOrigin\Pmt\Controller\Notify\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
371
            throw new ConcurrencyException();
372
        }
373
    }
374
375
    /**
376
     * @throws \Exception
377
     */
378
    private function blockConcurrency()
379
    {
380
        try {
381
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
382
            $dbConnection = $this->dbObject->getConnection();
383
            $tableName    = $this->dbObject->getTableName(self::CONCURRENCY_TABLE);
384
            $dbConnection->insert($tableName, array('id'=>$this->quoteId, 'timestamp'=>time()));
385
        } catch (Exception $exception) {
386
            throw new ConcurrencyException();
387
        }
388
    }
389
390
    /** STEP 2 GMO - Get Merchant Order */
391
    /** STEP 3 GPOI - Get Pmt OrderId */
392
    /** STEP 4 GPO - Get Pmt Order */
393
    /** STEP 5 COS - Check Order Status */
394
    /**
395
     * @param $statusArray
396
     *
397
     * @throws \Exception
398
     */
399
    private function checkPmtStatus($statusArray)
400
    {
401
        $pmtStatus = array();
402
        foreach ($statusArray as $status) {
403
            $pmtStatus[] = constant("\PagaMasTarde\OrdersApiClient\Model\Order::STATUS_$status");
404
        }
405
406
        $payed = in_array($this->pmtOrder->getStatus(), $pmtStatus);
407
        if (!$payed) {
408
            throw new WrongStatusException($this->pmtOrder->getStatus());
409
        }
410
    }
411
412
    /** STEP 6 CMOS - Check Merchant Order Status */
413
    /**
414
     * @throws \Exception
415
     */
416
    private function getMagentoOrderId()
417
    {
418
        try {
419
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
420
            $dbConnection = $this->dbObject->getConnection();
421
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
422
            $pmtOrderId   = $this->pmtOrderId;
423
424
            $query        = "select mg_order_id from $tableName where id='$this->quoteId' and order_id='$pmtOrderId'";
425
            $queryResult  = $dbConnection->fetchRow($query);
426
            $this->magentoOrderId = $queryResult['mg_order_id'];
427
        } catch (\Exception $e) {
428
            throw new UnknownException($e->getMessage());
429
        }
430
    }
431
432
    /** STEP 7 VA - Validate Amount */
433
    /** STEP 8 PMO - Process Merchant Order */
434
    /**
435
     * @throws UnknownException
436
     */
437
    private function saveOrder()
438
    {
439
        try {
440
            $this->paymentInterface->setMethod(self::PAYMENT_METHOD);
441
            $this->magentoOrderId = $this->quoteManagement->placeOrder($this->quoteId, $this->paymentInterface);
442
            /** @var \Magento\Sales\Api\Data\OrderInterface magentoOrder */
443
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
444
445
            if ($this->magentoOrderId == '') {
446
                throw new UnknownException('Order can not be saved');
447
            }
448
        } catch (\Exception $e) {
449
            throw new UnknownException($e->getMessage());
450
        }
451
    }
452
453
    /**
454
     * @throws UnknownException
455
     */
456
    private function updateBdInfo()
457
    {
458
        try {
459
            /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
460
            $dbConnection = $this->dbObject->getConnection();
461
            $tableName    = $this->dbObject->getTableName(self::ORDERS_TABLE);
462
            $pmtOrderId   = $this->pmtOrder->getId();
463
            $dbConnection->update(
464
                $tableName,
465
                array('mg_order_id' => $this->magentoOrderId),
466
                "order_id='$pmtOrderId' and id='$this->quoteId'"
467
            );
468
        } catch (\Exception $e) {
469
            throw new UnknownException($e->getMessage());
470
        }
471
    }
472
473
    /** STEP 9 CPO - Confirmation Pmt Order */
474
    /**
475
     * @throws UnknownException
476
     */
477
    private function rollbackMerchantOrder()
478
    {
479
        try {
480
            $this->magentoOrder->setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT, true);
0 ignored issues
show
Unused Code introduced by
The call to Magento\Sales\Api\Data\OrderInterface::setState() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

480
            $this->magentoOrder->/** @scrutinizer ignore-call */ 
481
                                 setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT, true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
481
            $this->magentoOrder->setStatus(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT);
482
            $this->magentoOrder->save();
483
        } catch (\Exception $e) {
484
            throw new UnknownException($e->getMessage());
485
        }
486
    }
487
488
    /**
489
     * @return string
490
     */
491
    private function getRedirectUrl()
492
    {
493
        //$returnUrl = 'checkout/#payment';
494
        $returnUrl = $this->_url->getUrl('checkout', ['_fragment' => 'payment']);
495
        if ($this->magentoOrderId!='') {
496
            /** @var Order $this->magentoOrder */
497
            $this->magentoOrder = $this->orderRepositoryInterface->get($this->magentoOrderId);
498
            if (!$this->_objectManager->get(\Magento\Checkout\Model\Session\SuccessValidator::class)->isValid()) {
499
                $this->checkoutSession
500
                    ->setLastOrderId($this->magentoOrderId)
501
                    ->setLastRealOrderId($this->magentoOrder->getIncrementId())
502
                    ->setLastQuoteId($this->quoteId)
503
                    ->setLastSuccessQuoteId($this->quoteId)
504
                    ->setLastOrderStatus($this->magentoOrder->getStatus());
505
            }
506
507
            //Magento status flow => https://docs.magento.com/m2/ce/user_guide/sales/order-status-workflow.html
508
            //Order Workflow => https://docs.magento.com/m2/ce/user_guide/sales/order-workflow.html
509
            $orderStatus    = strtolower($this->magentoOrder->getStatus());
510
            $acceptedStatus = array('processing', 'completed');
511
            if (in_array($orderStatus, $acceptedStatus)) {
512
                if (isset($this->extraConfig['PMT_OK_URL']) &&  $this->extraConfig['PMT_OK_URL']!= '') {
513
                    $returnUrl = $this->extraConfig['PMT_OK_URL'];
514
                } else {
515
                    $returnUrl = 'checkout/onepage/success';
516
                }
517
            } else {
518
                if (isset($this->extraConfig['PMT_KO_URL']) && $this->extraConfig['PMT_KO_URL'] != '') {
519
                    $returnUrl = $this->extraConfig['PMT_KO_URL'];
520
                } else {
521
                    //$returnUrl = 'checkout/#payment';
522
                    $returnUrl = $this->_url->getUrl('checkout', ['_fragment' => 'payment']);
523
                }
524
            }
525
        }
526
        return $returnUrl;
527
    }
528
529
    /**
530
     * @param $exceptionMessage
531
     *
532
     * @throws UnknownException
533
     */
534
    private function insertLog($exceptionMessage)
535
    {
536
        try {
537
            if ($exceptionMessage instanceof \Exception) {
538
                $this->checkDbLogTable();
539
                $logEntry = new LogEntry();
540
                $logEntryJson = $logEntry->error($exceptionMessage)->toJson();
541
542
                /** @var \Magento\Framework\DB\Adapter\AdapterInterface $dbConnection */
543
                $dbConnection = $this->dbObject->getConnection();
544
                $tableName    = $this->dbObject->getTableName(self::LOGS_TABLE);
545
                $dbConnection->insert($tableName, array('log' => $logEntryJson));
546
            }
547
        } catch (\Exception $e) {
548
            throw new UnknownException($e->getMessage());
549
        }
550
    }
551
}
552