瀏覽代碼

证书形式使用authCode获取支付宝授权,并获取用户基础信息

sunxiaobiao 1 年之前
父節點
當前提交
321ac21a9d
共有 1 個文件被更改,包括 166 次插入0 次删除
  1. 166 0
      app/common/repositories/user/UserRepository.php

+ 166 - 0
app/common/repositories/user/UserRepository.php

@@ -11,6 +11,8 @@
11 11
 namespace app\common\repositories\user;
12 12
 
13 13
 
14
+use alipay\aop\AlipayConfig;
15
+use alipay\aop\AopCertClient;
14 16
 use app\common\dao\BaseDao;
15 17
 use app\common\dao\user\UserDao;
16 18
 use app\common\model\merchant\order\OrderMerchant;
@@ -3321,6 +3323,170 @@ class UserRepository extends BaseRepository
3321 3323
             return $w->getMessage();
3322 3324
         }
3323 3325
     }
3326
+
3327
+    /**
3328
+     * 使用证书方式,通过 authCode 换取支付宝 AccessToken 和用户信息
3329
+     *
3330
+     * @param string $authorization_code 从 APP 获取的授权码
3331
+     * @param bool   $info               是否在获取 Token 后立即获取用户信息 (true: 获取, false: 只获取 Token)
3332
+     * @return array|string              成功时返回包含 token 或用户信息的数组,失败时返回错误信息数组或字符串
3333
+     * @throws \Exception
3334
+     */
3335
+    public function getTokenByCodeWithCert($authorization_code, $info = false)
3336
+    {
3337
+        try {
3338
+            // --- 1. 配置证书信息 ---
3339
+            // *** 请确保这里的 AppID 和 PrivateKey 是你进行【授权登录】的那个支付宝应用的 ***
3340
+            // *** 它可能与你进行【转账】的应用是同一个,也可能是不同的,请仔细核对 ***
3341
+            $app_id = "你的授权登录应用的AppID"; // 例如: 20210021XXXXXXXXXX
3342
+            $privateKey = "你的应用私钥(PKCS1格式内容或路径)"; // 从.pem文件读取 或 直接粘贴(不推荐)
3343
+
3344
+            // *** 证书文件路径 ***
3345
+            // 建议使用绝对路径或相对于项目根目录的路径,确保PHP有读取权限
3346
+            $app_cert_path = dirname(__FILE__) . '/../../../../extend/alipay/aop/cert/appCertPublicKey.crt'; // 应用公钥证书路径
3347
+            $alipay_cert_path = dirname(__FILE__) . '/../../../../extend/alipay/aop/cert/alipayCertPublicKey.crt'; // 支付宝公钥证书路径
3348
+            $alipay_root_cert_path = dirname(__FILE__) . '/../../../../extend/alipay/aop/cert/alipayRootCert.crt'; // 支付宝根证书路径
3349
+
3350
+            $url = "https://openapi.alipay.com/gateway.do"; // 支付宝网关
3351
+
3352
+            // --- 2. 初始化支付宝配置 (使用 AlipayConfig) ---
3353
+            $alipayConfig = new AlipayConfig();
3354
+            $alipayConfig->setAppId($app_id);
3355
+            $alipayConfig->setCharset("utf-8");
3356
+            $alipayConfig->setFormat("json");
3357
+            $alipayConfig->setSignType("RSA2");
3358
+            $alipayConfig->setServerUrl($url);
3359
+            $alipayConfig->setPrivateKey($privateKey); // 设置应用私钥
3360
+
3361
+            // *** 设置证书路径 ***
3362
+            $alipayConfig->setAppCertPath($app_cert_path);
3363
+            $alipayConfig->setAlipayPublicCertPath($alipay_cert_path);
3364
+            $alipayConfig->setRootCertPath($alipay_root_cert_path);
3365
+
3366
+            // --- 3. 实例化证书模式客户端 (使用 AopCertClient) ---
3367
+            $aop = new AopCertClient($alipayConfig);
3368
+            $aop->isCheckAlipayPublicCert = true; // 强烈建议启用支付宝公钥证书校验
3369
+
3370
+            // --- 4. 创建并执行获取 Token 的请求 ---
3371
+            $request = new AlipaySystemOauthTokenRequest();
3372
+            $request->setGrantType("authorization_code");
3373
+            $request->setCode($authorization_code); // 设置从 APP 获取的 authCode
3374
+
3375
+            // 执行请求
3376
+            $result = $aop->execute($request); // R 返回的是原始响应字符串,需要解码
3377
+            $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response"; // 结果节点的 key,例如 "alipay_system_oauth_token_response"
3378
+
3379
+            // 解析 JSON 响应
3380
+            $resultObj = json_decode($result);
3381
+
3382
+            // --- 5. 处理 Token 响应 ---
3383
+            if (isset($resultObj->$responseNode) && $resultObj->$responseNode->code == '10000') {
3384
+                // 成功获取 Token
3385
+                $response = $resultObj->$responseNode;
3386
+                $return_data = [
3387
+                    'code' => 200,
3388
+                    'msg' => 'Token fetched successfully',
3389
+                    'user_id' => $response->user_id,
3390
+                    'access_token' => $response->access_token,
3391
+                    'refresh_token' => $response->refresh_token, // 注意保存 refresh token 以便后续刷新
3392
+                    'expires_in' => $response->expires_in,
3393
+                    're_expires_in' => $response->re_expires_in,
3394
+                ];
3395
+                $access_token = $response->access_token; // 保存 access_token 供后续使用
3396
+
3397
+                // --- 6. (可选) 获取用户信息 ---
3398
+                if ($info) {
3399
+                    $userInfoRequest = new AlipayUserInfoShareRequest; // 注意命名空间
3400
+                    // 使用获取到的 access_token 执行请求
3401
+                    // 注意:AopCertClient 的 execute 方法第二个参数是 accessToken
3402
+                    $userInfoResult = $aop->execute($userInfoRequest, $access_token);
3403
+                    $userInfoResponseNode = str_replace(".", "_", $userInfoRequest->getApiMethodName()) . "_response"; // 例如 "alipay_user_info_share_response"
3404
+                    $userInfoResultObj = json_decode($userInfoResult);
3405
+
3406
+                    if (isset($userInfoResultObj->$userInfoResponseNode) && $userInfoResultObj->$userInfoResponseNode->code == '10000') {
3407
+                        // 成功获取用户信息
3408
+                        $userInfoResponse = $userInfoResultObj->$userInfoResponseNode;
3409
+                        // 将用户信息合并到返回结果中
3410
+                        $return_data['user_info'] = [
3411
+                            'user_id' => $userInfoResponse->user_id, // 支付宝用户ID
3412
+                            'nick_name' => $userInfoResponse->nick_name ?? null, // 昵称
3413
+                            'avatar' => $userInfoResponse->avatar ?? null,     // 头像
3414
+                            // 根据你的授权范围,还可能包含性别(gender), 省份(province), 城市(city)等
3415
+                            // 'gender' => $userInfoResponse->gender ?? null,
3416
+                            // 'province' => $userInfoResponse->province ?? null,
3417
+                            // 'city' => $userInfoResponse->city ?? null,
3418
+                        ];
3419
+                        Db::name('log')->insert(['data' => '支付宝用户信息获取成功: ' . json_encode($userInfoResponse, JSON_UNESCAPED_UNICODE)]);
3420
+                        return $return_data; // 返回包含 Token 和用户信息的完整数据
3421
+                    } else {
3422
+                        // 获取用户信息失败
3423
+                        $errorMsg = 'Failed to get user info';
3424
+                        $subMsg = '';
3425
+                        if (isset($userInfoResultObj->$userInfoResponseNode)) {
3426
+                            $errorMsg .= ': ' . $userInfoResultObj->$userInfoResponseNode->sub_msg;
3427
+                            $subMsg = $userInfoResultObj->$userInfoResponseNode->sub_msg;
3428
+                        } elseif (isset($userInfoResultObj->error_response)) {
3429
+                            $errorMsg .= ': ' . $userInfoResultObj->error_response->sub_msg;
3430
+                            $subMsg = $userInfoResultObj->error_response->sub_msg;
3431
+                        }
3432
+                        Db::name('log')->insert(['data' => '支付宝用户信息获取失败: ' . $errorMsg . ' | Raw: ' . $userInfoResult]);
3433
+                        // 即使获取用户信息失败,Token 仍然是有效的,可以选择只返回 Token 信息
3434
+                        // 或者标记用户信息获取失败
3435
+                        $return_data['user_info_error'] = $subMsg ?: $errorMsg;
3436
+                        return $return_data; // 返回 Token 信息和用户信息获取失败标记
3437
+                        // 或者直接返回错误
3438
+                        // return ['code' => 500, 'msg' => $errorMsg];
3439
+                    }
3440
+                } else {
3441
+                    // 不需要获取用户信息,直接返回 Token 数据
3442
+                    Db::name('log')->insert(['data' => '支付宝Token获取成功: ' . json_encode($response, JSON_UNESCAPED_UNICODE)]);
3443
+                    return $return_data;
3444
+                }
3445
+
3446
+            } else {
3447
+                // 获取 Token 失败
3448
+                $errorMsg = 'Failed to exchange token';
3449
+                $subMsg = '';
3450
+                if (isset($resultObj->$responseNode)) {
3451
+                    $errorMsg .= ': ' . ($resultObj->$responseNode->sub_msg ?? 'Unknown error');
3452
+                    $subMsg = $resultObj->$responseNode->sub_msg ?? '';
3453
+                } elseif (isset($resultObj->error_response)) {
3454
+                    $errorMsg .= ': ' . ($resultObj->error_response->sub_msg ?? 'Unknown error');
3455
+                    $subMsg = $resultObj->error_response->sub_msg ?? '';
3456
+                }
3457
+                Db::name('log')->insert(['data' => '支付宝Token获取失败: ' . $errorMsg . ' | Raw: ' . $result]);
3458
+                return ['code' => 500, 'msg' => $subMsg ?: $errorMsg];
3459
+            }
3460
+
3461
+        } catch (\Exception $e) { // 捕获 SDK 或其他异常
3462
+            Db::name("log")->insert(['data' => 'Alipay Token/Info Error: '.$e->getFile() . ':' . $e->getLine() . '【' . $e->getMessage() . '】']); //日志记录
3463
+            return ['code' => 500, 'msg' => 'System Error: ' . $e->getMessage()];
3464
+        }
3465
+    }
3466
+
3467
+    // 你可以在 Controller 的其他方法中调用这个函数
3468
+    public function handleAlipayCallback($authCode)
3469
+    {
3470
+        if (!$authCode) {
3471
+            return ['status' => 'error', 'message' => 'Auth code is missing'];
3472
+        }
3473
+        // 调用上面封装的方法,并请求获取用户信息
3474
+        $result = $this->getTokenByCodeWithCert($authCode, true); // 第二个参数为 true
3475
+
3476
+        // 根据 $result 处理后续逻辑 (如用户登录注册,返回 token 给 APP 等)
3477
+        if ($result['code'] == 200) {
3478
+            // 成功处理
3479
+            // $alipay_user_id = $result['user_id'];
3480
+            // $nick_name = $result['user_info']['nick_name'] ?? 'Unknown';
3481
+            // $avatar = $result['user_info']['avatar'] ?? '';
3482
+
3483
+            return ['status' => 'success', 'data' => $result];
3484
+        } else {
3485
+            // 处理失败
3486
+            return ['status' => 'error', 'message' => $result['msg']];
3487
+        }
3488
+    }
3489
+
3324 3490
     /**
3325 3491
      * @param $id
3326 3492
      * @return Form