Completed
Push — master ( 994d23...0182e7 )
by Songda
01:44
created

Support::create()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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