Completed
Push — master ( 4acb65...24404c )
by Songda
01:42
created

Support   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 404
Duplicated Lines 8.17 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 7
dl 33
loc 404
rs 8.72
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A __get() 0 4 1
A getBaseUri() 0 4 1
A getInstance() 12 12 4
B requestApi() 0 32 8
A filterPayload() 0 25 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 initialize() 0 4 1
A getConfig() 12 12 3
A getTypeName() 0 14 3
A setBaseUri() 0 17 3
A setHttpOptions() 9 9 2

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 Yansongda\Pay\Exceptions\GatewayException;
6
use Yansongda\Pay\Exceptions\InvalidArgumentException;
7
use Yansongda\Pay\Exceptions\InvalidSignException;
8
use Yansongda\Pay\Gateways\Wechat;
9
use Yansongda\Pay\Log;
10
use Yansongda\Supports\Collection;
11
use Yansongda\Supports\Config;
12
use Yansongda\Supports\Traits\HasHttpRequest;
13
14
/**
15
 * @author yansongda <[email protected]>
16
 *
17
 * @property string appid
18
 * @property string app_id
19
 * @property string miniapp_id
20
 * @property string sub_appid
21
 * @property string sub_app_id
22
 * @property string sub_miniapp_id
23
 * @property string mch_id
24
 * @property string sub_mch_id
25
 * @property string key
26
 * @property string return_url
27
 * @property string cert_client
28
 * @property string cert_key
29
 * @property array log
30
 * @property array http
31
 * @property string mode
32
 */
33
class Support
34
{
35
    use HasHttpRequest;
36
37
    /**
38
     * Wechat gateway.
39
     *
40
     * @var string
41
     */
42
    protected $baseUri = 'https://api.mch.weixin.qq.com/';
43
44
    /**
45
     * Config.
46
     *
47
     * @var Config
48
     */
49
    protected $config;
50
51
    /**
52
     * Instance.
53
     *
54
     * @var Support
55
     */
56
    private static $instance;
57
58
    /**
59
     * Bootstrap.
60
     *
61
     * @author yansongda <[email protected]>
62
     *
63
     * @param Config $config
64
     *
65
     * @throws InvalidArgumentException
66
     */
67
    private function __construct(Config $config)
68
    {
69
        $this->config = $config;
70
        $this->setBaseUri()->setHttpOptions();
71
    }
72
73
    /**
74
     * __get.
75
     *
76
     * @author yansongda <[email protected]>
77
     *
78
     * @param $key
79
     *
80
     * @return mixed|null|Config
81
     */
82
    public function __get($key)
83
    {
84
        return $this->getConfig($key);
85
    }
86
87
    /**
88
     * Get Base Uri.
89
     *
90
     * @author yansongda <[email protected]>
91
     *
92
     * @return string
93
     */
94
    public function getBaseUri()
95
    {
96
        return $this->baseUri;
97
    }
98
99
    /**
100
     * Get instance.
101
     *
102
     * @author yansongda <[email protected]>
103
     *
104
     * @param Config|null $config
105
     *
106
     * @throws InvalidArgumentException
107
     *
108
     * @return self
109
     */
110 View Code Duplication
    public static function getInstance($config = null): 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...
111
    {
112
        if ((!(self::$instance instanceof self)) && is_null($config)) {
113
            throw new InvalidArgumentException('Must Initialize Support With Config Before Using');
114
        }
115
116
        if (!(self::$instance instanceof self)) {
117
            self::$instance = new self($config);
0 ignored issues
show
Bug introduced by
It seems like $config defined by parameter $config on line 110 can be null; however, Yansongda\Pay\Gateways\W...\Support::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
118
        }
119
120
        return self::$instance;
121
    }
122
123
    /**
124
     * Request wechat api.
125
     *
126
     * @author yansongda <[email protected]>
127
     *
128
     * @param string $endpoint
129
     * @param array  $data
130
     * @param bool   $cert
131
     *
132
     * @throws GatewayException
133
     * @throws InvalidArgumentException
134
     * @throws InvalidSignException
135
     *
136
     * @return Collection
137
     */
138
    public static function requestApi($endpoint, $data, $cert = false): Collection
139
    {
140
        Log::debug('Request To Wechat Api', [self::getInstance()->getBaseUri().$endpoint, $data]);
141
142
        $result = self::getInstance()->post(
143
            $endpoint,
144
            self::toXml($data),
145
            $cert ? [
146
                'cert' => self::getInstance()->cert_client,
147
                'ssl_key' => self::getInstance()->cert_key
148
            ] : []
149
        );
150
        $result = is_array($result) ? $result : self::fromXml($result);
151
152
        Log::debug('Result Of Wechat Api', $result);
153
154
        if (!isset($result['return_code']) || $result['return_code'] != 'SUCCESS' || $result['result_code'] != 'SUCCESS') {
155
            throw new GatewayException(
156
                'Get Wechat API Error:'.$result['return_msg'].($result['err_code_des'] ?? ''),
157
                $result,
158
                20000
159
            );
160
        }
161
162
        if (strpos($endpoint, 'mmpaymkttransfers') !== false || self::generateSign($result) === $result['sign']) {
163
            return new Collection($result);
164
        }
165
166
        Log::warning('Wechat Sign Verify FAILED', $result);
167
168
        throw new InvalidSignException('Wechat Sign Verify FAILED', $result);
169
    }
170
171
    /**
172
     * Filter payload.
173
     *
174
     * @author yansongda <[email protected]>
175
     *
176
     * @param array        $payload
177
     * @param array|string $params
178
     * @param bool         $preserveNotifyUrl
179
     *
180
     * @throws InvalidArgumentException
181
     *
182
     * @return array
183
     */
184
    public static function filterPayload($payload, $params, $preserveNotifyUrl = false): array
185
    {
186
        $payload = array_merge(
187
            $payload,
188
            is_array($params) ? $params : ['out_trade_no' => $params]
189
        );
190
191
        $type = self::getInstance()->getTypeName($params['type'] ?? '');
192
193
        $payload['appid'] = self::getInstance()->getConfig($type, '');
194
195
        if (self::getInstance()->getConfig('mode', Wechat::MODE_NORMAL) === Wechat::MODE_SERVICE) {
196
            $payload['sub_appid'] = self::getInstance()->getConfig('sub_'.$type, '');
197
        }
198
199
        unset($payload['trade_type'], $payload['type']);
200
201
        if (!$preserveNotifyUrl) {
202
            unset($payload['notify_url']);
203
        }
204
205
        $payload['sign'] = self::generateSign($payload);
206
207
        return $payload;
208
    }
209
210
    /**
211
     * Generate wechat sign.
212
     *
213
     * @author yansongda <[email protected]>
214
     *
215
     * @param array $data
216
     *
217
     * @throws InvalidArgumentException
218
     *
219
     * @return string
220
     */
221
    public static function generateSign($data): string
222
    {
223
        $key = self::getInstance()->key;
224
225
        if (is_null($key)) {
226
            throw new InvalidArgumentException('Missing Wechat Config -- [key]');
227
        }
228
229
        ksort($data);
230
231
        $string = md5(self::getSignContent($data).'&key='.$key);
232
233
        Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]);
234
235
        return strtoupper($string);
236
    }
237
238
    /**
239
     * Generate sign content.
240
     *
241
     * @author yansongda <[email protected]>
242
     *
243
     * @param array $data
244
     *
245
     * @return string
246
     */
247
    public static function getSignContent($data): string
248
    {
249
        $buff = '';
250
251
        foreach ($data as $k => $v) {
252
            $buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
253
        }
254
255
        Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]);
256
257
        return trim($buff, '&');
258
    }
259
260
    /**
261
     * Decrypt refund contents.
262
     *
263
     * @author yansongda <[email protected]>
264
     *
265
     * @param string $contents
266
     *
267
     * @throws InvalidArgumentException
268
     *
269
     * @return string
270
     */
271
    public static function decryptRefundContents($contents): string
272
    {
273
        return openssl_decrypt(
274
            base64_decode($contents),
275
            'AES-256-ECB',
276
            self::getInstance()->key,
277
            OPENSSL_RAW_DATA
278
        );
279
    }
280
281
    /**
282
     * Convert array to xml.
283
     *
284
     * @author yansongda <[email protected]>
285
     *
286
     * @param array $data
287
     *
288
     * @throws InvalidArgumentException
289
     *
290
     * @return string
291
     */
292
    public static function toXml($data): string
293
    {
294
        if (!is_array($data) || count($data) <= 0) {
295
            throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
296
        }
297
298
        $xml = '<xml>';
299
        foreach ($data as $key => $val) {
300
            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
301
                                       '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
302
        }
303
        $xml .= '</xml>';
304
305
        return $xml;
306
    }
307
308
    /**
309
     * Convert xml to array.
310
     *
311
     * @author yansongda <[email protected]>
312
     *
313
     * @param string $xml
314
     *
315
     * @throws InvalidArgumentException
316
     *
317
     * @return array
318
     */
319
    public static function fromXml($xml): array
320
    {
321
        if (!$xml) {
322
            throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
323
        }
324
325
        libxml_disable_entity_loader(true);
326
327
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
328
    }
329
330
    /**
331
     * Initialize.
332
     *
333
     * @author yansongda <[email protected]>
334
     *
335
     * @param Config $config
336
     *
337
     * @throws InvalidArgumentException
338
     *
339
     * @return Support
340
     */
341
    public static function initialize(Config $config): self
342
    {
343
        return self::getInstance($config);
344
    }
345
346
    /**
347
     * Get service config.
348
     *
349
     * @author yansongda <[email protected]>
350
     *
351
     * @param null|string $key
352
     * @param null|mixed  $default
353
     *
354
     * @return mixed|null
355
     */
356 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...
357
    {
358
        if (is_null($key)) {
359
            return $this->config->all();
360
        }
361
362
        if ($this->config->has($key)) {
363
            return $this->config[$key];
364
        }
365
366
        return $default;
367
    }
368
369
    /**
370
     * Get app id according to param type.
371
     *
372
     * @author yansongda <[email protected]>
373
     *
374
     * @param string $type
375
     *
376
     * @return string
377
     */
378
    public function getTypeName($type = ''): string
379
    {
380
        $type = $type.'_id';
381
382
        if ($type == 'app') {
383
            $type = 'appid';
384
        }
385
386
        if ($type == '') {
387
            $type = 'app_id';
388
        }
389
390
        return $type;
391
    }
392
393
    /**
394
     * Set base uri.
395
     *
396
     * @author yansongda <[email protected]>
397
     *
398
     * @throws InvalidArgumentException
399
     *
400
     * @return self
401
     */
402
    private function setBaseUri(): self
403
    {
404
        switch ($this->config->get('mode', Wechat::MODE_NORMAL)) {
405
            case Wechat::MODE_DEV:
406
                self::getInstance()->baseUri = 'https://api.mch.weixin.qq.com/sandboxnew/';
407
                break;
408
409
            case Wechat::MODE_HK:
410
                self::getInstance()->baseUri = 'https://apihk.mch.weixin.qq.com/';
411
                break;
412
413
            default:
414
                break;
415
        }
416
417
        return $this;
418
    }
419
420
    /**
421
     * Set Http options.
422
     *
423
     * @author yansongda <[email protected]>
424
     *
425
     * @return self
426
     */
427 View Code Duplication
    private 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...
428
    {
429
        if ($this->config->has('http')) {
430
            $this->config->forget('http.base_uri');
431
            $this->httpOptions = $this->config->get('http');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->config->get('http') of type * is incompatible with the declared type array of property $httpOptions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
432
        }
433
434
        return $this;
435
    }
436
}
437