Completed
Push — master ( 026505...b42306 )
by Songda
02:02
created

src/Gateways/Alipay/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\Alipay;
4
5
use Yansongda\Pay\Events;
6
use Yansongda\Pay\Exceptions\GatewayException;
7
use Yansongda\Pay\Exceptions\InvalidConfigException;
8
use Yansongda\Pay\Exceptions\InvalidSignException;
9
use Yansongda\Pay\Gateways\Alipay;
10
use Yansongda\Pay\Log;
11
use Yansongda\Supports\Arr;
12
use Yansongda\Supports\Collection;
13
use Yansongda\Supports\Config;
14
use Yansongda\Supports\Str;
15
use Yansongda\Supports\Traits\HasHttpRequest;
16
17
/**
18
 * @author yansongda <[email protected]>
19
 *
20
 * @property string app_id alipay app_id
21
 * @property string ali_public_key
22
 * @property string private_key
23
 * @property array http http options
24
 * @property string mode current mode
25
 * @property array log log options
26
 */
27
class Support
28
{
29
    use HasHttpRequest;
30
31
    /**
32
     * Alipay gateway.
33
     *
34
     * @var string
35
     */
36
    protected $baseUri;
37
38
    /**
39
     * Config.
40
     *
41
     * @var Config
42
     */
43
    protected $config;
44
45
    /**
46
     * Instance.
47
     *
48
     * @var Support
49
     */
50
    private static $instance;
51
52
    /**
53
     * Bootstrap.
54
     *
55
     * @author yansongda <[email protected]>
56
     *
57
     * @param Config $config
58
     */
59 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...
60
    {
61
        $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
62
        $this->config = $config;
63
64
        $this->setHttpOptions();
65
    }
66
67
    /**
68
     * __get.
69
     *
70
     * @author yansongda <[email protected]>
71
     *
72
     * @param $key
73
     *
74
     * @return mixed|null|Config
75
     */
76
    public function __get($key)
77
    {
78
        return $this->getConfig($key);
79
    }
80
81
    /**
82
     * create.
83
     *
84
     * @author yansongda <[email protected]>
85
     *
86
     * @param Config $config
87
     *
88
     * @return Support
89
     */
90 View Code Duplication
    public static function create(Config $config)
91
    {
92
        if (php_sapi_name() === 'cli' || !(self::$instance instanceof self)) {
93
            self::$instance = new self($config);
94
        }
95
96
        return self::$instance;
97
    }
98
99
    /**
100
     * clear.
101
     *
102
     * @author yansongda <[email protected]>
103
     *
104
     * @return void
105
     */
106
    public function clear()
107
    {
108
        self::$instance = null;
109
    }
110
111
    /**
112
     * Get Alipay API result.
113
     *
114
     * @author yansongda <[email protected]>
115
     *
116
     * @param array $data
117
     *
118
     * @throws GatewayException
119
     * @throws InvalidConfigException
120
     * @throws InvalidSignException
121
     *
122
     * @return Collection
123
     */
124
    public static function requestApi(array $data): Collection
125
    {
126
        Events::dispatch(Events::API_REQUESTING, new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
127
128
        $data = array_filter($data, function ($value) {
129
            return ($value == '' || is_null($value)) ? false : true;
130
        });
131
132
        $result = mb_convert_encoding(self::$instance->post('', $data), 'utf-8', 'gb2312');
133
134
        $result = json_decode($result, true);
135
136
        Events::dispatch(Events::API_REQUESTED, new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
137
138
        return self::processingApiResult($data, $result);
139
    }
140
141
    /**
142
     * Generate sign.
143
     *
144
     * @author yansongda <[email protected]>
145
     *
146
     * @param array $params
147
     *
148
     * @throws InvalidConfigException
149
     *
150
     * @return string
151
     */
152
    public static function generateSign(array $params): string
153
    {
154
        $privateKey = self::$instance->private_key;
155
156
        if (is_null($privateKey)) {
157
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
158
        }
159
160 View Code Duplication
        if (Str::endsWith($privateKey, '.pem')) {
161
            $privateKey = openssl_pkey_get_private($privateKey);
162
        } else {
163
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
164
                wordwrap($privateKey, 64, "\n", true).
165
                "\n-----END RSA PRIVATE KEY-----";
166
        }
167
168
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
169
170
        $sign = base64_encode($sign);
171
172
        Log::debug('Alipay Generate Sign', [$params, $sign]);
173
174
        return $sign;
175
    }
176
177
    /**
178
     * Verify sign.
179
     *
180
     * @author yansongda <[email protected]>
181
     *
182
     * @param array       $data
183
     * @param bool        $sync
184
     * @param string|null $sign
185
     *
186
     * @throws InvalidConfigException
187
     *
188
     * @return bool
189
     */
190
    public static function verifySign(array $data, $sync = false, $sign = null): bool
191
    {
192
        $publicKey = self::$instance->ali_public_key;
193
194
        if (is_null($publicKey)) {
195
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
196
        }
197
198 View Code Duplication
        if (Str::endsWith($publicKey, '.pem')) {
199
            $publicKey = openssl_pkey_get_public($publicKey);
200
        } else {
201
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
202
                wordwrap($publicKey, 64, "\n", true).
203
                "\n-----END PUBLIC KEY-----";
204
        }
205
206
        $sign = $sign ?? $data['sign'];
207
208
        $toVerify = $sync ? mb_convert_encoding(json_encode($data, JSON_UNESCAPED_UNICODE), 'gb2312', 'utf-8') :
209
                            self::getSignContent($data, true);
210
211
        return openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
212
    }
213
214
    /**
215
     * Get signContent that is to be signed.
216
     *
217
     * @author yansongda <[email protected]>
218
     *
219
     * @param array $data
220
     * @param bool  $verify
221
     *
222
     * @return string
223
     */
224
    public static function getSignContent(array $data, $verify = false): string
225
    {
226
        $data = self::encoding($data, $data['charset'] ?? 'gb2312', 'utf-8');
227
228
        ksort($data);
229
230
        $stringToBeSigned = '';
231
        foreach ($data as $k => $v) {
232
            if ($verify && $k != 'sign' && $k != 'sign_type') {
233
                $stringToBeSigned .= $k.'='.$v.'&';
234
            }
235
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
236
                $stringToBeSigned .= $k.'='.$v.'&';
237
            }
238
        }
239
240
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
241
242
        return trim($stringToBeSigned, '&');
243
    }
244
245
    /**
246
     * Convert encoding.
247
     *
248
     * @author yansongda <[email protected]>
249
     *
250
     * @param string|array $data
251
     * @param string       $to
252
     * @param string       $from
253
     *
254
     * @return array
255
     */
256
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
257
    {
258
        return Arr::encoding((array) $data, $to, $from);
259
    }
260
261
    /**
262
     * Get service config.
263
     *
264
     * @author yansongda <[email protected]>
265
     *
266
     * @param null|string $key
267
     * @param null|mixed  $default
268
     *
269
     * @return mixed|null
270
     */
271 View Code Duplication
    public function getConfig($key = null, $default = null)
272
    {
273
        if (is_null($key)) {
274
            return $this->config->all();
275
        }
276
277
        if ($this->config->has($key)) {
278
            return $this->config[$key];
279
        }
280
281
        return $default;
282
    }
283
284
    /**
285
     * Get Base Uri.
286
     *
287
     * @author yansongda <[email protected]>
288
     *
289
     * @return string
290
     */
291
    public function getBaseUri()
292
    {
293
        return $this->baseUri;
294
    }
295
296
    /**
297
     * processingApiResult.
298
     *
299
     * @author yansongda <[email protected]>
300
     *
301
     * @param $data
302
     * @param $result
303
     *
304
     * @throws GatewayException
305
     * @throws InvalidConfigException
306
     * @throws InvalidSignException
307
     *
308
     * @return Collection
309
     */
310
    protected static function processingApiResult($data, $result): Collection
311
    {
312
        $method = str_replace('.', '_', $data['method']).'_response';
313
314
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
315
            throw new GatewayException(
316
                'Get Alipay API Error:'.$result[$method]['msg'].
317
                    (isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''),
318
                $result
319
            );
320
        }
321
322
        if (self::verifySign($result[$method], true, $result['sign'])) {
323
            return new Collection($result[$method]);
324
        }
325
326
        Events::dispatch(Events::SIGN_FAILED, new Events\SignFailed('Alipay', '', $result));
327
328
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
329
    }
330
331
    /**
332
     * Set Http options.
333
     *
334
     * @author yansongda <[email protected]>
335
     *
336
     * @return self
337
     */
338 View Code Duplication
    protected function setHttpOptions(): self
339
    {
340
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
341
            $this->config->forget('http.base_uri');
342
            $this->httpOptions = $this->config->get('http');
343
        }
344
345
        return $this;
346
    }
347
}
348