Passed
Push — master ( 4ee940...caedb6 )
by ma
01:46
created

Alipay::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * 蚂蚁金服 https://openhome.alipay.com
4
 * 支付宝第三方应用授权文档
5
 *    https://docs.open.alipay.com/20160728150111277227
6
 * 1.设置:回调地址/加签方式(RSA(SHA256)密钥)/接口内容加密方式(AES密钥)
7
 *     应用公钥(SHA256withRsa)生成方法
8
 *     1.1 在liunx 使用 openssl
9
 *     1.2 OpenSSL> genrsa -out rsa_private_key.pem   2048  #生成私钥
10
 *     1.3 OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
11
 *     1.4 OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥
12
 *     1.5 OpenSSL> exit #退出OpenSSL程序
13
 *     1.6 复制rsa_public_key.pem 中间那一段到支付宝填写(注: 不要复制头和尾)
14
 * 2.1.PC登录需要签约:	第三方应用授权/获取会员信息
15
 * 2.2.APP登录需要签约:	APP支付宝登录/获取会员信息
16
 *
17
 * 网页授权文档:https://opendocs.alipay.com/open/284/web
18
 */
19
namespace tinymeng\OAuth2\Gateways;
20
21
use tinymeng\OAuth2\Connector\Gateway;
22
use tinymeng\OAuth2\Helper\ConstCode;
23
use tinymeng\OAuth2\Helper\Str;
24
25
/**
26
 * Class Alipay
27
 * @package tinymeng\OAuth2\Gateways
28
 * @Author: TinyMeng <[email protected]>
29
 * @Created: 2018/11/9
30
 */
31
class Alipay extends Gateway
32
{
33
    const RSA_PRIVATE = 1;
34
    const RSA_PUBLIC  = 2;
35
36
    const API_BASE            = 'https://openapi.alipay.com/gateway.do';
37
    protected $AuthorizeURL   = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm';
38
39
    /**
40
     * 非必须参数。接口权限值,目前只支持 auth_user 和 auth_base 两个值。以空格分隔的权限列表,若不传递此参数,代表请求的数据访问操作权限与上次获取Access Token时一致。通过Refresh Token刷新Access Token时所要求的scope权限范围必须小于等于上次获取Access Token时授予的权限范围。
41
     * @var string
42
     */
43
    protected $scope;
44
45
    /**
46
     * 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2.
47
     * @var string
48
     */
49
    protected $sign_type = 'RSA2';
50
51
    /**
52
     * @param $config
53
     * @throws \Exception
54
     */
55
    public function __construct($config)
56
    {
57
        parent::__construct($config);
58
        $this->AccessTokenURL = static::API_BASE;
59
    }
60
61
    /**
62
     * Description:  得到跳转地址
63
     * @author: JiaMeng <[email protected]>
64
     * Updater:
65
     * @return string
66
     */
67
    public function getRedirectUrl()
68
    {
69
        //存储state
70
        $this->saveState();
71
        //登录参数
72
        $params = [
73
            'app_id'       => $this->config['app_id'],
74
            'redirect_uri' => $this->config['callback'],
75
            'scope'        => $this->config['scope'],
76
            'state'        => $this->config['state'],
77
        ];
78
79
        if ($this->config['is_sandbox'] == true) {
80
            //使用沙箱环境url
81
            $this->AuthorizeURL = str_replace("alipay", "alipaydev", $this->AuthorizeURL);
82
        }
83
84
        return $this->AuthorizeURL . '?' . http_build_query($params);
85
    }
86
87
    /**
88
     * Description:  获取当前授权用户的openid标识
89
     * @author: JiaMeng <[email protected]>
90
     * Updater:
91
     * @return mixed
92
     * @throws \Exception
93
     */
94
    public function openid()
95
    {
96
        $this->getToken();
97
98
        if (isset($this->token['openid'])) {
99
            return $this->token['openid'];
100
        } else {
101
            throw new \Exception('没有获取到支付宝用户ID!');
102
        }
103
    }
104
105
    /**
106
     * Description:  获取格式化后的用户信息
107
     * @author: JiaMeng <[email protected]>
108
     * Updater:
109
     * @return array
110
     */
111
    public function userInfo()
112
    {
113
        $result = $this->getUserInfo();
114
115
        $userInfo = [
116
            'open_id'  => $this->openid(),
117
            'union_id'  => $this->token['user_id'],
118
            'access_token'=> $this->token['access_token'] ?? '',
119
            'channel' => ConstCode::TYPE_ALIPAY,
120
            'nickname'    => $result['nick_name'],
121
            'gender'  => isset($result['gender']) ? $this->getGender($result['gender']) : ConstCode::GENDER,
122
            'avatar'  => $result['avatar'],
123
        ];
124
        $userInfo['type'] = ConstCode::getTypeConst($userInfo['channel'],$this->type);
125
        return $userInfo;
126
    }
127
128
    /**
129
     * Description:  获取原始接口返回的用户信息
130
     * @author: JiaMeng <[email protected]>
131
     * Updater:
132
     * @return mixed
133
     * @throws \Exception
134
     */
135
    public function getUserInfo()
136
    {
137
        if($this->type == 'app'){//App登录
138
            if(!isset($_REQUEST['access_token']) ){
139
                throw new \Exception("AliPay APP登录 需要传输access_token参数! ");
140
            }
141
            $this->token['access_token'] = $_REQUEST['access_token'];
142
        }else {
143
            /** 获取token信息 */
144
            $this->getToken();
145
        }
146
147
        $params = [
148
            'app_id'     => $this->config['app_id'],
149
            'method'     => 'alipay.user.info.share',
150
            'charset'    => 'UTF-8',
151
            'sign_type'  => $this->sign_type,
152
            'timestamp'  => date("Y-m-d H:i:s"),
153
            'version'    => '1.0',
154
            'auth_token' => $this->token['access_token'],
155
        ];
156
        $params['sign'] = $this->signature($params);
157
158
        $data = $this->post(self::API_BASE, $params);
159
        $data = mb_convert_encoding($data, 'utf-8', 'gbk');
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type true; however, parameter $string of mb_convert_encoding() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
        $data = mb_convert_encoding(/** @scrutinizer ignore-type */ $data, 'utf-8', 'gbk');
Loading history...
160
        $result =  json_decode($data, true);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

160
        $result =  json_decode(/** @scrutinizer ignore-type */ $data, true);
Loading history...
161
        return $result['alipay_user_info_share_response'];
162
    }
163
164
    /**
165
     * Description:  重写 获取的AccessToken请求参数
166
     * @author: JiaMeng <[email protected]>
167
     * Updater:
168
     * @return array
169
     */
170
    protected function accessTokenParams()
171
    {
172
        $params = [
173
            'app_id'     => $this->config['app_id'],
174
            'method'     => 'alipay.system.oauth.token',
175
            'charset'    => 'UTF-8',
176
            'sign_type'  => $this->sign_type,
177
            'timestamp'  => date("Y-m-d H:i:s"),
178
            'version'    => '1.0',
179
            'grant_type' => $this->config['grant_type'],
180
            'code'       => $this->getCode(),
181
        ];
182
        $params['sign'] = $this->signature($params);
183
        return $params;
184
    }
185
186
    /**
187
     * Description:  支付宝签名
188
     * @author: JiaMeng <[email protected]>
189
     * Updater:
190
     * @param array $data
191
     * @return string
192
     * @throws \Exception
193
     */
194
    private function signature($data = [])
195
    {
196
        ksort($data);
197
        $str = Str::buildParams($data);
198
199
        $rsaKey = $this->getRsaKeyVal(self::RSA_PRIVATE);
200
        $res    = openssl_get_privatekey($rsaKey);
201
        if ($res !== false) {
202
            $sign = '';
203
            openssl_sign($str, $sign, $res, OPENSSL_ALGO_SHA256);
204
            openssl_free_key($res);
205
            return base64_encode($sign);
206
        }
207
        throw new \Exception('支付宝RSA私钥不正确');
208
    }
209
210
    /**
211
     * Description:  获取密钥
212
     * @author: JiaMeng <[email protected]>
213
     * Updater:
214
     * @param int $type
215
     * @return string
216
     * @throws \Exception
217
     */
218
    private function getRsaKeyVal($type = self::RSA_PUBLIC)
219
    {
220
        if ($type === self::RSA_PUBLIC) {
221
            $keyname = 'pem_public';
222
            $header  = '-----BEGIN PUBLIC KEY-----';
223
            $footer  = '-----END PUBLIC KEY-----';
224
        } else {
225
            $keyname = 'pem_private';
226
            $header  = '-----BEGIN RSA PRIVATE KEY-----';
227
            $footer  = '-----END RSA PRIVATE KEY-----';
228
        }
229
        $rsa = $this->config[$keyname];
230
        if (is_file($rsa)) {
231
            $rsa = file_get_contents($rsa);
232
        }
233
        if (empty($rsa)) {
234
            throw new \Exception('支付宝RSA密钥未配置');
235
        }
236
        $rsa    = str_replace([PHP_EOL, $header, $footer], '', $rsa);
237
        $rsaVal = $header . PHP_EOL . chunk_split($rsa, 64, PHP_EOL) . $footer;
238
        return $rsaVal;
239
    }
240
241
    /**
242
     * Description:  解析access_token方法
243
     * @author: JiaMeng <[email protected]>
244
     * Updater:
245
     * @param $token
246
     * @return mixed
247
     * @throws \Exception
248
     */
249
    protected function parseToken($token)
250
    {
251
        $token = mb_convert_encoding($token, 'utf-8', 'gbk');
252
        $data  = json_decode($token, true);
0 ignored issues
show
Bug introduced by
It seems like $token can also be of type array; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

252
        $data  = json_decode(/** @scrutinizer ignore-type */ $token, true);
Loading history...
253
254
        if (isset($data['alipay_system_oauth_token_response'])) {
255
            $data           = $data['alipay_system_oauth_token_response'];
256
            $data['openid'] = $data['user_id'];
257
            return $data;
258
        } else {
259
            throw new \Exception("获取支付宝 ACCESS_TOKEN 出错:{$token}");
260
        }
261
    }
262
}
263