Completed
Push — master ( 66b6ef...2d082c )
by Songda
01:56
created

Support::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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;
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 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...
66
    {
67
        $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)];
68
        $this->config = $config;
69
70
        $this->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
     * create.
89
     *
90
     * @author yansongda <[email protected]>
91
     *
92
     * @param Config $config
93
     *
94
     * @return Support
95
     */
96 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...
97
    {
98
        if (php_sapi_name() === 'cli' || !(self::$instance instanceof self)) {
99
            self::$instance = new self($config);
100
        }
101
102
        return self::$instance;
103
    }
104
105
    /**
106
     * getInstance.
107
     *
108
     * @author yansongda <[email protected]>
109
     * @throws InvalidArgumentException
110
     *
111
     * @return Support
112
     */
113
    public static function getInstance()
114
    {
115
        if (is_null(self::$instance)) {
116
            throw new InvalidArgumentException('You Should [Create] First Before Using');
117
        }
118
119
        return self::$instance;
120
    }
121
122
    /**
123
     * clear.
124
     *
125
     * @author yansongda <[email protected]>
126
     *
127
     * @return void
128
     */
129
    public function clear()
130
    {
131
        self::$instance = null;
132
    }
133
134
    /**
135
     * Request wechat api.
136
     *
137
     * @author yansongda <[email protected]>
138
     *
139
     * @param string $endpoint
140
     * @param array  $data
141
     * @param bool   $cert
142
     *
143
     * @throws GatewayException
144
     * @throws InvalidArgumentException
145
     * @throws InvalidSignException
146
     *
147
     * @return Collection
148
     */
149
    public static function requestApi($endpoint, $data, $cert = false): Collection
150
    {
151
        Log::debug('Request To Wechat Api', [self::$instance->getBaseUri().$endpoint, $data]);
152
153
        $result = self::$instance->post(
154
            $endpoint,
155
            self::toXml($data),
156
            $cert ? [
157
                'cert'    => self::$instance->cert_client,
158
                'ssl_key' => self::$instance->cert_key,
159
            ] : []
160
        );
161
        $result = is_array($result) ? $result : self::fromXml($result);
162
163
        Log::debug('Result Of Wechat Api', $result);
164
165
        if (!isset($result['return_code']) || $result['return_code'] != 'SUCCESS' || $result['result_code'] != 'SUCCESS') {
166
            throw new GatewayException(
167
                'Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg']).($result['err_code_des'] ?? ''),
168
                $result,
169
                20000
170
            );
171
        }
172
173
        if (strpos($endpoint, 'mmpaymkttransfers') !== false || self::generateSign($result) === $result['sign']) {
174
            return new Collection($result);
175
        }
176
177
        Log::warning('Wechat Sign Verify FAILED', $result);
178
179
        throw new InvalidSignException('Wechat Sign Verify FAILED', $result);
180
    }
181
182
    /**
183
     * Filter payload.
184
     *
185
     * @author yansongda <[email protected]>
186
     *
187
     * @param array        $payload
188
     * @param array|string $params
189
     * @param bool         $preserveNotifyUrl
190
     *
191
     * @throws InvalidArgumentException
192
     *
193
     * @return array
194
     */
195
    public static function filterPayload($payload, $params, $preserveNotifyUrl = false): array
196
    {
197
        $type = self::getTypeName($params['type'] ?? '');
198
199
        $payload = array_merge(
200
            $payload,
201
            is_array($params) ? $params : ['out_trade_no' => $params]
202
        );
203
        $payload['appid'] = self::$instance->getConfig($type, '');
204
205
        if (self::$instance->getConfig('mode', Wechat::MODE_NORMAL) === Wechat::MODE_SERVICE) {
206
            $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, '');
207
        }
208
209
        unset($payload['trade_type'], $payload['type']);
210
        if (!$preserveNotifyUrl) {
211
            unset($payload['notify_url']);
212
        }
213
214
        $payload['sign'] = self::generateSign($payload);
215
216
        return $payload;
217
    }
218
219
    /**
220
     * Generate wechat sign.
221
     *
222
     * @author yansongda <[email protected]>
223
     *
224
     * @param array $data
225
     *
226
     * @throws InvalidArgumentException
227
     *
228
     * @return string
229
     */
230
    public static function generateSign($data): string
231
    {
232
        $key = self::$instance->key;
233
234
        if (is_null($key)) {
235
            throw new InvalidArgumentException('Missing Wechat Config -- [key]');
236
        }
237
238
        ksort($data);
239
240
        $string = md5(self::getSignContent($data).'&key='.$key);
241
242
        Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]);
243
244
        return strtoupper($string);
245
    }
246
247
    /**
248
     * Generate sign content.
249
     *
250
     * @author yansongda <[email protected]>
251
     *
252
     * @param array $data
253
     *
254
     * @return string
255
     */
256
    public static function getSignContent($data): string
257
    {
258
        $buff = '';
259
260
        foreach ($data as $k => $v) {
261
            $buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
262
        }
263
264
        Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]);
265
266
        return trim($buff, '&');
267
    }
268
269
    /**
270
     * Decrypt refund contents.
271
     *
272
     * @author yansongda <[email protected]>
273
     *
274
     * @param string $contents
275
     *
276
     * @return string
277
     */
278
    public static function decryptRefundContents($contents): string
279
    {
280
        return openssl_decrypt(
281
            base64_decode($contents),
282
            'AES-256-ECB',
283
            md5(self::$instance->key),
284
            OPENSSL_RAW_DATA
285
        );
286
    }
287
288
    /**
289
     * Convert array to xml.
290
     *
291
     * @author yansongda <[email protected]>
292
     *
293
     * @param array $data
294
     *
295
     * @throws InvalidArgumentException
296
     *
297
     * @return string
298
     */
299
    public static function toXml($data): string
300
    {
301
        if (!is_array($data) || count($data) <= 0) {
302
            throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
303
        }
304
305
        $xml = '<xml>';
306
        foreach ($data as $key => $val) {
307
            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
308
                                       '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
309
        }
310
        $xml .= '</xml>';
311
312
        return $xml;
313
    }
314
315
    /**
316
     * Convert xml to array.
317
     *
318
     * @author yansongda <[email protected]>
319
     *
320
     * @param string $xml
321
     *
322
     * @throws InvalidArgumentException
323
     *
324
     * @return array
325
     */
326
    public static function fromXml($xml): array
327
    {
328
        if (!$xml) {
329
            throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
330
        }
331
332
        libxml_disable_entity_loader(true);
333
334
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
335
    }
336
337
    /**
338
     * Get service config.
339
     *
340
     * @author yansongda <[email protected]>
341
     *
342
     * @param null|string $key
343
     * @param null|mixed  $default
344
     *
345
     * @return mixed|null
346
     */
347 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...
348
    {
349
        if (is_null($key)) {
350
            return $this->config->all();
351
        }
352
353
        if ($this->config->has($key)) {
354
            return $this->config[$key];
355
        }
356
357
        return $default;
358
    }
359
360
    /**
361
     * Get app id according to param type.
362
     *
363
     * @author yansongda <[email protected]>
364
     *
365
     * @param string $type
366
     *
367
     * @return string
368
     */
369
    public static function getTypeName($type = ''): string
370
    {
371
        switch ($type) {
372
            case '':
373
                $type = 'app_id';
374
                break;
375
            case 'app':
376
                $type = 'appid';
377
                break;
378
            default:
379
                $type = $type.'_id';
380
        }
381
382
        return $type;
383
    }
384
385
    /**
386
     * Get Base Uri.
387
     *
388
     * @author yansongda <[email protected]>
389
     *
390
     * @return string
391
     */
392
    public function getBaseUri()
393
    {
394
        return $this->baseUri;
395
    }
396
397
    /**
398
     * Set Http options.
399
     *
400
     * @author yansongda <[email protected]>
401
     *
402
     * @return self
403
     */
404 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...
405
    {
406
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
407
            $this->config->forget('http.base_uri');
408
            $this->httpOptions = $this->config->get('http');
409
        }
410
411
        return $this;
412
    }
413
}
414