支付開發填坑記之支付寶

支付寶在全部支付方式中最好開發的了,由於文檔比較清晰,並且開發起來也比較簡單。所以,支付寶的坑是相對較少的。
原文地址php

APP支付

APP支付步驟爲:html

  1. 獲取支付寶的配置信息。
  2. 生成商家訂單信息。
  3. 根據訂單信息生成待校驗數據
  4. 生成請求給支付寶的加密字符串
  5. 將待校驗數據和加密字符串拼接,返回給APP。
  6. APP將獲得的數據請求支付寶客戶端進行支付。

因爲APP支付是由APP去調起支付寶支付,因此服務端須要作的事情就是將請求參數封裝好以後返回APP便可。node

  1. 獲取支付寶的配置信息。
    支付時須要的配置信息有:json

    • key: 交易安全校驗碼。
    • app_id:支付寶分配給開發者的應用ID。
  2. 生成商家訂單信息。
    這個步驟由商家自行生成。支付寶那邊只須要知道的訂單信息爲:api

    • subject: 必填。商品的標題/交易標題/訂單標題/訂單關鍵字等。
    • total_amount: 必填。訂單價格。
    • out_trade_no: 必填。商戶網站惟一訂單號。
    • body: 非必填。交易的具體描述信息。
  3. 根據訂單信息生成待校驗數據
    APP支付的詳細請求參數: 點擊查看
    APP-支付寶
  4. 生成請求給支付寶的加密字符串數組

    $sign = $alipaySubmit->buildRequestParaForApp($para_token);

    其中, buildRequestParaForApp 的實現爲:安全

    1. 對待簽名參數數組排序
    /**
     * 對數組排序
     * @param $para 排序前的數組
     * return 排序後的數組
     */
    function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }
    1. 生成簽名結果(阿里推薦的是RSA2的簽名方式,這裏項目用的是RSA)
    /**
     * RSA簽名
     * @param $data 待簽名數據
     * @param $private_key_path 商戶私鑰文件路徑
     * return 簽名結果
     */
    function rsaSign($data, $private_key_path) {
        $priKey = file_get_contents($private_key_path);
        $res = openssl_get_privatekey($priKey);
        openssl_sign($data, $sign, $res);
        openssl_free_key($res);
        //base64編碼
        $sign = base64_encode($sign);
        return $sign;
    }
  5. 將待校驗數據和加密字符串拼接,返回給APP。服務器

    $url = "";
    foreach ($para_token as $key => $value) {
        $url .= $key."=".urlencode($value)."&";
    }
    return $url."sign=".urlencode($sign);
  6. APP將獲得的數據請求支付寶客戶端進行支付。
    APP端將拼接好的字符串拿去請求支付寶客戶端便可調起支付寶進行支付。拼接好的字符串大體以下圖所示:
    APP-支付寶-APP

網頁版支付

網頁版支付步驟爲:數據結構

  1. 設置支付寶的配置信息。
  2. 向支付寶申請新訂單,獲取支付token。
  3. 攜帶token進行訂單支付。

網頁版的支付寶支付相對於APP調起支付寶要複雜,由於網頁支付時,須要屢次請求支付寶服務器獲取支付的必要參數。app

  1. 設置支付寶配置信息。

    /**調用受權接口alipay.wap.trade.create.direct獲取受權碼token**/
            
        //返回格式
        private  $format = "";
        //必填,不須要修改
        
        //版本
        private $v = "";
        //必填,不須要修改
        
        //請求號
        private $req_id = "";
        //必填,須保證每次請求都是惟一
        
        //**req_data詳細信息**
        
        //服務器異步通知頁面路徑
        private $notify_url = "";
        //需http://格式的完整路徑,不容許加?id=123這類自定義參數
        
        //頁面跳轉同步通知頁面路徑
        private $call_back_url = "";
        //需http://格式的完整路徑,不容許加?id=123這類自定義參數
        
        //賣家支付寶帳戶
        private $seller_email = "";
        //必填
        
        //商戶訂單號
        private $out_trade_no = "";
        //商戶網站訂單系統中惟一訂單號,必填
        
        //訂單名稱
        private $subject = "";
        //必填
        
        //付款金額
        private $total_fee = "";
        //必填
        
        //請求業務參數詳細
        private $req_data = "";
        //必填
        
        //配置
        private $alipay_config = array();
        
    /************************************************************/
  2. 向支付寶申請新訂單,並獲取訂單的token。
    請求訂單token須要的req_data

    請求token的service爲: alipay.wap.trade.create.direct

    1. 構造參數:

      $para_token = array(
          "service" => "alipay.wap.trade.create.direct",
          //  合做者身份(partner ID)
          "partner" => trim($this->alipay_config['partner']),
          //  APP使用的是RSA,網頁版使用的是MD5
          "sec_id" => trim($this->alipay_config['sign_type']),
          //  返回的數據格式
          "format"    => $this->format,
          //  版本號?
          "v" => $this->v,
          //  惟一的請求號
          "req_id"    => $this->req_id,
          //  請求參數
          "req_data"  => $req_data,
          //  字符集,通常爲utf8便可。
          "_input_charset"    => trim(strtolower($this->alipay_config['input_charset']))
      );
    2. 將構造好的請求參數,進行處理,字典排序,拼接字符串,簽名:

      $para_filter = paraFilter($para_temp);
      $para_sort = argSort($para_filter);
      $mysign = $this->buildRequestMysign($para_sort);
      //簽名結果與簽名方式加入請求提交參數組中
      $para_sort['sign'] = $mysign;
      return $para_sort;

      處理:過濾值爲空的數據,過濾簽名類型和簽名。

      function paraFilter($para) {
          $para_filter = array();
          while (list ($key, $val) = each ($para)) {
              if($key == "sign" || $key == "sign_type" || $val == "")continue;
              else    $para_filter[$key] = $para[$key];
          }
          return $para_filter;
      }

      字典排序:

      /**
       * 對數組排序
       * @param $para 排序前的數組
*/
    function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }
    ```
    簽名:
    ```php
    /**
     * 生成簽名結果
     * @param $para_sort 已排序要簽名的數組
     * return 簽名結果字符串
     */
    function buildRequestMysign($para_sort) {
        //把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串
        $prestr = createLinkstring($para_sort);
        $mysign = "";
        switch (strtoupper(trim($this->alipay_config['sign_type']))) {
            case "MD5" :
                //  MD5直接將密鑰拼接在字符串後面再進行MD5加密。
                $mysign = md5Sign($prestr, $this->alipay_config['key']);
                break;
            case "RSA" :
                //  RSA則是先讀取商戶的私鑰,再用該密鑰對字符串進行加密。
                $mysign = rsaSign($prestr, $this->alipay_config['private_key_path']);
                break;
            case "0001" :
                $mysign = rsaSign($prestr, $this->alipay_config['private_key_path']);
                break;
            default :
                $mysign = "";
        }
        
        return $mysign;
    }
    ```
3.  用構造好的參數請求支付寶後臺申請新訂單:

    **注意:請求時,必須帶上SSL證書。**

    ```php
    $sResult = getHttpResponsePOST($this->alipay_gateway_new, $this->alipay_config['cacert'],$request_data,trim(strtolower($this->alipay_config['input_charset'])));
    ```
    請求函數的實現:
    ```php
    /**
     * 遠程獲取數據,POST模式
     * 注意:
     * 1.使用Crul須要修改服務器中php.ini文件的設置,找到php_curl.dll去掉前面的";"就好了
     * 2.文件夾中cacert.pem是SSL證書請保證其路徑有效,目前默認路徑是:getcwd().'\\cacert.pem'
     * @param $url 指定URL完整路徑地址
     * @param $cacert_url 指定當前工做目錄絕對路徑
     * @param $para 請求的數據
     * @param $input_charset 編碼格式。默認值:空值
     * return 遠程輸出的數據
     */
    function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') {
    
        if (trim($input_charset) != '') {
            $url = $url."_input_charset=".$input_charset;
        }
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL證書認證
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//嚴格認證
        curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//證書地址
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 過濾HTTP頭
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 顯示輸出結果
        curl_setopt($curl,CURLOPT_POST,true); // post傳輸數據
        curl_setopt($curl,CURLOPT_POSTFIELDS,$para);// post傳輸數據
        $responseText = curl_exec($curl);
        //var_dump( curl_error($curl) );//若是執行curl過程當中出現異常,可打開此開關,以便查看異常內容
        curl_close($curl);
        
        return $responseText;
    }
    ```
    處理支付寶返回的數據,並獲取token。
    
    ```php
    //URLDECODE返回的信息
    $html_text = urldecode($html_text);
    //解析遠程模擬提交後返回的信息
    $para_html_text = parseResponse($html_text);
    //獲取request_token
    $request_token = $para_html_text['request_token'];
    ```
    parseResponse函數的實現:
    
    ```php
    /**
     * 解析遠程模擬提交後返回的信息
     * @param $str_text 要解析的字符串
     * @return 解析結果
     */
    function parseResponse($str_text) {
        //以「&」字符切割字符串
        $para_split = explode('&',$str_text);
        //把切割後的字符串數組變成變量與數值組合的數組
        foreach ($para_split as $item) {
            //得到第一個=字符的位置
            $nPos = strpos($item,'=');
            //得到字符串長度
            $nLen = strlen($item);
            //得到變量名
            $key = substr($item,0,$nPos);
            //得到數值
            $value = substr($item,$nPos+1,$nLen-$nPos-1);
            //放入數組中
            $para_text[$key] = $value;
        }
        
        if( ! empty ($para_text['res_data'])) {
            //解析加密部分字符串
            if($this->alipay_config['sign_type'] == '0001') {
                $para_text['res_data'] = rsaDecrypt($para_text['res_data'], $this->alipay_config['private_key_path']);
            }
            
            //token從res_data中解析出來(也就是說res_data中已經包含token的內容)
            $doc = new DOMDocument();
            $doc->loadXML($para_text['res_data']);
            $para_text['request_token'] = $doc->getElementsByTagName( "request_token" )->item(0)->nodeValue;
        }
        
        return $para_text;
    }
    ```
  1. 攜帶token進行訂單支付。

    成功請求token回來後,就能夠向支付寶發出一次支付請求。

    一樣構造請求數據:

    //業務詳細只須要攜帶步驟2的token便可。
    $req_data = '<auth_and_execute_req><request_token>' . $request_token . '</request_token></auth_and_execute_req>';
    //必填
    
    //構造要請求的參數數組,無需改動
    $parameter = array(
        "service" => "alipay.wap.auth.authAndExecute",
        //  合做者身份(partner ID)
        "partner" => trim($this->alipay_config['partner']),
        //  簽名類型
        "sec_id" => trim($this->alipay_config['sign_type']),
        //  和步驟2一致
        "format"    => $this->format,
        "v" => $this->v,
        "req_id"    => $this->req_id,
        //  業務詳細參數
        "req_data"  => $req_data,
        //  字符集,通常爲utf8.
        "_input_charset"    => trim(strtolower($this->alipay_config['input_charset']))
    );

    將這些參數,在頁面中傳送給支付寶便可發起一次支付請求。

    在PHP 中的實現就是將這些參數,渲染至HTML中,再將HTML中的表單提交便可。

    到此,網頁版的支付寶支付完成整個流程。

支付結果異步通知

在上面,咱們看到有兩個參數傳給了支付寶:

  • call_back_url: 交易成功後,支付寶頁面上「返回到商家頁面」的地址(同步回調)
  • notify_url: 交易狀態變動後,支付寶通知網站的回調地址(異步通知)
對於手機網站支付產生的交易,支付寶會根據原始支付API中傳入的異步通知地址notify_url,經過POST請求的形式將支付結果做爲參數通知到商戶系統。

對於App支付產生的交易,支付寶會根據原始支付API中傳入的異步通知地址notify_url,經過POST請求的形式將支付結果做爲參數通知到商戶系統。

支付寶異步通知官方文檔中寫的比較清楚,何時出發通知,返回什麼參數,注意事項都有,開發者能夠根據本身的狀況查看具體信息。

驗籤步驟能夠移步至這裏

這裏就簡單的用手上的項目舉例說明,支付寶通知後,後臺是如何進行驗籤和處理訂單。

public function app_notifyOp(){
    $payment_api = $this->_get_payment_api();
    $payment_config = $this->_get_payment_config();
    // 支付寶是用POST方式發送通知信息
    $callback_info = $payment_api->getNotifyInfoApp($_POST);
    if($callback_info) {
        //驗證成功
        if ($callback_info['order_state']) {
            // 若是是支付成功則改變訂單狀態
            $result = $this->_update_order($callback_info['out_trade_no'], $callback_info['trade_no']);
        }else{
            // 若是是退款成功則修改退訂的相關狀態
            $result = $this->_app_refund($callback_info['out_trade_no'], $callback_info['trade_no'], $callback_info['refund_fee']);
        }
        if($result['state']) {
            echo 'success';die;
        }
    }
    //驗證失敗
    echo "fail";die;
}
  1. 獲取支付寶通知數據
    支付寶異步通知是POST請求,返回的數據結構以下:

    {
        "total_amount": "31.00",
        "buyer_id": "ID",
        "trade_no": "TRADE_NO",
        "body": "pay_sn:580546601841783375",
        "notify_time": "2017-04-27 09:50:59",
        "subject": "580546601841783375",
        "sign_type": "RSA",
        "buyer_logon_id": "ID",
        "auth_app_id": "APPID",
        "charset": "utf-8",
        "notify_type": "trade_status_sync",
        "invoice_amount": "31.00",
        "out_trade_no": "580546601841783375_r",
        "trade_status": "TRADE_SUCCESS",
        "gmt_payment": "2017-04-27 09:50:58",
        "version": "1.0",
        "point_amount": "0.00",
        "sign": "SIGNATURE",
        "gmt_create": "2017-04-27 09:50:58",
        "buyer_pay_amount": "31.00",
        "receipt_amount": "31.00",
        "fund_bill_list": "[{&quot;amount&quot;:&quot;31.00&quot;,&quot;fundChannel&quot;:&quot;ALIPAYACCOUNT&quot;}]",
        "app_id": "APPID",
        "seller_id": "SELLERID",
        "notify_id": "8414394a1190f25edbbec9ba4b98642mem",
        "seller_email": "YOUR_ALIPAY_ACCOUNT"
    }
  2. 驗籤數據
    驗籤須要支付寶的公鑰

    驗籤和簽名的流程是同樣的,都是將全部除了 sign 之外的參數,進行字典排序,並以 key=value 的形式以 & 符號拼成字符串,再使用密鑰進行簽名,將獲得的簽名與支付寶返回的簽名進行對比,完成驗簽過程。

    function getSignVeryfy($para_temp, $sign) {
        //除去待簽名參數數組中的空值和簽名參數
        $para = paraFilter($para_temp);
        
        //對待簽名參數數組排序
        $para = argSort($para);
    
        //把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串
        $prestr = createLinkstring($para);
        $prestr = htmlspecialchars_decode($prestr);
        $isSgin = false;
        switch (strtoupper(trim($this->alipay_config['sign_type']))) {
            case "MD5" :
                $isSgin = md5Verify($prestr, $sign, $this->alipay_config['key']);
                break;
            case "RSA" :
                $isSgin = rsaVerify($prestr, trim($this->alipay_config['ali_public_key_path']), $sign);
                break;
            case "0001" :
                $isSgin = rsaVerify($prestr, trim($this->alipay_config['ali_public_key_path']), $sign);
                break;
            default :
                $isSgin = false;
        }
        logResult($log);
        
        return $isSgin;
    }

    可是這裏有個坑,就是返回數據中的 fund_bill_list 是通過html轉義的(如例子中的數據: [{&quot;amount&quot;:&quot;31.00&quot;,&quot;fundChannel&quot;:&quot;ALIPAYACCOUNT&quot;}]),若是直接使用該參數進行簽名,則會致使簽名失敗。這裏就須要將字符串轉義了: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}] ,用轉義後的參數值進行簽名,經過校驗。

  3. 更改訂單狀態

驗簽完畢後,後臺就能夠根據實際狀況進行訂單狀態的更改。

完畢

祝各位程序猿在開發支付寶支付時再也不有坑,也但願支付寶在後續的更新中再也不埋雷。

相關文章
相關標籤/搜索