Completed
Push — master ( 68d189...af18d0 )
by Songda
02:04 queued 13s
created

Support   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 443
Duplicated Lines 8.13 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 0
Metric Value
wmc 61
lcom 2
cbo 14
dl 36
loc 443
rs 3.52
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 7 7 1
A __get() 0 4 1
A create() 8 8 3
A getInstance() 0 8 2
A clear() 0 4 1
A requestApi() 0 14 3
A generateSign() 0 30 5
A bchexdec() 0 9 2
B verifySign() 0 32 7
B getSignContent() 0 18 10
A encoding() 0 4 1
A getConfig() 12 12 3
A getBaseUri() 0 4 1
A processingApiResult() 0 20 5
A setHttpOptions() 9 9 3
A getCertSN() 0 22 5
B getRootCertSN() 0 35 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Support 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Support, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Yansongda\Pay\Gateways\Alipay;
4
5
use Yansongda\Pay\Events;
6
use Yansongda\Pay\Exceptions\GatewayException;
7
use Yansongda\Pay\Exceptions\InvalidArgumentException;
8
use Yansongda\Pay\Exceptions\InvalidConfigException;
9
use Yansongda\Pay\Exceptions\InvalidSignException;
10
use Yansongda\Pay\Gateways\Alipay;
11
use Yansongda\Pay\Log;
12
use Yansongda\Supports\Arr;
13
use Yansongda\Supports\Collection;
14
use Yansongda\Supports\Config;
15
use Yansongda\Supports\Str;
16
use Yansongda\Supports\Traits\HasHttpRequest;
17
18
/**
19
 * @author yansongda <[email protected]>
20
 *
21
 * @property string app_id alipay app_id
22
 * @property string ali_public_key
23
 * @property string private_key
24
 * @property array http http options
25
 * @property string mode current mode
26
 * @property array log log options
27
 * @property string pid ali pid
28
 */
29
class Support
30
{
31
    use HasHttpRequest;
32
33
    /**
34
     * Alipay gateway.
35
     *
36
     * @var string
37
     */
38
    protected $baseUri;
39
40
    /**
41
     * Config.
42
     *
43
     * @var Config
44
     */
45
    protected $config;
46
47
    /**
48
     * Instance.
49
     *
50
     * @var Support
51
     */
52
    private static $instance;
53
54
    /**
55
     * Bootstrap.
56
     *
57
     * @author yansongda <[email protected]>
58
     *
59
     * @param Config $config
60
     */
61 View Code Duplication
    private function __construct(Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
    {
63
        $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
64
        $this->config = $config;
65
66
        $this->setHttpOptions();
67
    }
68
69
    /**
70
     * __get.
71
     *
72
     * @author yansongda <[email protected]>
73
     *
74
     * @param $key
75
     *
76
     * @return mixed|Config|null
77
     */
78
    public function __get($key)
79
    {
80
        return $this->getConfig($key);
81
    }
82
83
    /**
84
     * create.
85
     *
86
     * @author yansongda <[email protected]>
87
     *
88
     * @param Config $config
89
     *
90
     * @return Support
91
     */
92 View Code Duplication
    public static function create(Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
93
    {
94
        if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
95
            self::$instance = new self($config);
96
        }
97
98
        return self::$instance;
99
    }
100
101
    /**
102
     * getInstance.
103
     *
104
     * @author yansongda <[email protected]>
105
     *
106
     * @throws InvalidArgumentException
107
     *
108
     * @return Support
109
     */
110
    public static function getInstance()
111
    {
112
        if (is_null(self::$instance)) {
113
            throw new InvalidArgumentException('You Should [Create] First Before Using');
114
        }
115
116
        return self::$instance;
117
    }
118
119
    /**
120
     * clear.
121
     *
122
     * @author yansongda <[email protected]>
123
     */
124
    public function clear()
125
    {
126
        self::$instance = null;
127
    }
128
129
    /**
130
     * Get Alipay API result.
131
     *
132
     * @author yansongda <[email protected]>
133
     *
134
     * @param array $data
135
     *
136
     * @throws GatewayException
137
     * @throws InvalidConfigException
138
     * @throws InvalidSignException
139
     *
140
     * @return Collection
141
     */
142
    public static function requestApi(array $data): Collection
143
    {
144
        Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
145
146
        $data = array_filter($data, function ($value) {
147
            return ('' == $value || is_null($value)) ? false : true;
148
        });
149
150
        $result = json_decode(self::$instance->post('', $data), true);
151
152
        Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
153
154
        return self::processingApiResult($data, $result);
155
    }
156
157
    /**
158
     * Generate sign.
159
     *
160
     * @author yansongda <[email protected]>
161
     *
162
     * @param array $params
163
     *
164
     * @throws InvalidConfigException
165
     *
166
     * @return string
167
     */
168
    public static function generateSign(array $params): string
169
    {
170
        $privateKey = self::$instance->private_key;
171
172
        if (is_null($privateKey)) {
173
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
174
        }
175
176
        if (Str::endsWith($privateKey, '.pem')) {
177
            $privateKey = openssl_pkey_get_private(
178
                Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
179
            );
180
        } else {
181
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
182
                wordwrap($privateKey, 64, "\n", true).
183
                "\n-----END RSA PRIVATE KEY-----";
184
        }
185
186
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
187
188
        $sign = base64_encode($sign);
189
190
        Log::debug('Alipay Generate Sign', [$params, $sign]);
191
192
        if (is_resource($privateKey)) {
193
            openssl_free_key($privateKey);
194
        }
195
196
        return $sign;
197
    }
198
199
    /**
200
     * Verify sign.
201
     *
202
     * @author yansongda <[email protected]>
203
     *
204
     * @param array       $data
205
     * @param bool        $sync
206
     * @param string|null $sign
207
     *
208
     * @throws InvalidConfigException
209
     *
210
     * @return bool
211
     */
212
    public static function verifySign(array $data, $sync = false, $sign = null): bool
213
    {
214
        $publicKey = self::$instance->ali_public_key;
215
216
        if (is_null($publicKey)) {
217
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
218
        }
219
220
        if (Str::endsWith($publicKey, '.crt')) {
221
            $publicKey = file_get_contents($publicKey);
222
        } elseif (Str::endsWith($publicKey, '.pem')) {
223
            $publicKey = openssl_pkey_get_public(
224
                Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
225
            );
226
        } else {
227
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
228
                wordwrap($publicKey, 64, "\n", true).
229
                "\n-----END PUBLIC KEY-----";
230
        }
231
232
        $sign = $sign ?? $data['sign'];
233
234
        $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
235
236
        $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
237
238
        if (is_resource($publicKey)) {
239
            openssl_free_key($publicKey);
240
        }
241
242
        return $isVerify;
243
    }
244
245
    /**
246
     * Get signContent that is to be signed.
247
     *
248
     * @author yansongda <[email protected]>
249
     *
250
     * @param array $data
251
     * @param bool  $verify
252
     *
253
     * @return string
254
     */
255
    public static function getSignContent(array $data, $verify = false): string
256
    {
257
        ksort($data);
258
259
        $stringToBeSigned = '';
260
        foreach ($data as $k => $v) {
261
            if ($verify && 'sign' != $k && 'sign_type' != $k) {
262
                $stringToBeSigned .= $k.'='.$v.'&';
263
            }
264
            if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
265
                $stringToBeSigned .= $k.'='.$v.'&';
266
            }
267
        }
268
269
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
270
271
        return trim($stringToBeSigned, '&');
272
    }
273
274
    /**
275
     * Convert encoding.
276
     *
277
     * @author yansongda <[email protected]>
278
     *
279
     * @param string|array $data
280
     * @param string       $to
281
     * @param string       $from
282
     *
283
     * @return array
284
     */
285
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
286
    {
287
        return Arr::encoding((array) $data, $to, $from);
288
    }
289
290
    /**
291
     * Get service config.
292
     *
293
     * @author yansongda <[email protected]>
294
     *
295
     * @param string|null $key
296
     * @param mixed|null  $default
297
     *
298
     * @return mixed|null
299
     */
300 View Code Duplication
    public function getConfig($key = null, $default = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
301
    {
302
        if (is_null($key)) {
303
            return $this->config->all();
304
        }
305
306
        if ($this->config->has($key)) {
307
            return $this->config[$key];
308
        }
309
310
        return $default;
311
    }
312
313
    /**
314
     * Get Base Uri.
315
     *
316
     * @author yansongda <[email protected]>
317
     *
318
     * @return string
319
     */
320
    public function getBaseUri()
321
    {
322
        return $this->baseUri;
323
    }
324
325
    /**
326
     * processingApiResult.
327
     *
328
     * @author yansongda <[email protected]>
329
     *
330
     * @param $data
331
     * @param $result
332
     *
333
     * @throws GatewayException
334
     * @throws InvalidConfigException
335
     * @throws InvalidSignException
336
     *
337
     * @return Collection
338
     */
339
    protected static function processingApiResult($data, $result): Collection
340
    {
341
        $method = str_replace('.', '_', $data['method']).'_response';
342
343
        if (!isset($result['sign']) || '10000' != $result[$method]['code']) {
344
            throw new GatewayException(
345
                'Get Alipay API Error:'.$result[$method]['msg'].
346
                    (isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''),
347
                $result
348
            );
349
        }
350
351
        if (self::verifySign($result[$method], true, $result['sign'])) {
352
            return new Collection($result[$method]);
353
        }
354
355
        Events::dispatch(new Events\SignFailed('Alipay', '', $result));
356
357
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
358
    }
359
360
    /**
361
     * Set Http options.
362
     *
363
     * @author yansongda <[email protected]>
364
     *
365
     * @return self
366
     */
367 View Code Duplication
    protected function setHttpOptions(): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
    {
369
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
370
            $this->config->forget('http.base_uri');
371
            $this->httpOptions = $this->config->get('http');
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * 生成应用证书SN
379
     * 
380
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
381
     * 
382
     * @param $certPath
383
     * @return string
384
     * @throws /Exception
385
     */
386
    public static function getCertSN($certPath): string
387
    {
388
        if (!is_file($certPath)) {
389
            throw new \Exception('unknown certPath -- [getCertSN]');
390
        }
391
        $x509data = file_get_contents($certPath);
392
        if ($x509data === false) {
393
            throw new \Exception('Alipay CertSN Error -- [getCertSN]');
394
        }
395
        openssl_x509_read($x509data);
396
        $certdata = openssl_x509_parse($x509data);
397
        if (empty($certdata)) {
398
            throw new \Exception('Alipay openssl_x509_parse Error -- [getCertSN]');
399
        }
400
        $issuer_arr = [];
401
        foreach ($certdata['issuer'] as $key => $val) {
402
            $issuer_arr[] = $key . '=' . $val;
403
        }
404
        $issuer = implode(',', array_reverse($issuer_arr));
405
        Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]);
406
        return md5($issuer . $certdata['serialNumber']);
407
    }
408
409
    /**
410
     * 0x转高精度数字
411
     * 
412
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
413
     * 
414
     * @param $hex
415
     * @return int|string
416
     */
417
    private static function bchexdec($hex)
418
    {
419
        $dec = 0;
420
        $len = strlen($hex);
421
        for ($i = 1; $i <= $len; $i++) {
422
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
423
        }
424
        return $dec;
425
    }
426
427
    /**
428
     * 生成支付宝根证书SN
429
     * 
430
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
431
     * 
432
     * @param $certPath
433
     * @return string
434
     * @throws /Exception
435
     */
436
    public static function getRootCertSN($certPath)
437
    {
438
        if (!is_file($certPath)) {
439
            throw new \Exception('unknown certPath -- [getRootCertSN]');
440
        }
441
        $x509data = file_get_contents($certPath);
442
        if ($x509data === false) {
443
            throw new \Exception('Alipay CertSN Error -- [getRootCertSN]');
444
        }
445
        $kCertificateEnd = "-----END CERTIFICATE-----";
446
        $certStrList = explode($kCertificateEnd, $x509data);
447
        $md5_arr = [];
448
        foreach ($certStrList as $one) {
449
            if (!empty(trim($one))) {
450
                $_x509data = $one . $kCertificateEnd;
451
                openssl_x509_read($_x509data);
452
                $_certdata = openssl_x509_parse($_x509data);
453
                if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) {
454
                    $issuer_arr = [];
455
                    foreach ($_certdata['issuer'] as $key => $val) {
456
                        $issuer_arr[] = $key . '=' . $val;
457
                    }
458
                    $_issuer = implode(',', array_reverse($issuer_arr));
459
                    if (strpos($_certdata['serialNumber'], '0x') === 0) {
460
                        $serialNumber = self::bchexdec($_certdata['serialNumber']);
461
                    } else {
462
                        $serialNumber = $_certdata['serialNumber'];
463
                    }
464
                    $md5_arr[] = md5($_issuer . $serialNumber);
465
                    Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]);
466
                }
467
            }
468
        }
469
        return implode('_', $md5_arr);
470
    }
471
}
472