原生 PHP 實現支付寶 App 第三方登入獲取 使用者資訊

bigbug-gg發表於2019-06-20

App 專案要求實現第三方 微信 和 支付寶 登入,微信可以直接在 App 端完成認證拿到使用者資訊,支付寶則需要後端獲取。

  1. 服務端先拿到 App端 呼叫 支付寶SDK 所需要的 infoStr
  2. App端 透過 infoStr 獲得使用者 授權code
  3. 服務端透過 授權code 拿到請求 token
  4. 服務端透過 token 獲得使用者資訊

在這之前,支付寶介面對接流程你應該有所瞭解。

  1. 建立 RSA2 方法:獲得 sign

    /**
     * enRSA2 RSA加密
     * 
     * @param String $data
     * @return String
     */
    private function enRSA2($data)
    {
        $str = chunk_split(trim($this->private_key), 64, "\n");
        $key = "-----BEGIN RSA PRIVATE KEY-----\n$str-----END RSA PRIVATE KEY-----\n";
        // $key = file_get_contents(storage_path('rsa_private_key.pem')); 為檔案時這樣引入
        $signature = '';
        $signature = openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256)?base64_encode($signature):NULL;
        return $signature;
    }
  2. 建立一個 Get 引數拼接方法,保證符合支付寶加簽字串要求:

    /**
     * myHttpBuildQuery
     * 之所以不用 自帶函式 `http_build_query` 
     * 是因為格式化的時間帶有  ‘:’  會被轉換成十六進位制 utf-8 碼
     * 
     * @param Array
     * @return String
     */
    private function myHttpBuildQuery($dataArr)
    {
        ksort($dataArr);
        $signStr = '';
        foreach ($dataArr as $key => $val) {
            if (empty($signStr)) {
                $signStr = $key.'='.$val;
            } else {
                $signStr .= '&'.$key.'='.$val;
            }
        }
        return $signStr;  
    }
  3. 給到APP端需要的 infoStr:

    /**
     * InfoStr APP登入需要的的infostr
     * 
     * @return String
     */
    public function infoStr()
    {
        $infoStr = http_build_query([
            'apiname' => 'com.alipay.account.auth',
            'method' => 'alipay.open.auth.sdk.code.get',
            'app_id' => $this->app_id,
            'app_name' => 'mc',
            'biz_type' => 'openservice',
            'pid' => $this->pid,
            'product_id' => 'APP_FAST_LOGIN',
            'scope' => 'kuaijie',
            'target_id' => mt_rand(999, 99999), //商戶標識該次使用者授權請求的ID,該值在商戶端應保持唯一
            'auth_type' => 'AUTHACCOUNT', // AUTHACCOUNT代表授權;LOGIN代表登入
            'sign_type' => 'RSA2',
        ]);
        $infoStr .= '&sign='.$this->enRSA2($infoStr);
        return $infoStr;
    }
  4. 拿到使用者資訊:

     /**
     * AlipayToken 獲得使用者 請求token, 透過它獲得 使用者資訊
     * 
     * 需要按照支付寶加簽流程來。
     */
    public function userInfo($app_auth_token)
    {
        $infoArr = [
            'method' => 'alipay.system.oauth.token',
            'app_id' => $this->app_id,
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'code' => $app_auth_token,
            'grant_type' => 'authorization_code',
        ];

        $signStr = $this->myHttpBuildQuery($infoArr);
        $sign = urlencode($this->enRSA2($signStr));
        $qureStr = $signStr.'&sign='.$sign;

        $res = new Client();
        $body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
        $body = json_decode($body);
        if (!isset($body->alipay_system_oauth_token_response->access_token)) {
            return '介面異常';
        } else {
            $autho_token = $body->alipay_system_oauth_token_response->access_token;
            $userinfo = $this->aliPayUserInfo($autho_token);
            return $userinfo; // 或則 返回 json_encode($userinfo) 根據實際需求來 
        }
    }

    /**
     * AliPayUserInfo 透過 token 獲取使用者資訊
     */
    private function aliPayUserInfo($autho_token)
    {
        $infoArr = [
            'method' => 'alipay.user.info.share',
            'app_id' => $this->app_id,
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'auth_token' => $autho_token,
        ];

        $signStr = $this->myHttpBuildQuery($infoArr);
        $sign = urlencode($this->enRSA2($signStr));
        $qureStr = $signStr.'&sign='.$sign;

        $res = new Client();
        $body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
        $body = json_decode($body);
        if (!isset($body->alipay_user_info_share_response)) {
            return '介面異常';
        }
        $body = $body->alipay_user_info_share_response;
        return $body;
    }
<?php 

// 使用 Guzzle 做請求操作
use GuzzleHttp\Client;

// 支付寶APP 第三方登入
// 特點:相比微信,支付寶所有敏感資訊都在服務端完成, 保證了安全
//
// 流程:
// 1.服務端到APP infoStr 
// 2.APP端 透過infoStr 獲得 auth_code 
// 3.服務端透過 auth_code 拿到請求 token
// 4.服務端透過 token 獲得使用者資訊

class AliPayUser{

    protected $app_id = '支付寶app_id';
    protected $pid = '支付寶pid';
    protected $private_key = '你的私鑰';

    /**
     * InfoStr APP登入需要的的infostr
     * 
     * @return String
     */
    public function infoStr()
    {
        $infoStr = http_build_query([
            'apiname' => 'com.alipay.account.auth',
            'method' => 'alipay.open.auth.sdk.code.get',
            'app_id' => $this->app_id,
            'app_name' => 'mc',
            'biz_type' => 'openservice',
            'pid' => $this->pid,
            'product_id' => 'APP_FAST_LOGIN',
            'scope' => 'kuaijie',
            'target_id' => mt_rand(999, 99999), //商戶標識該次使用者授權請求的ID,該值在商戶端應保持唯一
            'auth_type' => 'AUTHACCOUNT', // AUTHACCOUNT代表授權;LOGIN代表登入
            'sign_type' => 'RSA2',
        ]);
        $infoStr .= '&sign='.$this->enRSA2($infoStr);
        return $infoStr;
    }

     /**
     * AlipayToken 獲得使用者 請求token, 透過它獲得 使用者資訊
     * 
     * 需要按照支付寶加簽流程來。
     */
    public function userInfo($app_auth_token)
    {
        $infoArr = [
            'method' => 'alipay.system.oauth.token',
            'app_id' => $this->app_id,
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'code' => $app_auth_token,
            'grant_type' => 'authorization_code',
        ];

        $signStr = $this->myHttpBuildQuery($infoArr);
        $sign = urlencode($this->enRSA2($signStr));
        $qureStr = $signStr.'&sign='.$sign;

        $res = new Client();
        $body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
        $body = json_decode($body);
        if (!isset($body->alipay_system_oauth_token_response->access_token)) {
            return '介面異常';
        } else {
            $autho_token = $body->alipay_system_oauth_token_response->access_token;
            $userinfo = $this->aliPayUserInfo($autho_token);
            return $userinfo; // 或則 返回 json_encode($userinfo) 根據實際需求來 
        }
    }

    /**
     * AliPayUserInfo 透過 token 獲取使用者資訊
     */
    private function aliPayUserInfo($autho_token)
    {
        $infoArr = [
            'method' => 'alipay.user.info.share',
            'app_id' => $this->app_id,
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'auth_token' => $autho_token,
        ];

        $signStr = $this->myHttpBuildQuery($infoArr);
        $sign = urlencode($this->enRSA2($signStr));
        $qureStr = $signStr.'&sign='.$sign;

        $res = new Client();
        $body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
        $body = json_decode($body);
        if (!isset($body->alipay_user_info_share_response)) {
            return '介面異常';
        }
        $body = $body->alipay_user_info_share_response;
        return $body;
    }

    /**
     * enRSA2 RSA加密
     * 
     * @param String $data
     * @return String
     */
    private function enRSA2($data)
    {
        $str = chunk_split(trim($this->private_key), 64, "\n");
        $key = "-----BEGIN RSA PRIVATE KEY-----\n$str-----END RSA PRIVATE KEY-----\n";
        // $key = file_get_contents(storage_path('rsa_private_key.pem')); 為檔案時這樣引入
        $signature = '';
        $signature = openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256)?base64_encode($signature):NULL;
        return $signature;
    }

    /**
     * myHttpBuildQuery 返回一個 http Get 傳引數組
     * 之所以不用 自帶函式 http_build_query 時間帶 ‘:’ 會被轉換
     * 
     * @param Array
     * @return String
     */
    private function myHttpBuildQuery($dataArr)
    {
        ksort($dataArr);
        $signStr = '';
        foreach ($dataArr as $key => $val) {
            if (empty($signStr)) {
                $signStr = $key.'='.$val;
            } else {
                $signStr .= '&'.$key.'='.$val;
            }
        }
        return $signStr;  
    }
}
  1. 注意:這份程式碼是從原有專案扒出來,主要是為有此需求的開發人員提供參考,並未測試是否能直接使用,請自行測試。
  2. 之所以不用支付寶php_SDK,是因為需求有限:只獲取使用者的資訊,沒必要。
  3. 程式碼有不合理的地方還請提出來,大家互相學習。
本作品採用《CC 協議》,轉載必須註明作者和本文連結
現在做的任何事,都是未來的基石,請相信自己的能力。

相關文章