Completed
Push — master ( 1ec9e3...ab571a )
by Songda
03:58
created

src/Gateways/Wechat/Support.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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