Completed
Pull Request — master (#286)
by wang
03:44
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
 */
28
class Support
29
{
30
    use HasHttpRequest;
31
32
    /**
33
     * Alipay gateway.
34
     *
35
     * @var string
36
     */
37
    protected $baseUri;
38
39
    /**
40
     * Config.
41
     *
42
     * @var Config
43
     */
44
    protected $config;
45
46
    /**
47
     * Instance.
48
     *
49
     * @var Support
50
     */
51
    private static $instance;
52
53
    /**
54
     * Bootstrap.
55
     *
56
     * @author yansongda <[email protected]>
57
     *
58
     * @param Config $config
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|null|Config
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
     * @param Config $config
88
     *
89
     * @return Support
90
     */
91 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...
92
    {
93
        if (php_sapi_name() === 'cli' || !(self::$instance instanceof self)) {
94
            self::$instance = new self($config);
95
        }
96
97
        return self::$instance;
98
    }
99
100
    /**
101
     * getInstance.
102
     *
103
     * @author yansongda <[email protected]>
104
     *
105
     * @throws InvalidArgumentException
106
     *
107
     * @return Support
108
     */
109
    public static function getInstance()
110
    {
111
        if (is_null(self::$instance)) {
112
            throw new InvalidArgumentException('You Should [Create] First Before Using');
113
        }
114
115
        return self::$instance;
116
    }
117
118
    /**
119
     * clear.
120
     *
121
     * @author yansongda <[email protected]>
122
     *
123
     * @return void
124
     */
125
    public function clear()
126
    {
127
        self::$instance = null;
128
    }
129
130
    /**
131
     * Get Alipay API result.
132
     *
133
     * @author yansongda <[email protected]>
134
     *
135
     * @param array $data
136
     *
137
     * @throws GatewayException
138
     * @throws InvalidConfigException
139
     * @throws InvalidSignException
140
     *
141
     * @return Collection
142
     */
143
    public static function requestApi(array $data): Collection
144
    {
145
        Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
146
147
        $data = array_filter($data, function ($value) {
148
            return ($value == '' || is_null($value)) ? false : true;
149
        });
150
151
        $result = json_decode(self::$instance->post('', $data), true);
152
153
        Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
154
155
        return self::processingApiResult($data, $result);
156
    }
157
158
    /**
159
     * Generate sign.
160
     *
161
     * @author yansongda <[email protected]>
162
     *
163
     * @param array $params
164
     *
165
     * @throws InvalidConfigException
166
     *
167
     * @return string
168
     */
169
    public static function generateSign(array $params): string
170
    {
171
        $privateKey = self::$instance->private_key;
172
173
        if (is_null($privateKey)) {
174
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
175
        }
176
177 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...
178
            $privateKey = openssl_pkey_get_private(
179
                Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
180
            );
181
        } else {
182
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
183
                wordwrap($privateKey, 64, "\n", true).
184
                "\n-----END RSA PRIVATE KEY-----";
185
        }
186
187
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
188
189
        $sign = base64_encode($sign);
190
191
        Log::debug('Alipay Generate Sign', [$params, $sign]);
192
193
        if (is_resource($privateKey)) {
194
            openssl_free_key($privateKey);
195
        }
196
197
        return $sign;
198
    }
199
200
    /**
201
     * Verify sign.
202
     *
203
     * @author yansongda <[email protected]>
204
     *
205
     * @param array       $data
206
     * @param bool        $sync
207
     * @param string|null $sign
208
     *
209
     * @throws InvalidConfigException
210
     *
211
     * @return bool
212
     */
213
    public static function verifySign(array $data, $sync = false, $sign = null): bool
214
    {
215
        $publicKey = self::$instance->ali_public_key;
216
217
        if (is_null($publicKey)) {
218
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
219
        }
220
221 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...
222
            $publicKey = openssl_pkey_get_public(
223
                Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
224
            );
225
        } else {
226
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
227
                wordwrap($publicKey, 64, "\n", true).
228
                "\n-----END PUBLIC KEY-----";
229
        }
230
231
        $sign = $sign ?? $data['sign'];
232
233
        $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
234
235
        $isVerify = openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
236
237
        if (is_resource($publicKey)) {
238
            openssl_free_key($publicKey);
239
        }
240
241
        return $isVerify;
242
    }
243
244
    /**
245
     * Get signContent that is to be signed.
246
     *
247
     * @author yansongda <[email protected]>
248
     *
249
     * @param array $data
250
     * @param bool  $verify
251
     *
252
     * @return string
253
     */
254
    public static function getSignContent(array $data, $verify = false): string
255
    {
256
        ksort($data);
257
258
        $stringToBeSigned = '';
259
        foreach ($data as $k => $v) {
260
            if ($verify && $k != 'sign' && $k != 'sign_type') {
261
                $stringToBeSigned .= $k.'='.$v.'&';
262
            }
263
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
264
                $stringToBeSigned .= $k.'='.$v.'&';
265
            }
266
        }
267
268
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
269
270
        return trim($stringToBeSigned, '&');
271
    }
272
273
    /**
274
     * Convert encoding.
275
     *
276
     * @author yansongda <[email protected]>
277
     *
278
     * @param string|array $data
279
     * @param string       $to
280
     * @param string       $from
281
     *
282
     * @return array
283
     */
284
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
285
    {
286
        return Arr::encoding((array) $data, $to, $from);
287
    }
288
289
    /**
290
     * Get service config.
291
     *
292
     * @author yansongda <[email protected]>
293
     *
294
     * @param null|string $key
295
     * @param null|mixed  $default
296
     *
297
     * @return mixed|null
298
     */
299 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...
300
    {
301
        if (is_null($key)) {
302
            return $this->config->all();
303
        }
304
305
        if ($this->config->has($key)) {
306
            return $this->config[$key];
307
        }
308
309
        return $default;
310
    }
311
312
    /**
313
     * Get Base Uri.
314
     *
315
     * @author yansongda <[email protected]>
316
     *
317
     * @return string
318
     */
319
    public function getBaseUri()
320
    {
321
        return $this->baseUri;
322
    }
323
324
    /**
325
     * processingApiResult.
326
     *
327
     * @author yansongda <[email protected]>
328
     *
329
     * @param $data
330
     * @param $result
331
     *
332
     * @throws GatewayException
333
     * @throws InvalidConfigException
334
     * @throws InvalidSignException
335
     *
336
     * @return Collection
337
     */
338
    protected static function processingApiResult($data, $result): Collection
339
    {
340
        $method = str_replace('.', '_', $data['method']).'_response';
341
342
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
343
            throw new GatewayException(
344
                'Get Alipay API Error:'.$result[$method]['msg'].
345
                    (isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''),
346
                $result
347
            );
348
        }
349
350
        if (self::verifySign($result[$method], true, $result['sign'])) {
351
            return new Collection($result[$method]);
352
        }
353
354
        Events::dispatch(new Events\SignFailed('Alipay', '', $result));
355
356
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
357
    }
358
359
    /**
360
     * Set Http options.
361
     *
362
     * @author yansongda <[email protected]>
363
     *
364
     * @return self
365
     */
366 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...
367
    {
368
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
369
            $this->config->forget('http.base_uri');
370
            $this->httpOptions = $this->config->get('http');
371
        }
372
373
        return $this;
374
    }
375
}
376