Passed
Pull Request — master (#246)
by
unknown
04:09 queued 01:28
created

SnappPay   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 2
Metric Value
eloc 199
c 6
b 1
f 2
dl 0
loc 432
rs 8.8
wmc 45

15 Methods

Rating   Name   Duplication   Size   Complexity  
A verify() 0 28 3
A normalizerAmount() 0 3 2
A oauth() 0 27 2
A __construct() 0 6 1
A eligible() 0 22 3
B purchase() 0 60 6
A pay() 0 5 1
B normalizerCartList() 0 21 7
A setPaymentUrl() 0 3 1
B update() 0 47 6
A status() 0 28 3
A revert() 0 28 3
A settle() 0 28 3
A getPaymentUrl() 0 3 1
A cancel() 0 28 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
4
namespace Shetabit\Multipay\Drivers\SnappPay;
5
6
use GuzzleHttp\Client;
7
use GuzzleHttp\RequestOptions;
8
use Shetabit\Multipay\Abstracts\Driver;
9
use Shetabit\Multipay\Contracts\ReceiptInterface;
10
use Shetabit\Multipay\Exceptions\InvalidPaymentException;
11
use Shetabit\Multipay\Exceptions\PurchaseFailedException;
12
use Shetabit\Multipay\Invoice;
13
use Shetabit\Multipay\Receipt;
14
use Shetabit\Multipay\RedirectionForm;
15
16
class SnappPay extends Driver
17
{
18
    const VERSION = '1.8';
19
    const RELEASE_DATE = '2023-01-08';
20
21
    const OAUTH_URL = '/api/online/v1/oauth/token';
22
    const ELIGIBLE_URL = '/api/online/offer/v1/eligible';
23
    const TOKEN_URL = '/api/online/payment/v1/token';
24
    const VERIFY_URL = '/api/online/payment/v1/verify';
25
    const SETTLE_URL = '/api/online/payment/v1/settle';
26
    const REVERT_URL = '/api/online/payment/v1/revert';
27
    const STATUS_URL = '/api/online/payment/v1/status';
28
    const CANCEL_URL = '/api/online/payment/v1/cancel';
29
    const UPDATE_URL = '/api/online/payment/v1/update';
30
31
    /**
32
     * SnappPay Client.
33
     *
34
     * @var Client
35
     */
36
    protected $client;
37
38
    /**
39
     * Invoice
40
     *
41
     * @var Invoice
42
     */
43
    protected $invoice;
44
45
    /**
46
     * Driver settings
47
     *
48
     * @var object
49
     */
50
    protected $settings;
51
    /**
52
     * SnappPay Oauth Data
53
     *
54
     * @var string
55
     */
56
    protected $oauthToken;
57
58
    /**
59
     * SnappPay payment url
60
     *
61
     * @var string
62
     */
63
    protected $paymentUrl;
64
65
    /**
66
     * SnappPay constructor.
67
     * Construct the class with the relevant settings.
68
     */
69
    public function __construct(Invoice $invoice, $settings)
70
    {
71
        $this->invoice($invoice);
72
        $this->settings = (object) $settings;
73
        $this->client = new Client();
74
        $this->oauthToken = $this->oauth();
75
    }
76
77
    /**
78
     * @throws PurchaseFailedException
79
     */
80
    public function purchase(): string
81
    {
82
        $phone = $this->invoice->getDetail('phone')
83
            ?? $this->invoice->getDetail('cellphone')
84
            ?? $this->invoice->getDetail('mobile');
85
86
        // convert to format +98 901 XXX XXXX
87
        $phone = preg_replace('/^0/', '+98', $phone);
88
89
        $data = [
90
            'amount' => $this->normalizerAmount($this->invoice->getAmount()),
0 ignored issues
show
Bug introduced by
It seems like $this->invoice->getAmount() can also be of type double; however, parameter $amount of Shetabit\Multipay\Driver...Pay::normalizerAmount() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

90
            'amount' => $this->normalizerAmount(/** @scrutinizer ignore-type */ $this->invoice->getAmount()),
Loading history...
91
            'mobile' => $phone,
92
            'paymentMethodTypeDto' => 'INSTALLMENT',
93
            'transactionId' => $this->invoice->getUuid(),
94
            'returnURL' => $this->settings->callbackUrl,
95
        ];
96
97
        if (!is_null($discountAmount = $this->invoice->getDetail('discountAmount'))) {
98
            $data['discountAmount'] = $this->normalizerAmount($discountAmount);
0 ignored issues
show
Bug introduced by
$discountAmount of type string is incompatible with the type integer expected by parameter $amount of Shetabit\Multipay\Driver...Pay::normalizerAmount(). ( Ignorable by Annotation )

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

98
            $data['discountAmount'] = $this->normalizerAmount(/** @scrutinizer ignore-type */ $discountAmount);
Loading history...
99
        }
100
101
        if (!is_null($externalSourceAmount = $this->invoice->getDetail('externalSourceAmount'))) {
102
            $data['externalSourceAmount'] = $externalSourceAmount;
103
        }
104
105
        if (is_null($this->invoice->getDetail('cartList'))) {
106
            throw new PurchaseFailedException('"cartList" is required for this driver');
107
        }
108
109
        $data['cartList'] = $this->invoice->getDetail('cartList');
110
111
        $this->normalizerCartList($data);
112
113
        $response = $this
114
            ->client
115
            ->post(
116
                $this->settings->apiPaymentUrl.self::TOKEN_URL,
117
                [
118
                    RequestOptions::BODY => json_encode($data),
119
                    RequestOptions::HEADERS => [
120
                        'Content-Type' => 'application/json',
121
                        'Authorization' => 'Bearer '.$this->oauthToken,
122
                    ],
123
                    RequestOptions::HTTP_ERRORS => false,
124
                ]
125
            );
126
127
        $body = json_decode($response->getBody()->getContents(), true);
128
129
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
130
            // error has happened
131
            $message = $body['errorData']['message'] ?? 'خطا در هنگام درخواست برای پرداخت رخ داده است.';
132
            throw new PurchaseFailedException($message);
133
        }
134
135
        $this->invoice->transactionId($body['response']['paymentToken']);
136
        $this->setPaymentUrl($body['response']['paymentPageUrl']);
137
138
        // return the transaction's id
139
        return $this->invoice->getTransactionId();
140
    }
141
142
    public function pay(): RedirectionForm
143
    {
144
        parse_str(parse_url($this->getPaymentUrl(), PHP_URL_QUERY), $formData);
145
146
        return $this->redirectWithForm($this->getPaymentUrl(), $formData, 'GET');
147
    }
148
149
    /**
150
     * @throws InvalidPaymentException
151
     */
152
    public function verify(): ReceiptInterface
153
    {
154
        $data = [
155
            'paymentToken' => $this->invoice->getTransactionId(),
156
        ];
157
158
        $response = $this
159
            ->client
160
            ->post(
161
                $this->settings->apiPaymentUrl.self::VERIFY_URL,
162
                [
163
                    RequestOptions::BODY => json_encode($data),
164
                    RequestOptions::HEADERS => [
165
                        'Authorization' => 'Bearer '.$this->oauthToken,
166
                    ],
167
                    RequestOptions::HTTP_ERRORS => false,
168
                ]
169
            );
170
171
        $body = json_decode($response->getBody()->getContents(), true);
172
173
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
174
            // error has happened
175
            $message = $body['errorData']['message'] ?? 'خطا در هنگام تایید تراکنش';
176
            throw new PurchaseFailedException($message);
177
        }
178
179
        return (new Receipt('digipay', $body['response']['transactionId']))->detail($body['response']);
180
    }
181
182
    /**
183
     * @throws PurchaseFailedException
184
     */
185
    protected function oauth()
186
    {
187
        $response = $this
188
            ->client
189
            ->post(
190
                $this->settings->apiPaymentUrl.self::OAUTH_URL,
191
                [
192
                    RequestOptions::HEADERS => [
193
                        'Authorization' => 'Basic '.base64_encode("{$this->settings->client_id}:{$this->settings->client_secret}"),
194
                    ],
195
                    RequestOptions::FORM_PARAMS => [
196
                        'grant_type' => 'password',
197
                        'scope' => 'online-merchant',
198
                        'username' => $this->settings->username,
199
                        'password' => $this->settings->password,
200
                    ],
201
                    RequestOptions::HTTP_ERRORS => false,
202
                ]
203
            );
204
205
        if ($response->getStatusCode() != 200) {
206
            throw new PurchaseFailedException('خطا در هنگام احراز هویت.');
207
        }
208
209
        $body = json_decode($response->getBody()->getContents(), true);
210
211
        return $body['access_token'];
212
    }
213
214
    /**
215
     * @throws PurchaseFailedException
216
     */
217
    public function eligible()
218
    {
219
        if (is_null($amount = $this->invoice->getAmount())) {
0 ignored issues
show
introduced by
The condition is_null($amount = $this->invoice->getAmount()) is always false.
Loading history...
220
            throw new PurchaseFailedException('"amount" is required for this method.');
221
        }
222
223
        $response = $this->client->get($this->settings->apiPaymentUrl.self::ELIGIBLE_URL, [
224
            RequestOptions::HEADERS => [
225
                'Authorization' => 'Bearer '.$this->oauthToken,
226
            ],
227
            RequestOptions::QUERY => [
228
                'amount' => $this->normalizerAmount($amount),
0 ignored issues
show
Bug introduced by
It seems like $amount can also be of type double; however, parameter $amount of Shetabit\Multipay\Driver...Pay::normalizerAmount() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

228
                'amount' => $this->normalizerAmount(/** @scrutinizer ignore-type */ $amount),
Loading history...
229
            ],
230
        ]);
231
232
        $body = json_decode($response->getBody()->getContents(), true);
233
234
        if ($response->getStatusCode() != 200) {
235
            throw new InvalidPaymentException('', (int) $response->getStatusCode());
236
        }
237
238
        return $body;
239
    }
240
241
    private function normalizerAmount(int $amount): int
242
    {
243
        return $amount * ($this->settings->currency == 'T' ? 10 : 1);
244
    }
245
246
    private function normalizerCartList(array &$data): void
247
    {
248
        if (isset($data['cartList']['shippingAmount'])) {
249
            $data['cartList'] = [$data['cartList']];
250
        }
251
252
        foreach ($data['cartList'] as &$item) {
253
            if (isset($item['shippingAmount'])) {
254
                $item['shippingAmount'] = $this->normalizerAmount($item['shippingAmount']);
255
            }
256
257
            if (isset($item['taxAmount'])) {
258
                $item['taxAmount'] = $this->normalizerAmount($item['taxAmount']);
259
            }
260
261
            if (isset($item['totalAmount'])) {
262
                $item['totalAmount'] = $this->normalizerAmount($item['totalAmount']);
263
            }
264
265
            foreach ($item['cartItems'] as &$cartItem) {
266
                $cartItem['amount'] = $this->normalizerAmount($cartItem['amount']);
267
            }
268
        }
269
    }
270
271
    public function settle(): array
272
    {
273
        $data = [
274
            'paymentToken' => $this->invoice->getTransactionId(),
275
        ];
276
277
        $response = $this
278
            ->client
279
            ->post(
280
                $this->settings->apiPaymentUrl.self::SETTLE_URL,
281
                [
282
                    RequestOptions::BODY => json_encode($data),
283
                    RequestOptions::HEADERS => [
284
                        'Authorization' => 'Bearer '.$this->oauthToken,
285
                    ],
286
                    RequestOptions::HTTP_ERRORS => false,
287
                ]
288
            );
289
290
        $body = json_decode($response->getBody()->getContents(), true);
291
292
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
293
            // error has happened
294
            $message = $body['errorData']['message'] ?? 'خطا در Settle تراکنش';
295
            throw new PurchaseFailedException($message);
296
        }
297
298
        return $body['response'];
299
    }
300
301
    public function revert()
302
    {
303
        $data = [
304
            'paymentToken' => $this->invoice->getTransactionId(),
305
        ];
306
307
        $response = $this
308
            ->client
309
            ->post(
310
                $this->settings->apiPaymentUrl.self::REVERT_URL,
311
                [
312
                    RequestOptions::BODY => json_encode($data),
313
                    RequestOptions::HEADERS => [
314
                        'Authorization' => 'Bearer '.$this->oauthToken,
315
                    ],
316
                    RequestOptions::HTTP_ERRORS => false,
317
                ]
318
            );
319
320
        $body = json_decode($response->getBody()->getContents(), true);
321
322
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
323
            // error has happened
324
            $message = $body['errorData']['message'] ?? 'خطا در Revert تراکنش';
325
            throw new PurchaseFailedException($message);
326
        }
327
328
        return $body['response'];
329
    }
330
331
    public function status()
332
    {
333
        $data = [
334
            'paymentToken' => $this->invoice->getTransactionId(),
335
        ];
336
337
        $response = $this
338
            ->client
339
            ->get(
340
                $this->settings->apiPaymentUrl.self::STATUS_URL,
341
                [
342
                    RequestOptions::BODY => json_encode($data),
343
                    RequestOptions::HEADERS => [
344
                        'Authorization' => 'Bearer '.$this->oauthToken,
345
                    ],
346
                    RequestOptions::HTTP_ERRORS => false,
347
                ]
348
            );
349
350
        $body = json_decode($response->getBody()->getContents(), true);
351
352
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
353
            // error has happened
354
            $message = $body['errorData']['message'] ?? 'خطا در status تراکنش';
355
            throw new PurchaseFailedException($message);
356
        }
357
358
        return $body['response'];
359
    }
360
361
    public function cancel()
362
    {
363
        $data = [
364
            'paymentToken' => $this->invoice->getTransactionId(),
365
        ];
366
367
        $response = $this
368
            ->client
369
            ->post(
370
                $this->settings->apiPaymentUrl.self::CANCEL_URL,
371
                [
372
                    RequestOptions::BODY => json_encode($data),
373
                    RequestOptions::HEADERS => [
374
                        'Authorization' => 'Bearer '.$this->oauthToken,
375
                    ],
376
                    RequestOptions::HTTP_ERRORS => false,
377
                ]
378
            );
379
380
        $body = json_decode($response->getBody()->getContents(), true);
381
382
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
383
            // error has happened
384
            $message = $body['errorData']['message'] ?? 'خطا در Cancel تراکنش';
385
            throw new PurchaseFailedException($message);
386
        }
387
388
        return $body['response'];
389
    }
390
391
    public function update()
392
    {
393
        $data = [
394
            'amount' => $this->normalizerAmount($this->invoice->getAmount()),
0 ignored issues
show
Bug introduced by
It seems like $this->invoice->getAmount() can also be of type double; however, parameter $amount of Shetabit\Multipay\Driver...Pay::normalizerAmount() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

394
            'amount' => $this->normalizerAmount(/** @scrutinizer ignore-type */ $this->invoice->getAmount()),
Loading history...
395
            'paymentMethodTypeDto' => 'INSTALLMENT',
396
            'paymentToken' => $this->invoice->getTransactionId(),
397
        ];
398
399
        if (!is_null($discountAmount = $this->invoice->getDetail('discountAmount'))) {
400
            $data['discountAmount'] = $this->normalizerAmount($discountAmount);
0 ignored issues
show
Bug introduced by
$discountAmount of type string is incompatible with the type integer expected by parameter $amount of Shetabit\Multipay\Driver...Pay::normalizerAmount(). ( Ignorable by Annotation )

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

400
            $data['discountAmount'] = $this->normalizerAmount(/** @scrutinizer ignore-type */ $discountAmount);
Loading history...
401
        }
402
403
        if (!is_null($externalSourceAmount = $this->invoice->getDetail('externalSourceAmount'))) {
404
            $data['externalSourceAmount'] = $externalSourceAmount;
405
        }
406
407
        if (is_null($this->invoice->getDetail('cartList'))) {
408
            throw new PurchaseFailedException('"cartList" is required for this driver');
409
        }
410
411
        $data['cartList'] = $this->invoice->getDetail('cartList');
412
413
        $this->normalizerCartList($data);
414
415
        $response = $this
416
            ->client
417
            ->post(
418
                $this->settings->apiPaymentUrl.self::TOKEN_URL,
419
                [
420
                    RequestOptions::BODY => json_encode($data),
421
                    RequestOptions::HEADERS => [
422
                        'Content-Type' => 'application/json',
423
                        'Authorization' => 'Bearer '.$this->oauthToken,
424
                    ],
425
                    RequestOptions::HTTP_ERRORS => false,
426
                ]
427
            );
428
429
        $body = json_decode($response->getBody()->getContents(), true);
430
431
        if ($response->getStatusCode() != 200 || $body['successful'] === false) {
432
            // error has happened
433
            $message = $body['errorData']['message'] ?? 'خطا در بروزرسانی تراکنش رخ داده است.';
434
            throw new PurchaseFailedException($message);
435
        }
436
437
        return $body['response'];
438
    }
439
440
    public function getPaymentUrl(): string
441
    {
442
        return $this->paymentUrl;
443
    }
444
445
    public function setPaymentUrl(string $paymentUrl): void
446
    {
447
        $this->paymentUrl = $paymentUrl;
448
    }
449
}
450