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
|
|||
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\ApiRequested('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 |
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.