Completed
Pull Request — master (#389)
by
unknown
01:33
created

Support::getSignContent()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 6.9666
c 0
b 0
f 0
cc 12
nc 10
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Yansongda\Pay\Gateways\Alipay;
4
5
use Exception;
6
use Yansongda\Pay\Events;
7
use Yansongda\Pay\Exceptions\GatewayException;
8
use Yansongda\Pay\Exceptions\InvalidArgumentException;
9
use Yansongda\Pay\Exceptions\InvalidConfigException;
10
use Yansongda\Pay\Exceptions\InvalidSignException;
11
use Yansongda\Pay\Gateways\Alipay;
12
use Yansongda\Pay\Log;
13
use Yansongda\Supports\Arr;
14
use Yansongda\Supports\Collection;
15
use Yansongda\Supports\Config;
16
use Yansongda\Supports\Str;
17
use Yansongda\Supports\Traits\HasHttpRequest;
18
19
/**
20
 * @author yansongda <[email protected]>
21
 *
22
 * @property string app_id alipay app_id
23
 * @property string ali_public_key
24
 * @property string private_key
25
 * @property array http http options
26
 * @property string mode current mode
27
 * @property array log log options
28
 * @property string pid ali pid
29
 */
30
class Support
31
{
32
    use HasHttpRequest;
33
34
    /**
35
     * Alipay gateway.
36
     *
37
     * @var string
38
     */
39
    protected $baseUri;
40
41
    /**
42
     * Config.
43
     *
44
     * @var Config
45
     */
46
    protected $config;
47
48
    /**
49
     * Instance.
50
     *
51
     * @var Support
52
     */
53
    private static $instance;
54
55
    /**
56
     * Bootstrap.
57
     *
58
     * @author yansongda <[email protected]>
59
     */
60 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...
61
    {
62
        $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
63
        $this->config = $config;
64
65
        $this->setHttpOptions();
66
    }
67
68
    /**
69
     * __get.
70
     *
71
     * @author yansongda <[email protected]>
72
     *
73
     * @param $key
74
     *
75
     * @return mixed|Config|null
76
     */
77
    public function __get($key)
78
    {
79
        return $this->getConfig($key);
80
    }
81
82
    /**
83
     * create.
84
     *
85
     * @author yansongda <[email protected]>
86
     *
87
     * @return Support
88
     */
89 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...
90
    {
91
        if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
92
            self::$instance = new self($config);
93
        }
94
95
        return self::$instance;
96
    }
97
98
    /**
99
     * getInstance.
100
     *
101
     * @author yansongda <[email protected]>
102
     *
103
     * @throws InvalidArgumentException
104
     *
105
     * @return Support
106
     */
107
    public static function getInstance()
108
    {
109
        if (is_null(self::$instance)) {
110
            throw new InvalidArgumentException('You Should [Create] First Before Using');
111
        }
112
113
        return self::$instance;
114
    }
115
116
    /**
117
     * clear.
118
     *
119
     * @author yansongda <[email protected]>
120
     */
121
    public function clear()
122
    {
123
        self::$instance = null;
124
    }
125
126
    /**
127
     * Get Alipay API result.
128
     *
129
     * @author yansongda <[email protected]>
130
     *
131
     * @throws GatewayException
132
     * @throws InvalidConfigException
133
     * @throws InvalidSignException
134
     */
135
    public static function requestApi(array $data): Collection
136
    {
137
        Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
138
139
        $data = array_filter($data, function ($value) {
140
            return ('' == $value || is_null($value)) ? false : true;
141
        });
142
143
        $result = json_decode(self::$instance->post('', $data), true);
144
145
        Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
146
147
        return self::processingApiResult($data, $result);
148
    }
149
150
    /**
151
     * Generate sign.
152
     *
153
     * @author yansongda <[email protected]>
154
     *
155
     * @throws InvalidConfigException
156
     */
157
    public static function generateSign(array $params): string
158
    {
159
        $privateKey = self::$instance->private_key;
160
161
        if (is_null($privateKey)) {
162
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
163
        }
164
165
        if (Str::endsWith($privateKey, '.pem')) {
166
            $privateKey = openssl_pkey_get_private(
167
                Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
168
            );
169
        } else {
170
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
171
                wordwrap($privateKey, 64, "\n", true).
172
                "\n-----END RSA PRIVATE KEY-----";
173
        }
174
175
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
176
177
        $sign = base64_encode($sign);
178
179
        Log::debug('Alipay Generate Sign', [$params, $sign]);
180
181
        if (is_resource($privateKey)) {
182
            openssl_free_key($privateKey);
183
        }
184
185
        return $sign;
186
    }
187
188
    /**
189
     * Verify sign.
190
     *
191
     * @author yansongda <[email protected]>
192
     *
193
     * @param bool        $sync
194
     * @param string|null $sign
195
     *
196
     * @throws InvalidConfigException
197
     */
198
    public static function verifySign(array $data, $sync = false, $sign = null): bool
199
    {
200
        $publicKey = self::$instance->ali_public_key;
201
202
        if (is_null($publicKey)) {
203
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
204
        }
205
206
        if (Str::endsWith($publicKey, '.crt')) {
207
            $publicKey = file_get_contents($publicKey);
208
        } elseif (Str::endsWith($publicKey, '.pem')) {
209
            $publicKey = openssl_pkey_get_public(
210
                Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
211
            );
212
        } else {
213
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
214
                wordwrap($publicKey, 64, "\n", true).
215
                "\n-----END PUBLIC KEY-----";
216
        }
217
218
        $sign = $sign ?? $data['sign'];
219
220
        $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
221
222
        $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
223
224
        if (is_resource($publicKey)) {
225
            openssl_free_key($publicKey);
226
        }
227
228
        return $isVerify;
229
    }
230
231
    /**
232
     * Get signContent that is to be signed.
233
     *
234
     * @author yansongda <[email protected]>
235
     *
236
     * @param bool $verify
237
     */
238
    public static function getSignContent(array $data, $verify = false): string
239
    {
240
        ksort($data);
241
        
242
        $isReturn = array_key_exists('method', $data) && 'alipay.trade.page.pay.return' == $data['method'];
243
244
        $stringToBeSigned = '';
245
        foreach ($data as $k => $v) {
246
            if ($verify && 'sign' != $k && ('sign_type' != $k || $isReturn)) {
247
                $stringToBeSigned .= $k.'='.$v.'&';
248
            }
249
            if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
250
                $stringToBeSigned .= $k.'='.$v.'&';
251
            }
252
        }
253
254
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
255
256
        return trim($stringToBeSigned, '&');
257
    }
258
259
    /**
260
     * Convert encoding.
261
     *
262
     * @author yansongda <[email protected]>
263
     *
264
     * @param string|array $data
265
     * @param string       $to
266
     * @param string       $from
267
     */
268
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
269
    {
270
        return Arr::encoding((array) $data, $to, $from);
271
    }
272
273
    /**
274
     * Get service config.
275
     *
276
     * @author yansongda <[email protected]>
277
     *
278
     * @param string|null $key
279
     * @param mixed|null  $default
280
     *
281
     * @return mixed|null
282
     */
283 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...
284
    {
285
        if (is_null($key)) {
286
            return $this->config->all();
287
        }
288
289
        if ($this->config->has($key)) {
290
            return $this->config[$key];
291
        }
292
293
        return $default;
294
    }
295
296
    /**
297
     * Get Base Uri.
298
     *
299
     * @author yansongda <[email protected]>
300
     *
301
     * @return string
302
     */
303
    public function getBaseUri()
304
    {
305
        return $this->baseUri;
306
    }
307
308
    /**
309
     * 生成应用证书SN.
310
     *
311
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
312
     *
313
     * @param $certPath
314
     *
315
     * @throws /Exception
316
     */
317
    public static function getCertSN($certPath): string
318
    {
319
        if (!is_file($certPath)) {
320
            throw new Exception('unknown certPath -- [getCertSN]');
321
        }
322
        $x509data = file_get_contents($certPath);
323
        if (false === $x509data) {
324
            throw new Exception('Alipay CertSN Error -- [getCertSN]');
325
        }
326
        openssl_x509_read($x509data);
327
        $certdata = openssl_x509_parse($x509data);
328
        if (empty($certdata)) {
329
            throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]');
330
        }
331
        $issuer_arr = [];
332
        foreach ($certdata['issuer'] as $key => $val) {
333
            $issuer_arr[] = $key.'='.$val;
334
        }
335
        $issuer = implode(',', array_reverse($issuer_arr));
336
        Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]);
337
338
        return md5($issuer.$certdata['serialNumber']);
339
    }
340
341
    /**
342
     * 生成支付宝根证书SN.
343
     *
344
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
345
     *
346
     * @param $certPath
347
     *
348
     * @return string
349
     *
350
     * @throws /Exception
351
     */
352
    public static function getRootCertSN($certPath)
353
    {
354
        if (!is_file($certPath)) {
355
            throw new Exception('unknown certPath -- [getRootCertSN]');
356
        }
357
        $x509data = file_get_contents($certPath);
358
        if (false === $x509data) {
359
            throw new Exception('Alipay CertSN Error -- [getRootCertSN]');
360
        }
361
        $kCertificateEnd = '-----END CERTIFICATE-----';
362
        $certStrList = explode($kCertificateEnd, $x509data);
363
        $md5_arr = [];
364
        foreach ($certStrList as $one) {
365
            if (!empty(trim($one))) {
366
                $_x509data = $one.$kCertificateEnd;
367
                openssl_x509_read($_x509data);
368
                $_certdata = openssl_x509_parse($_x509data);
369
                if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) {
370
                    $issuer_arr = [];
371
                    foreach ($_certdata['issuer'] as $key => $val) {
372
                        $issuer_arr[] = $key.'='.$val;
373
                    }
374
                    $_issuer = implode(',', array_reverse($issuer_arr));
375
                    if (0 === strpos($_certdata['serialNumber'], '0x')) {
376
                        $serialNumber = self::bchexdec($_certdata['serialNumber']);
377
                    } else {
378
                        $serialNumber = $_certdata['serialNumber'];
379
                    }
380
                    $md5_arr[] = md5($_issuer.$serialNumber);
381
                    Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]);
382
                }
383
            }
384
        }
385
386
        return implode('_', $md5_arr);
387
    }
388
389
    /**
390
     * processingApiResult.
391
     *
392
     * @author yansongda <[email protected]>
393
     *
394
     * @param $data
395
     * @param $result
396
     *
397
     * @throws GatewayException
398
     * @throws InvalidConfigException
399
     * @throws InvalidSignException
400
     */
401
    protected static function processingApiResult($data, $result): Collection
402
    {
403
        $method = str_replace('.', '_', $data['method']).'_response';
404
405
        if (!isset($result['sign']) || '10000' != $result[$method]['code']) {
406
            throw new GatewayException('Get Alipay API Error:'.$result[$method]['msg'].(isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''), $result);
407
        }
408
409
        if (self::verifySign($result[$method], true, $result['sign'])) {
410
            return new Collection($result[$method]);
411
        }
412
413
        Events::dispatch(new Events\SignFailed('Alipay', '', $result));
414
415
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
416
    }
417
418
    /**
419
     * Set Http options.
420
     *
421
     * @author yansongda <[email protected]>
422
     */
423 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...
424
    {
425
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
426
            $this->config->forget('http.base_uri');
427
            $this->httpOptions = $this->config->get('http');
428
        }
429
430
        return $this;
431
    }
432
433
    /**
434
     * 0x转高精度数字.
435
     *
436
     * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
437
     *
438
     * @param $hex
439
     *
440
     * @return int|string
441
     */
442
    private static function bchexdec($hex)
443
    {
444
        $dec = 0;
445
        $len = strlen($hex);
446
        for ($i = 1; $i <= $len; ++$i) {
447
            if (ctype_xdigit($hex[$i - 1])) {
448
                $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
449
            }
450
        }
451
452
        return str_replace('.00', '', $dec);
453
    }
454
}
455