Completed
Push — master ( 80f9c1...347ac3 )
by Songda
19:24
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
        $result = json_decode($result, true);
134
135
        Events::dispatch(Events::API_REQUESTED, new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
136
137
        $method = str_replace('.', '_', $data['method']).'_response';
138
139
        if (!isset($result['sign']) || $result[$method]['code'] != '10000') {
140
            throw new GatewayException(
141
                'Get Alipay API Error:'.$result[$method]['msg'].($result[$method]['sub_code'] ?? ''),
142
                $result,
143
                $result[$method]['code']
144
            );
145
        }
146
147
        if (self::verifySign($result[$method], true, $result['sign'])) {
148
            return new Collection($result[$method]);
149
        }
150
151
        Events::dispatch(Events::SIGN_FAILED, new Events\SignFailed('Alipay', '', $result));
152
153
        throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
154
    }
155
156
    /**
157
     * Generate sign.
158
     *
159
     * @author yansongda <[email protected]>
160
     *
161
     * @param array $params
162
     *
163
     * @throws InvalidConfigException
164
     *
165
     * @return string
166
     */
167
    public static function generateSign(array $params): string
168
    {
169
        $privateKey = self::$instance->private_key;
170
171
        if (is_null($privateKey)) {
172
            throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
173
        }
174
175 View Code Duplication
        if (Str::endsWith($privateKey, '.pem')) {
176
            $privateKey = openssl_pkey_get_private($privateKey);
177
        } else {
178
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
179
                wordwrap($privateKey, 64, "\n", true).
180
                "\n-----END RSA PRIVATE KEY-----";
181
        }
182
183
        openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
184
185
        $sign = base64_encode($sign);
186
187
        Log::debug('Alipay Generate Sign', [$params, $sign]);
188
189
        return $sign;
190
    }
191
192
    /**
193
     * Verify sign.
194
     *
195
     * @author yansongda <[email protected]>
196
     *
197
     * @param array       $data
198
     * @param bool        $sync
199
     * @param string|null $sign
200
     *
201
     * @throws InvalidConfigException
202
     *
203
     * @return bool
204
     */
205
    public static function verifySign(array $data, $sync = false, $sign = null): bool
206
    {
207
        $publicKey = self::$instance->ali_public_key;
208
209
        if (is_null($publicKey)) {
210
            throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
211
        }
212
213 View Code Duplication
        if (Str::endsWith($publicKey, '.pem')) {
214
            $publicKey = openssl_pkey_get_public($publicKey);
215
        } else {
216
            $publicKey = "-----BEGIN PUBLIC KEY-----\n".
217
                wordwrap($publicKey, 64, "\n", true).
218
                "\n-----END PUBLIC KEY-----";
219
        }
220
221
        $sign = $sign ?? $data['sign'];
222
223
        $toVerify = $sync ? mb_convert_encoding(json_encode($data, JSON_UNESCAPED_UNICODE), 'gb2312', 'utf-8') :
224
                            self::getSignContent($data, true);
225
226
        return openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256) === 1;
227
    }
228
229
    /**
230
     * Get signContent that is to be signed.
231
     *
232
     * @author yansongda <[email protected]>
233
     *
234
     * @param array $data
235
     * @param bool  $verify
236
     *
237
     * @return string
238
     */
239
    public static function getSignContent(array $data, $verify = false): string
240
    {
241
        $data = self::encoding($data, $data['charset'] ?? 'gb2312', 'utf-8');
242
243
        ksort($data);
244
245
        $stringToBeSigned = '';
246
        foreach ($data as $k => $v) {
247
            if ($verify && $k != 'sign' && $k != 'sign_type') {
248
                $stringToBeSigned .= $k.'='.$v.'&';
249
            }
250
            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
251
                $stringToBeSigned .= $k.'='.$v.'&';
252
            }
253
        }
254
255
        Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
256
257
        return trim($stringToBeSigned, '&');
258
    }
259
260
    /**
261
     * Convert encoding.
262
     *
263
     * @author yansongda <[email protected]>
264
     *
265
     * @param string|array $data
266
     * @param string       $to
267
     * @param string       $from
268
     *
269
     * @return array
270
     */
271
    public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
272
    {
273
        return Arr::encoding((array) $data, $to, $from);
274
    }
275
276
    /**
277
     * Get service config.
278
     *
279
     * @author yansongda <[email protected]>
280
     *
281
     * @param null|string $key
282
     * @param null|mixed  $default
283
     *
284
     * @return mixed|null
285
     */
286 View Code Duplication
    public function getConfig($key = null, $default = null)
287
    {
288
        if (is_null($key)) {
289
            return $this->config->all();
290
        }
291
292
        if ($this->config->has($key)) {
293
            return $this->config[$key];
294
        }
295
296
        return $default;
297
    }
298
299
    /**
300
     * Get Base Uri.
301
     *
302
     * @author yansongda <[email protected]>
303
     *
304
     * @return string
305
     */
306
    public function getBaseUri()
307
    {
308
        return $this->baseUri;
309
    }
310
311
    /**
312
     * Set Http options.
313
     *
314
     * @author yansongda <[email protected]>
315
     *
316
     * @return self
317
     */
318 View Code Duplication
    protected function setHttpOptions(): self
319
    {
320
        if ($this->config->has('http') && is_array($this->config->get('http'))) {
321
            $this->config->forget('http.base_uri');
322
            $this->httpOptions = $this->config->get('http');
323
        }
324
325
        return $this;
326
    }
327
}
328