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