Completed
Push — master ( 065035...4118ee )
by Songda
01:49
created

Support::processingApiResult()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 4
nc 3
nop 2
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
    private function __construct(Config $config)
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 yansongda
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(Events::API_REQUESTING, 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 = mb_convert_encoding(self::$instance->post('', $data), 'utf-8', 'gb2312');
133
134
        $result = json_decode($result, true);
135
136
        Events::dispatch(Events::API_REQUESTED, new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
137
138
        return self::processingApiResult($data, $result);
139
    }
140
141
    /**
142
     * processingApiResult.
143
     *
144
     * @author yansongda <[email protected]>
145
     *
146
     * @param $data
147
     * @param $result
148
     *
149
     * @throws GatewayException
150
     * @throws InvalidConfigException
151
     * @throws InvalidSignException
152
     *
153
     * @return Collection
154
     */
155
    protected static function processingApiResult($data, $result): Collection
156
    {
157
        $method = str_replace('.', '_', $data['method']).'_response';
158
159
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
160
            throw new GatewayException(
161
                'Get Alipay API Error:'.$result[$method]['msg'].($result[$method]['sub_code'] ?? ''),
162
                $result
163
            );
164
        }
165
166
        if (self::verifySign($result[$method], true, $result['sign'])) {
167
            return new Collection($result[$method]);
168
        }
169
170
        Events::dispatch(Events::SIGN_FAILED, new Events\SignFailed('Alipay', '', $result));
171
172
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
173
    }
174
175
    /**
176
     * Generate sign.
177
     *
178
     * @author yansongda <[email protected]>
179
     *
180
     * @param array $params
181
     *
182
     * @throws InvalidConfigException
183
     *
184
     * @return string
185
     */
186
    public static function generateSign(array $params): string
187
    {
188
        $privateKey = self::$instance->private_key;
189
190
        if (is_null($privateKey)) {
191
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
192
        }
193
194 View Code Duplication
        if (Str::endsWith($privateKey, '.pem')) {
0 ignored issues
show
Duplication introduced by yansongda
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...
195
            $privateKey = openssl_pkey_get_private($privateKey);
196
        } else {
197
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
198
                wordwrap($privateKey, 64, "\n", true).
199
                "\n-----END RSA PRIVATE KEY-----";
200
        }
201
202
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
203
204
        $sign = base64_encode($sign);
205
206
        Log::debug('Alipay Generate Sign', [$params, $sign]);
207
208
        return $sign;
209
    }
210
211
    /**
212
     * Verify sign.
213
     *
214
     * @author yansongda <[email protected]>
215
     *
216
     * @param array       $data
217
     * @param bool        $sync
218
     * @param string|null $sign
219
     *
220
     * @throws InvalidConfigException
221
     *
222
     * @return bool
223
     */
224
    public static function verifySign(array $data, $sync = false, $sign = null): bool
225
    {
226
        $publicKey = self::$instance->ali_public_key;
227
228
        if (is_null($publicKey)) {
229
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
230
        }
231
232 View Code Duplication
        if (Str::endsWith($publicKey, '.pem')) {
0 ignored issues
show
Duplication introduced by yansongda
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...
233
            $publicKey = openssl_pkey_get_public($publicKey);
234
        } else {
235
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
236
                wordwrap($publicKey, 64, "\n", true).
237
                "\n-----END PUBLIC KEY-----";
238
        }
239
240
        $sign = $sign ?? $data['sign'];
241
242
        $toVerify = $sync ? mb_convert_encoding(json_encode($data, JSON_UNESCAPED_UNICODE), 'gb2312', 'utf-8') :
243
                            self::getSignContent($data, true);
244
245
        return openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
246
    }
247
248
    /**
249
     * Get signContent that is to be signed.
250
     *
251
     * @author yansongda <[email protected]>
252
     *
253
     * @param array $data
254
     * @param bool  $verify
255
     *
256
     * @return string
257
     */
258
    public static function getSignContent(array $data, $verify = false): string
259
    {
260
        $data = self::encoding($data, $data['charset'] ?? 'gb2312', 'utf-8');
261
262
        ksort($data);
263
264
        $stringToBeSigned = '';
265
        foreach ($data as $k => $v) {
266
            if ($verify && $k != 'sign' && $k != 'sign_type') {
267
                $stringToBeSigned .= $k.'='.$v.'&';
268
            }
269
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
270
                $stringToBeSigned .= $k.'='.$v.'&';
271
            }
272
        }
273
274
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
275
276
        return trim($stringToBeSigned, '&');
277
    }
278
279
    /**
280
     * Convert encoding.
281
     *
282
     * @author yansongda <[email protected]>
283
     *
284
     * @param string|array $data
285
     * @param string       $to
286
     * @param string       $from
287
     *
288
     * @return array
289
     */
290
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
291
    {
292
        return Arr::encoding((array) $data, $to, $from);
293
    }
294
295
    /**
296
     * Get service config.
297
     *
298
     * @author yansongda <[email protected]>
299
     *
300
     * @param null|string $key
301
     * @param null|mixed  $default
302
     *
303
     * @return mixed|null
304
     */
305 View Code Duplication
    public function getConfig($key = null, $default = null)
0 ignored issues
show
Duplication introduced by yansongda
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...
306
    {
307
        if (is_null($key)) {
308
            return $this->config->all();
309
        }
310
311
        if ($this->config->has($key)) {
312
            return $this->config[$key];
313
        }
314
315
        return $default;
316
    }
317
318
    /**
319
     * Get Base Uri.
320
     *
321
     * @author yansongda <[email protected]>
322
     *
323
     * @return string
324
     */
325
    public function getBaseUri()
326
    {
327
        return $this->baseUri;
328
    }
329
330
    /**
331
     * Set Http options.
332
     *
333
     * @author yansongda <[email protected]>
334
     *
335
     * @return self
336
     */
337 View Code Duplication
    protected function setHttpOptions(): self
0 ignored issues
show
Duplication introduced by yansongda
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...
338
    {
339
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
340
            $this->config->forget('http.base_uri');
341
            $this->httpOptions = $this->config->get('http');
342
        }
343
344
        return $this;
345
    }
346
}
347