Completed
Push — master ( fc309c...0894e0 )
by Songda
01:24
created

Support::verifySign()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 30

Duplication

Lines 9
Ratio 30 %

Importance

Changes 0
Metric Value
dl 9
loc 30
rs 8.8177
c 0
b 0
f 0
cc 6
nc 9
nop 3
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\InvalidConfigException;
8
use Yansongda\Pay\Exceptions\InvalidSignException;
9
use Yansongda\Pay\Gateways\Alipay;
10
use Yansongda\Pay\Log;
11
use Yansongda\Supports\Arr;
12
use Yansongda\Supports\Collection;
13
use Yansongda\Supports\Config;
14
use Yansongda\Supports\Str;
15
use Yansongda\Supports\Traits\HasHttpRequest;
16
17
/**
18
 * @author yansongda <[email protected]>
19
 *
20
 * @property string app_id alipay app_id
21
 * @property string ali_public_key
22
 * @property string private_key
23
 * @property array http http options
24
 * @property string mode current mode
25
 * @property array log log options
26
 */
27
class Support
28
{
29
    use HasHttpRequest;
30
31
    /**
32
     * Alipay gateway.
33
     *
34
     * @var string
35
     */
36
    protected $baseUri;
37
38
    /**
39
     * Config.
40
     *
41
     * @var Config
42
     */
43
    protected $config;
44
45
    /**
46
     * Instance.
47
     *
48
     * @var Support
49
     */
50
    private static $instance;
51
52
    /**
53
     * Bootstrap.
54
     *
55
     * @author yansongda <[email protected]>
56
     *
57
     * @param Config $config
58
     */
59 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...
60
    {
61
        $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
62
        $this->config = $config;
63
64
        $this->setHttpOptions();
65
    }
66
67
    /**
68
     * __get.
69
     *
70
     * @author yansongda <[email protected]>
71
     *
72
     * @param $key
73
     *
74
     * @return mixed|null|Config
75
     */
76
    public function __get($key)
77
    {
78
        return $this->getConfig($key);
79
    }
80
81
    /**
82
     * create.
83
     *
84
     * @author yansongda <[email protected]>
85
     *
86
     * @param Config $config
87
     *
88
     * @return Support
89
     */
90 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...
91
    {
92
        if (php_sapi_name() === 'cli' || !(self::$instance instanceof self)) {
93
            self::$instance = new self($config);
94
        }
95
96
        return self::$instance;
97
    }
98
99
    /**
100
     * clear.
101
     *
102
     * @author yansongda <[email protected]>
103
     *
104
     * @return void
105
     */
106
    public function clear()
107
    {
108
        self::$instance = null;
109
    }
110
111
    /**
112
     * Get Alipay API result.
113
     *
114
     * @author yansongda <[email protected]>
115
     *
116
     * @param array $data
117
     *
118
     * @throws GatewayException
119
     * @throws InvalidConfigException
120
     * @throws InvalidSignException
121
     *
122
     * @return Collection
123
     */
124
    public static function requestApi(array $data): Collection
125
    {
126
        Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
127
128
        $data = array_filter($data, function ($value) {
129
            return ($value == '' || is_null($value)) ? false : true;
130
        });
131
132
        $result = json_decode(self::$instance->post('', $data), true);
133
134
        Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
135
136
        return self::processingApiResult($data, $result);
137
    }
138
139
    /**
140
     * Generate sign.
141
     *
142
     * @author yansongda <[email protected]>
143
     *
144
     * @param array $params
145
     *
146
     * @throws InvalidConfigException
147
     *
148
     * @return string
149
     */
150
    public static function generateSign(array $params): string
151
    {
152
        $privateKey = self::$instance->private_key;
153
154
        if (is_null($privateKey)) {
155
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
156
        }
157
158 View Code Duplication
        if (Str::endsWith($privateKey, '.pem')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
159
            $privateKey = openssl_pkey_get_private(
160
                Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
161
            );
162
        } else {
163
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
164
                wordwrap($privateKey, 64, "\n", true).
165
                "\n-----END RSA PRIVATE KEY-----";
166
        }
167
168
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
169
170
        $sign = base64_encode($sign);
171
172
        Log::debug('Alipay Generate Sign', [$params, $sign]);
173
174
        if (is_resource($privateKey)) {
175
            openssl_free_key($privateKey);
176
        }
177
178
        return $sign;
179
    }
180
181
    /**
182
     * Verify sign.
183
     *
184
     * @author yansongda <[email protected]>
185
     *
186
     * @param array       $data
187
     * @param bool        $sync
188
     * @param string|null $sign
189
     *
190
     * @throws InvalidConfigException
191
     *
192
     * @return bool
193
     */
194
    public static function verifySign(array $data, $sync = false, $sign = null): bool
195
    {
196
        $publicKey = self::$instance->ali_public_key;
197
198
        if (is_null($publicKey)) {
199
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
200
        }
201
202 View Code Duplication
        if (Str::endsWith($publicKey, '.pem')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
203
            $publicKey = openssl_pkey_get_public(
204
                Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
205
            );
206
        } else {
207
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
208
                wordwrap($publicKey, 64, "\n", true).
209
                "\n-----END PUBLIC KEY-----";
210
        }
211
212
        $sign = $sign ?? $data['sign'];
213
214
        $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
215
216
        $isVerify = openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
217
218
        if (is_resource($publicKey)) {
219
            openssl_free_key($publicKey);
220
        }
221
222
        return $isVerify;
223
    }
224
225
    /**
226
     * Get signContent that is to be signed.
227
     *
228
     * @author yansongda <[email protected]>
229
     *
230
     * @param array $data
231
     * @param bool  $verify
232
     *
233
     * @return string
234
     */
235
    public static function getSignContent(array $data, $verify = false): string
236
    {
237
        ksort($data);
238
239
        $stringToBeSigned = '';
240
        foreach ($data as $k => $v) {
241
            if ($verify && $k != 'sign' && $k != 'sign_type') {
242
                $stringToBeSigned .= $k.'='.$v.'&';
243
            }
244
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
245
                $stringToBeSigned .= $k.'='.$v.'&';
246
            }
247
        }
248
249
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
250
251
        return trim($stringToBeSigned, '&');
252
    }
253
254
    /**
255
     * Convert encoding.
256
     *
257
     * @author yansongda <[email protected]>
258
     *
259
     * @param string|array $data
260
     * @param string       $to
261
     * @param string       $from
262
     *
263
     * @return array
264
     */
265
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
266
    {
267
        return Arr::encoding((array) $data, $to, $from);
268
    }
269
270
    /**
271
     * Get service config.
272
     *
273
     * @author yansongda <[email protected]>
274
     *
275
     * @param null|string $key
276
     * @param null|mixed  $default
277
     *
278
     * @return mixed|null
279
     */
280 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...
281
    {
282
        if (is_null($key)) {
283
            return $this->config->all();
284
        }
285
286
        if ($this->config->has($key)) {
287
            return $this->config[$key];
288
        }
289
290
        return $default;
291
    }
292
293
    /**
294
     * Get Base Uri.
295
     *
296
     * @author yansongda <[email protected]>
297
     *
298
     * @return string
299
     */
300
    public function getBaseUri()
301
    {
302
        return $this->baseUri;
303
    }
304
305
    /**
306
     * processingApiResult.
307
     *
308
     * @author yansongda <[email protected]>
309
     *
310
     * @param $data
311
     * @param $result
312
     *
313
     * @throws GatewayException
314
     * @throws InvalidConfigException
315
     * @throws InvalidSignException
316
     *
317
     * @return Collection
318
     */
319
    protected static function processingApiResult($data, $result): Collection
320
    {
321
        $method = str_replace('.', '_', $data['method']).'_response';
322
323
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
324
            throw new GatewayException(
325
                'Get Alipay API Error:'.$result[$method]['msg'].
326
                    (isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''),
327
                $result
328
            );
329
        }
330
331
        if (self::verifySign($result[$method], true, $result['sign'])) {
332
            return new Collection($result[$method]);
333
        }
334
335
        Events::dispatch(new Events\SignFailed('Alipay', '', $result));
336
337
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
338
    }
339
340
    /**
341
     * Set Http options.
342
     *
343
     * @author yansongda <[email protected]>
344
     *
345
     * @return self
346
     */
347 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...
348
    {
349
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
350
            $this->config->forget('http.base_uri');
351
            $this->httpOptions = $this->config->get('http');
352
        }
353
354
        return $this;
355
    }
356
}
357