Support   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 444
Duplicated Lines 8.56 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 13
dl 38
loc 444
rs 8.4
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __get() 0 4 1
A create() 10 10 3
A getInstance() 0 8 2
A clear() 0 4 1
A requestApi() 0 18 3
A filterPayload() 0 23 4
A generateSign() 0 16 2
A getSignContent() 0 12 5
A decryptRefundContents() 0 9 1
A toXml() 0 15 5
A fromXml() 0 10 2
A getConfig() 12 12 3
A getTypeName() 0 15 3
A getBaseUri() 0 4 1
B processingApiResult() 0 26 8
A setDevKey() 0 16 2
A setHttpOptions() 9 9 3
A __construct() 7 7 1

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\Wechat;
4
5
use Exception;
6
use Yansongda\Pay\Events;
7
use Yansongda\Pay\Exceptions\BusinessException;
8
use Yansongda\Pay\Exceptions\GatewayException;
9
use Yansongda\Pay\Exceptions\InvalidArgumentException;
10
use Yansongda\Pay\Exceptions\InvalidSignException;
11
use Yansongda\Pay\Gateways\Wechat;
12
use Yansongda\Pay\Log;
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 appid
22
 * @property string app_id
23
 * @property string miniapp_id
24
 * @property string sub_appid
25
 * @property string sub_app_id
26
 * @property string sub_miniapp_id
27
 * @property string mch_id
28
 * @property string sub_mch_id
29
 * @property string key
30
 * @property string return_url
31
 * @property string cert_client
32
 * @property string cert_key
33
 * @property array log
34
 * @property array http
35
 * @property string mode
36
 */
37
class Support
38
{
39
    use HasHttpRequest;
40
41
    /**
42
     * Wechat gateway.
43
     *
44
     * @var string
45
     */
46
    protected $baseUri;
47
48
    /**
49
     * Config.
50
     *
51
     * @var Config
52
     */
53
    protected $config;
54
55
    /**
56
     * Instance.
57
     *
58
     * @var Support
59
     */
60
    private static $instance;
61
62
    /**
63
     * Bootstrap.
64
     *
65
     * @author yansongda <[email protected]>
66
     *
67
     * @param Config $config
68
     */
69 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...
70
    {
71
        $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)];
72
        $this->config = $config;
73
74
        $this->setHttpOptions();
75
    }
76
77
    /**
78
     * __get.
79
     *
80
     * @author yansongda <[email protected]>
81
     *
82
     * @param $key
83
     *
84
     * @return mixed|null|Config
85
     */
86
    public function __get($key)
87
    {
88
        return $this->getConfig($key);
89
    }
90
91
    /**
92
     * create.
93
     *
94
     * @author yansongda <[email protected]>
95
     *
96
     * @param Config $config
97
     *
98
     * @throws GatewayException
99
     * @throws InvalidArgumentException
100
     * @throws InvalidSignException
101
     *
102
     * @return Support
103
     */
104 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...
105
    {
106
        if (php_sapi_name() === 'cli' || !(self::$instance instanceof self)) {
107
            self::$instance = new self($config);
108
109
            self::setDevKey();
110
        }
111
112
        return self::$instance;
113
    }
114
115
    /**
116
     * getInstance.
117
     *
118
     * @author yansongda <[email protected]>
119
     *
120
     * @throws InvalidArgumentException
121
     *
122
     * @return Support
123
     */
124
    public static function getInstance()
125
    {
126
        if (is_null(self::$instance)) {
127
            throw new InvalidArgumentException('You Should [Create] First Before Using');
128
        }
129
130
        return self::$instance;
131
    }
132
133
    /**
134
     * clear.
135
     *
136
     * @author yansongda <[email protected]>
137
     *
138
     * @return void
139
     */
140
    public static function clear()
141
    {
142
        self::$instance = null;
143
    }
144
145
    /**
146
     * Request wechat api.
147
     *
148
     * @author yansongda <[email protected]>
149
     *
150
     * @param string $endpoint
151
     * @param array  $data
152
     * @param bool   $cert
153
     *
154
     * @throws GatewayException
155
     * @throws InvalidArgumentException
156
     * @throws InvalidSignException
157
     *
158
     * @return Collection
159
     */
160
    public static function requestApi($endpoint, $data, $cert = false): Collection
161
    {
162
        Events::dispatch(new Events\ApiRequesting('Wechat', '', self::$instance->getBaseUri().$endpoint, $data));
163
164
        $result = self::$instance->post(
165
            $endpoint,
166
            self::toXml($data),
167
            $cert ? [
168
                'cert'    => self::$instance->cert_client,
169
                'ssl_key' => self::$instance->cert_key,
170
            ] : []
171
        );
172
        $result = is_array($result) ? $result : self::fromXml($result);
173
174
        Events::dispatch(new Events\ApiRequested('Wechat', '', self::$instance->getBaseUri().$endpoint, $result));
175
176
        return self::processingApiResult($endpoint, $result);
177
    }
178
179
    /**
180
     * Filter payload.
181
     *
182
     * @author yansongda <[email protected]>
183
     *
184
     * @param array        $payload
185
     * @param array|string $params
186
     * @param bool         $preserve_notify_url
187
     *
188
     * @throws InvalidArgumentException
189
     *
190
     * @return array
191
     */
192
    public static function filterPayload($payload, $params, $preserve_notify_url = false): array
193
    {
194
        $type = self::getTypeName($params['type'] ?? '');
195
196
        $payload = array_merge(
197
            $payload,
198
            is_array($params) ? $params : ['out_trade_no' => $params]
199
        );
200
        $payload['appid'] = self::$instance->getConfig($type, '');
201
202
        if (self::$instance->getConfig('mode', Wechat::MODE_NORMAL) === Wechat::MODE_SERVICE) {
203
            $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, '');
204
        }
205
206
        unset($payload['trade_type'], $payload['type']);
207
        if (!$preserve_notify_url) {
208
            unset($payload['notify_url']);
209
        }
210
211
        $payload['sign'] = self::generateSign($payload);
212
213
        return $payload;
214
    }
215
216
    /**
217
     * Generate wechat sign.
218
     *
219
     * @author yansongda <[email protected]>
220
     *
221
     * @param array $data
222
     *
223
     * @throws InvalidArgumentException
224
     *
225
     * @return string
226
     */
227
    public static function generateSign($data): string
228
    {
229
        $key = self::$instance->key;
230
231
        if (is_null($key)) {
232
            throw new InvalidArgumentException('Missing Wechat Config -- [key]');
233
        }
234
235
        ksort($data);
236
237
        $string = md5(self::getSignContent($data).'&key='.$key);
238
239
        Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]);
240
241
        return strtoupper($string);
242
    }
243
244
    /**
245
     * Generate sign content.
246
     *
247
     * @author yansongda <[email protected]>
248
     *
249
     * @param array $data
250
     *
251
     * @return string
252
     */
253
    public static function getSignContent($data): string
254
    {
255
        $buff = '';
256
257
        foreach ($data as $k => $v) {
258
            $buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
259
        }
260
261
        Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]);
262
263
        return trim($buff, '&');
264
    }
265
266
    /**
267
     * Decrypt refund contents.
268
     *
269
     * @author yansongda <[email protected]>
270
     *
271
     * @param string $contents
272
     *
273
     * @return string
274
     */
275
    public static function decryptRefundContents($contents): string
276
    {
277
        return openssl_decrypt(
278
            base64_decode($contents),
279
            'AES-256-ECB',
280
            md5(self::$instance->key),
281
            OPENSSL_RAW_DATA
282
        );
283
    }
284
285
    /**
286
     * Convert array to xml.
287
     *
288
     * @author yansongda <[email protected]>
289
     *
290
     * @param array $data
291
     *
292
     * @throws InvalidArgumentException
293
     *
294
     * @return string
295
     */
296
    public static function toXml($data): string
297
    {
298
        if (!is_array($data) || count($data) <= 0) {
299
            throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
300
        }
301
302
        $xml = '<xml>';
303
        foreach ($data as $key => $val) {
304
            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
305
                                       '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
306
        }
307
        $xml .= '</xml>';
308
309
        return $xml;
310
    }
311
312
    /**
313
     * Convert xml to array.
314
     *
315
     * @author yansongda <[email protected]>
316
     *
317
     * @param string $xml
318
     *
319
     * @throws InvalidArgumentException
320
     *
321
     * @return array
322
     */
323
    public static function fromXml($xml): array
324
    {
325
        if (!$xml) {
326
            throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
327
        }
328
329
        libxml_disable_entity_loader(true);
330
331
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
332
    }
333
334
    /**
335
     * Get service config.
336
     *
337
     * @author yansongda <[email protected]>
338
     *
339
     * @param null|string $key
340
     * @param null|mixed  $default
341
     *
342
     * @return mixed|null
343
     */
344 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...
345
    {
346
        if (is_null($key)) {
347
            return $this->config->all();
348
        }
349
350
        if ($this->config->has($key)) {
351
            return $this->config[$key];
352
        }
353
354
        return $default;
355
    }
356
357
    /**
358
     * Get app id according to param type.
359
     *
360
     * @author yansongda <[email protected]>
361
     *
362
     * @param string $type
363
     *
364
     * @return string
365
     */
366
    public static function getTypeName($type = ''): string
367
    {
368
        switch ($type) {
369
            case '':
370
                $type = 'app_id';
371
                break;
372
            case 'app':
373
                $type = 'appid';
374
                break;
375
            default:
376
                $type = $type.'_id';
377
        }
378
379
        return $type;
380
    }
381
382
    /**
383
     * Get Base Uri.
384
     *
385
     * @author yansongda <[email protected]>
386
     *
387
     * @return string
388
     */
389
    public function getBaseUri()
390
    {
391
        return $this->baseUri;
392
    }
393
394
    /**
395
     * processingApiResult.
396
     *
397
     * @author yansongda <[email protected]>
398
     *
399
     * @param       $endpoint
400
     * @param array $result
401
     *
402
     * @throws GatewayException
403
     * @throws InvalidArgumentException
404
     * @throws InvalidSignException
405
     *
406
     * @return Collection
407
     */
408
    protected static function processingApiResult($endpoint, array $result)
409
    {
410
        if (!isset($result['return_code']) || $result['return_code'] != 'SUCCESS') {
411
            throw new GatewayException(
412
                'Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg'] ?? ''),
413
                $result
414
            );
415
        }
416
417
        if (isset($result['result_code']) && $result['result_code'] != 'SUCCESS') {
418
            throw new BusinessException(
419
                'Wechat Business Error: '.$result['err_code'].' - '.$result['err_code_des'],
420
                $result
421
            );
422
        }
423
424
        if ($endpoint === 'pay/getsignkey' ||
425
            strpos($endpoint, 'mmpaymkttransfers') !== false ||
426
            self::generateSign($result) === $result['sign']) {
427
            return new Collection($result);
428
        }
429
430
        Events::dispatch(new Events\SignFailed('Wechat', '', $result));
431
432
        throw new InvalidSignException('Wechat Sign Verify FAILED', $result);
433
    }
434
435
    /**
436
     * setDevKey.
437
     *
438
     * @author yansongda <[email protected]>
439
     *
440
     * @throws GatewayException
441
     * @throws InvalidArgumentException
442
     * @throws InvalidSignException
443
     * @throws Exception
444
     *
445
     * @return Support
446
     */
447
    private static function setDevKey()
448
    {
449
        if (self::$instance->mode == Wechat::MODE_DEV) {
450
            $data = [
451
                'mch_id'    => self::$instance->mch_id,
452
                'nonce_str' => Str::random(),
453
            ];
454
            $data['sign'] = self::generateSign($data);
455
456
            $result = self::requestApi('pay/getsignkey', $data);
457
458
            self::$instance->config->set('key', $result['sandbox_signkey']);
459
        }
460
461
        return self::$instance;
462
    }
463
464
    /**
465
     * Set Http options.
466
     *
467
     * @author yansongda <[email protected]>
468
     *
469
     * @return self
470
     */
471 View Code Duplication
    private 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...
472
    {
473
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
474
            $this->config->forget('http.base_uri');
475
            $this->httpOptions = $this->config->get('http');
476
        }
477
478
        return $this;
479
    }
480
}
481