Completed
Push — master ( 6518f9...994d23 )
by Songda
01:44
created

src/Gateways/Alipay/Support.php (6 issues)

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\Exceptions\GatewayException;
6
use Yansongda\Pay\Exceptions\InvalidArgumentException;
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
    private function __construct(Config $config)
60
    {
61
        $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
62
        $this->config = $config;
63
        $this->setHttpOptions();
64
    }
65
66
    /**
67
     * __get.
68
     *
69
     * @author yansongda <[email protected]>
70
     *
71
     * @param $key
72
     *
73
     * @return mixed|null|Config
74
     */
75
    public function __get($key)
76
    {
77
        return $this->getConfig($key);
78
    }
79
80
    /**
81
     * Get Base Uri.
82
     *
83
     * @author yansongda <[email protected]>
84
     *
85
     * @return string
86
     */
87
    public function getBaseUri()
88
    {
89
        return $this->baseUri;
90
    }
91
92
    /**
93
     * Get instance.
94
     *
95
     * @author yansongda <[email protected]>
96
     *
97
     * @param Config|null $config
98
     *
99
     * @throws InvalidArgumentException
100
     *
101
     * @return self
102
     */
103 View Code Duplication
    public static function getInstance($config = null): 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...
104
    {
105
        if ((!(self::$instance instanceof self)) && is_null($config)) {
106
            throw new InvalidArgumentException('Must Initialize Support With Config Before Using');
107
        }
108
109
        if (!(self::$instance instanceof self)) {
110
            self::$instance = new self($config);
0 ignored issues
show
It seems like $config defined by parameter $config on line 103 can be null; however, Yansongda\Pay\Gateways\A...\Support::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
111
        }
112
113
        return self::$instance;
114
    }
115
116
    /**
117
     * Get Alipay API result.
118
     *
119
     * @author yansongda <[email protected]>
120
     *
121
     * @param array $data
122
     *
123
     * @throws GatewayException
124
     * @throws InvalidConfigException
125
     * @throws InvalidSignException
126
     * @throws InvalidArgumentException
127
     *
128
     * @return Collection
129
     */
130
    public static function requestApi(array $data): Collection
131
    {
132
        Log::debug('Request To Alipay Api', [self::getInstance()->getBaseUri(), $data]);
133
134
        $data = array_filter($data, function ($value) {
135
            return ($value == '' || is_null($value)) ? false : true;
136
        });
137
138
        $result = mb_convert_encoding(self::getInstance()->post('', $data), 'utf-8', 'gb2312');
139
        $result = json_decode($result, true);
140
141
        Log::debug('Result Of Alipay Api', $result);
142
143
        $method = str_replace('.', '_', $data['method']).'_response';
144
145
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
146
            throw new GatewayException(
147
                'Get Alipay API Error:'.$result[$method]['msg'].($result[$method]['sub_code'] ?? ''),
148
                $result,
149
                $result[$method]['code']
150
            );
151
        }
152
153
        if (self::verifySign($result[$method], true, $result['sign'])) {
154
            return new Collection($result[$method]);
155
        }
156
157
        Log::warning('Alipay Sign Verify FAILED', $result);
158
159
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
160
    }
161
162
    /**
163
     * Generate sign.
164
     *
165
     * @author yansongda <[email protected]>
166
     *
167
     * @param array $params
168
     *
169
     * @throws InvalidConfigException
170
     * @throws InvalidArgumentException
171
     *
172
     * @return string
173
     */
174
    public static function generateSign(array $params): string
175
    {
176
        $privateKey = self::getInstance()->private_key;
177
178
        if (is_null($privateKey)) {
179
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
180
        }
181
182 View Code Duplication
        if (Str::endsWith($privateKey, '.pem')) {
0 ignored issues
show
This code seems to be duplicated across 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...
183
            $privateKey = openssl_pkey_get_private($privateKey);
184
        } else {
185
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
186
                wordwrap($privateKey, 64, "\n", true).
187
                "\n-----END RSA PRIVATE KEY-----";
188
        }
189
190
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
191
192
        $sign = base64_encode($sign);
193
194
        Log::debug('Alipay Generate Sign Before Base64', [$params, $sign]);
195
196
        return $sign;
197
    }
198
199
    /**
200
     * Verify sign.
201
     *
202
     * @author yansongda <[email protected]>
203
     *
204
     * @param array       $data
205
     * @param bool        $sync
206
     * @param string|null $sign
207
     *
208
     * @throws InvalidConfigException
209
     * @throws InvalidArgumentException
210
     *
211
     * @return bool
212
     */
213
    public static function verifySign(array $data, $sync = false, $sign = null): bool
214
    {
215
        $publicKey = self::getInstance()->ali_public_key;
216
217
        if (is_null($publicKey)) {
218
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
219
        }
220
221 View Code Duplication
        if (Str::endsWith($publicKey, '.pem')) {
0 ignored issues
show
This code seems to be duplicated across 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...
222
            $publicKey = openssl_pkey_get_public($publicKey);
223
        } else {
224
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
225
                wordwrap($publicKey, 64, "\n", true).
226
                "\n-----END PUBLIC KEY-----";
227
        }
228
229
        $sign = $sign ?? $data['sign'];
230
231
        $toVerify = $sync ? mb_convert_encoding(json_encode($data, JSON_UNESCAPED_UNICODE), 'gb2312', 'utf-8') :
232
                            self::getSignContent($data, true);
233
234
        return openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
235
    }
236
237
    /**
238
     * Get signContent that is to be signed.
239
     *
240
     * @author yansongda <[email protected]>
241
     *
242
     * @param array $data
243
     * @param bool  $verify
244
     *
245
     * @return string
246
     */
247
    public static function getSignContent(array $data, $verify = false): string
248
    {
249
        $data = self::encoding($data, $data['charset'] ?? 'gb2312', 'utf-8');
250
251
        ksort($data);
252
253
        $stringToBeSigned = '';
254
        foreach ($data as $k => $v) {
255
            if ($verify && $k != 'sign' && $k != 'sign_type') {
256
                $stringToBeSigned .= $k.'='.$v.'&';
257
            }
258
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
259
                $stringToBeSigned .= $k.'='.$v.'&';
260
            }
261
        }
262
263
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
264
265
        return trim($stringToBeSigned, '&');
266
    }
267
268
    /**
269
     * Convert encoding.
270
     *
271
     * @author yansongda <[email protected]>
272
     *
273
     * @param string|array $data
274
     * @param string       $to
275
     * @param string       $from
276
     *
277
     * @return array
278
     */
279
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
280
    {
281
        return Arr::encoding((array) $data, $to, $from);
282
    }
283
284
    /**
285
     * Initialize.
286
     *
287
     * @author yansongda <[email protected]>
288
     *
289
     * @param Config $config
290
     *
291
     * @throws InvalidArgumentException
292
     *
293
     * @return Support
294
     */
295
    public static function initialize(Config $config): self
296
    {
297
        return self::getInstance($config);
298
    }
299
300
    /**
301
     * Get service config.
302
     *
303
     * @author yansongda <[email protected]>
304
     *
305
     * @param null|string $key
306
     * @param null|mixed  $default
307
     *
308
     * @return mixed|null
309
     */
310 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...
311
    {
312
        if (is_null($key)) {
313
            return $this->config->all();
314
        }
315
316
        if ($this->config->has($key)) {
317
            return $this->config[$key];
318
        }
319
320
        return $default;
321
    }
322
323
    /**
324
     * Set Http options.
325
     *
326
     * @author yansongda <[email protected]>
327
     *
328
     * @return self
329
     */
330 View Code Duplication
    protected 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...
331
    {
332
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
333
            $this->config->forget('http.base_uri');
334
            $this->httpOptions = $this->config->get('http');
335
        }
336
337
        return $this;
338
    }
339
}
340