Alipay   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 100
dl 0
loc 231
rs 10
c 0
b 0
f 0
wmc 19

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A openid() 0 8 2
A getRedirectUrl() 0 18 2
A getRsaKeyVal() 0 21 4
A accessTokenParams() 0 14 1
A signature() 0 14 2
A parseToken() 0 11 2
A userInfo() 0 18 2
A getUserInfo() 0 27 3
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\Exception\OAuthException;
23
use tinymeng\OAuth2\Helper\ConstCode;
24
use tinymeng\OAuth2\Helper\Str;
25
26
/**
27
 * Class Alipay
28
 * @package tinymeng\OAuth2\Gateways
29
 * @Author: TinyMeng <[email protected]>
30
 * @Created: 2018/11/9
31
 */
32
class Alipay extends Gateway
33
{
34
    const RSA_PRIVATE = 1;
35
    const RSA_PUBLIC  = 2;
36
37
    const API_BASE            = 'https://openapi.alipay.com/gateway.do';
38
    protected $AuthorizeURL   = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm';
39
40
    /**
41
     * 非必须参数。接口权限值,目前只支持 auth_user 和 auth_base 两个值。以空格分隔的权限列表,若不传递此参数,代表请求的数据访问操作权限与上次获取Access Token时一致。通过Refresh Token刷新Access Token时所要求的scope权限范围必须小于等于上次获取Access Token时授予的权限范围。
42
     * @var string
43
     */
44
    protected $scope;
45
46
    /**
47
     * 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2.
48
     * @var string
49
     */
50
    protected $sign_type = 'RSA2';
51
52
    /**
53
     * @param $config
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 OAuthException
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 OAuthException('没有获取到支付宝用户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['union_id']??'',
118
            'channel' => ConstCode::TYPE_ALIPAY,
119
            'nickname'    => $result['nick_name'],
120
            'gender'  => isset($result['gender']) ? $this->getGender($result['gender']) : ConstCode::GENDER,
121
            'avatar'  => $result['avatar'],
122
            // 拓展字段
123
            'access_token'  => $this->token['access_token']??'',
124
            'user_id'  => $this->token['user_id'],
125
            'native'   => $result,
126
        ];
127
        $userInfo['type'] = ConstCode::getTypeConst($userInfo['channel'],$this->type);
128
        return $userInfo;
129
    }
130
131
    /**
132
     * Description:  获取原始接口返回的用户信息
133
     * @author: JiaMeng <[email protected]>
134
     * Updater:
135
     * @return mixed
136
     * @throws OAuthException
137
     */
138
    public function getUserInfo()
139
    {
140
        if($this->type == 'app'){//App登录
141
            if(!isset($_REQUEST['access_token']) ){
142
                throw new OAuthException("AliPay APP登录 需要传输access_token参数! ");
143
            }
144
            $this->token['access_token'] = $_REQUEST['access_token'];
145
        }else {
146
            /** 获取token信息 */
147
            $this->getToken();
148
        }
149
150
        $params = [
151
            'app_id'     => $this->config['app_id'],
152
            'method'     => 'alipay.user.info.share',
153
            'charset'    => 'UTF-8',
154
            'sign_type'  => $this->sign_type,
155
            'timestamp'  => date("Y-m-d H:i:s"),
156
            'version'    => '1.0',
157
            'auth_token' => $this->token['access_token'],
158
        ];
159
        $params['sign'] = $this->signature($params);
160
161
        $data = $this->post(self::API_BASE, $params);
162
        $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

162
        $data = mb_convert_encoding(/** @scrutinizer ignore-type */ $data, 'utf-8', 'gbk');
Loading history...
163
        $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

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

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