Completed
Push — master ( 644a66...68d189 )
by Songda
13s queued 11s
created

Support::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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 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...
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 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...
221
            $publicKey = openssl_pkey_get_public(
222
                Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
223
            );
224
        } else {
225
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
226
                wordwrap($publicKey, 64, "\n", true).
227
                "\n-----END PUBLIC KEY-----";
228
        }
229
230
        $sign = $sign ?? $data['sign'];
231
232
        $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
233
234
        $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
235
236
        if (is_resource($publicKey)) {
237
            openssl_free_key($publicKey);
238
        }
239
240
        return $isVerify;
241
    }
242
243
    /**
244
     * Get signContent that is to be signed.
245
     *
246
     * @author yansongda <[email protected]>
247
     *
248
     * @param array $data
249
     * @param bool  $verify
250
     *
251
     * @return string
252
     */
253
    public static function getSignContent(array $data, $verify = false): string
254
    {
255
        ksort($data);
256
257
        $stringToBeSigned = '';
258
        foreach ($data as $k => $v) {
259
            if ($verify && 'sign' != $k && 'sign_type' != $k) {
260
                $stringToBeSigned .= $k.'='.$v.'&';
261
            }
262
            if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
263
                $stringToBeSigned .= $k.'='.$v.'&';
264
            }
265
        }
266
267
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
268
269
        return trim($stringToBeSigned, '&');
270
    }
271
272
    /**
273
     * Convert encoding.
274
     *
275
     * @author yansongda <[email protected]>
276
     *
277
     * @param string|array $data
278
     * @param string       $to
279
     * @param string       $from
280
     *
281
     * @return array
282
     */
283
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
284
    {
285
        return Arr::encoding((array) $data, $to, $from);
286
    }
287
288
    /**
289
     * Get service config.
290
     *
291
     * @author yansongda <[email protected]>
292
     *
293
     * @param string|null $key
294
     * @param mixed|null  $default
295
     *
296
     * @return mixed|null
297
     */
298 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...
299
    {
300
        if (is_null($key)) {
301
            return $this->config->all();
302
        }
303
304
        if ($this->config->has($key)) {
305
            return $this->config[$key];
306
        }
307
308
        return $default;
309
    }
310
311
    /**
312
     * Get Base Uri.
313
     *
314
     * @author yansongda <[email protected]>
315
     *
316
     * @return string
317
     */
318
    public function getBaseUri()
319
    {
320
        return $this->baseUri;
321
    }
322
323
    /**
324
     * processingApiResult.
325
     *
326
     * @author yansongda <[email protected]>
327
     *
328
     * @param $data
329
     * @param $result
330
     *
331
     * @throws GatewayException
332
     * @throws InvalidConfigException
333
     * @throws InvalidSignException
334
     *
335
     * @return Collection
336
     */
337
    protected static function processingApiResult($data, $result): Collection
338
    {
339
        $method = str_replace('.', '_', $data['method']).'_response';
340
341
        if (!isset($result['sign']) || '10000' != $result[$method]['code']) {
342
            throw new GatewayException(
343
                'Get Alipay API Error:'.$result[$method]['msg'].
344
                    (isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''),
345
                $result
346
            );
347
        }
348
349
        if (self::verifySign($result[$method], true, $result['sign'])) {
350
            return new Collection($result[$method]);
351
        }
352
353
        Events::dispatch(new Events\SignFailed('Alipay', '', $result));
354
355
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
356
    }
357
358
    /**
359
     * Set Http options.
360
     *
361
     * @author yansongda <[email protected]>
362
     *
363
     * @return self
364
     */
365 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...
366
    {
367
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
368
            $this->config->forget('http.base_uri');
369
            $this->httpOptions = $this->config->get('http');
370
        }
371
372
        return $this;
373
    }
374
}
375