Support   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 326
Duplicated Lines 15.34 %

Coupling/Cohesion

Components 2
Dependencies 13

Importance

Changes 0
Metric Value
wmc 41
lcom 2
cbo 13
dl 50
loc 326
rs 9.1199
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 7 7 1
A __get() 0 4 1
A create() 8 8 3
A clear() 0 4 1
A requestApi() 0 14 3
A generateSign() 7 28 4
A verifySign() 7 28 5
B getSignContent() 0 18 10
A encoding() 0 4 1
A getConfig() 12 12 3
A getBaseUri() 0 4 1
A processingApiResult() 0 20 5
A setHttpOptions() 9 9 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Support often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Support, and based on these observations, apply Extract Interface, too.

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