PHP APP端支付寶支付

       應業務需求,作了支付寶支付和微信支付,今天分享一下手機端app支付寶支付對接流程,實際開發過程是先後端分離,前端調用後端API接口,實現功能返回數據,我所用的跨擠啊爲TP5,大體能夠分爲四步:php

       1.在螞蟻金服開放平臺建立應用,簽約商戶,生成應用公鑰和私鑰;前端

       2.配置統一下單支付參數;node

       3.整合支付寶demo類文件;數據庫

       4.建立Alipay支付類,類內建立兩個方法(alipay_app:統一下單方法和alipay_notify:支付成功異步回調方法);json

       

       第一步主要是在螞蟻金服開放平臺登陸你的支付寶帳號,接入支付功能,我的就選我的,服務商就選服務商,須要填寫一些材料,如手機號,郵箱等,完成後就能夠建立應用啦,建立應用完成後須要進行簽約,只有簽約以後你應用裏面開放的支付功能纔會生效,簽約也須要填一堆信息,簽約須要審覈,成功後你會拿到一個2088開頭partner值,這個第三步配置參數的時候須要用到,以後還要爲你的應用生成公鑰和私鑰,這點在開放平臺開發文檔中有詳細描述,下載生成祕鑰工具,選擇對應的祕鑰類型,祕鑰和公鑰必定要保存好,這裏就很少作贅述啦,到此開放平臺的準備工做就結束了。後端

 

       第二步就是整合支付寶demo文件了,我這裏已經整合好了,直接把代碼複製到兩個文件中就能夠了,一個爲支付類,一個爲通知類:api

/*此爲支付類*/
class AlipayApp{
    /**
     * 把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串
     * @param $para 須要拼接的數組
     * return 拼接完成之後的字符串
     */
    function createLinkstring($para,$showQuotes = false) {
        // $arg  = "";
        // while (list ($key, $val) = each ($para)) {
        // $arg.=$key."=".$val."&";
        // }
        ////去掉最後一個&字符
        // $arg = substr($arg,0,count($arg)-2);
        ////若是存在轉義字符,那麼去掉轉義
        // if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
        // return $arg;
        $arg  = "";
        $quotes = '';
        if($showQuotes){
            $quotes = '"';
        }
        foreach ($para as $key => $val) {
            if($arg == ''){
                $arg = $key.'='.$quotes.$val.$quotes;
            }else{
                $arg = $arg.'&'.$key.'='.$quotes.$val.$quotes;
            }
        }
        if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
        return $arg;
    }
    
    /**
     * 對數組排序
     * @param $para 排序前的數組
     * return 排序後的數組
     */
    function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }
    
    /**
     * 除去數組中的空值和簽名參數
     * @param $para 簽名參數組
     * return 去掉空值與簽名參數後的新簽名參數組
     */
    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;
    }
    
    function query_timestamp() {
        $url = $this->alipay_gateway_new."service=query_timestamp&partner=".trim(strtolower($this->alipay_config['partner']))."&_input_charset=".trim(strtolower($this->alipay_config['input_charset']));
        $encrypt_key = "";
        $doc = new DOMDocument();
        $doc->load($url);
        $itemEncrypt_key = $doc->getElementsByTagName( "encrypt_key" );
        $encrypt_key = $itemEncrypt_key->item(0)->nodeValue;
        return $encrypt_key;
    }
    
    /**
     * 寫日誌,方便測試(看網站需求,也能夠改爲把記錄存入數據庫)
     * 注意:服務器須要開通fopen配置
     * @param $word 要寫入日誌裏的文本內容 默認值:空值
     */
    function logResult($word='') {
        date_default_timezone_set("PRC");
        $fp = fopen("log.txt","a");
        flock($fp, LOCK_EX) ;
        fwrite($fp,"執行日期:".strftime("%Y%m%d%H%M%S",time())."\n".$word."\n");
        flock($fp, LOCK_UN);
        fclose($fp);
    }
    
    /**
     * 遠程獲取數據,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;
    }
    
    /**
     * 遠程獲取數據,GET模式
     * 注意:
     * 1.使用Crul須要修改服務器中php.ini文件的設置,找到php_curl.dll去掉前面的";"就好了
     * 2.文件夾中cacert.pem是SSL證書請保證其路徑有效,目前默認路徑是:getcwd().'\\cacert.pem'
     * @param $url 指定URL完整路徑地址
     * @param $cacert_url 指定當前工做目錄絕對路徑
     * return 遠程輸出的數據
     */
    function getHttpResponseGET($url,$cacert_url) {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 過濾HTTP頭
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 顯示輸出結果
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL證書認證
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//嚴格認證
        curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//證書地址
        $responseText = curl_exec($curl);
        //var_dump( curl_error($curl) );//若是執行curl過程當中出現異常,可打開此開關,以便查看異常內容
        curl_close($curl);
        return $responseText;
    }
    
    /**
     * 實現多種字符編碼方式
     * @param $input 須要編碼的字符串
     * @param $_output_charset 輸出的編碼格式
     * @param $_input_charset 輸入的編碼格式
     * return 編碼後的字符串
     */
    function charsetEncode($input,$_output_charset ,$_input_charset) {
        $output = "";
        if(!isset($_output_charset) )$_output_charset  = $_input_charset;
        if($_input_charset == $_output_charset || $input ==null ) {
            $output = $input;
        } elseif (function_exists("mb_convert_encoding")) {
            $output = mb_convert_encoding($input,$_output_charset,$_input_charset);
        } elseif(function_exists("iconv")) {
            $output = iconv($_input_charset,$_output_charset,$input);
        } else die("sorry, you have no libs support for charset change.");
        return $output;
    }
    
    /**
     * 實現多種字符解碼方式
     * @param $input 須要解碼的字符串
     * @param $_output_charset 輸出的解碼格式
     * @param $_input_charset 輸入的解碼格式
     * return 解碼後的字符串
     */
    function charsetDecode($input,$_input_charset ,$_output_charset) {
        $output = "";
        if(!isset($_input_charset) )$_input_charset  = $_input_charset ;
        if($_input_charset == $_output_charset || $input ==null ) {
            $output = $input;
        } elseif (function_exists("mb_convert_encoding")) {
            $output = mb_convert_encoding($input,$_output_charset,$_input_charset);
        } elseif(function_exists("iconv")) {
            $output = iconv($_input_charset,$_output_charset,$input);
        } else die("sorry, you have no libs support for charset changes.");
        return $output;
    }
    
    /**
     * RSA簽名
     * @param $data 待簽名數據
     * @param $private_key 商戶私鑰字符串
     * return 簽名結果
     */
    function rsaSign($data, $private_key) {
        //如下爲了初始化私鑰,保證在您填寫私鑰時無論是帶格式仍是不帶格式均可以經過驗證。
        $private_key=str_replace("-----BEGIN RSA PRIVATE KEY-----","",$private_key);
        $private_key=str_replace("-----END RSA PRIVATE KEY-----","",$private_key);
        $private_key=str_replace("\n","",$private_key);
        $private_key="-----BEGIN RSA PRIVATE KEY-----".PHP_EOL .wordwrap($private_key, 64, "\n", true). PHP_EOL."-----END RSA PRIVATE KEY-----";
        $res=openssl_get_privatekey($private_key);
        if($res)
        {
            openssl_sign($data, $sign,$res);
        }
        else {
            echo "您的私鑰格式不正確!"."<br/>"."The format of your private_key is incorrect!";
            exit();
        }
        openssl_free_key($res);
        //base64編碼
        $sign = base64_encode($sign);
        return $sign;
    }
    
    /**
     * RSA驗籤
     * @param $data 待簽名數據
     * @param $alipay_public_key 支付寶的公鑰字符串
     * @param $sign 要校對的的簽名結果
     * return 驗證結果
     */
    function rsaVerify($data, $alipay_public_key, $sign)  {
        //如下爲了初始化私鑰,保證在您填寫私鑰時無論是帶格式仍是不帶格式均可以經過驗證。
        $alipay_public_key=str_replace("-----BEGIN PUBLIC KEY-----","",$alipay_public_key);
        $alipay_public_key=str_replace("-----END PUBLIC KEY-----","",$alipay_public_key);
        $alipay_public_key=str_replace("\n","",$alipay_public_key);
        $alipay_public_key='-----BEGIN PUBLIC KEY-----'.PHP_EOL.wordwrap($alipay_public_key, 64, "\n", true) .PHP_EOL.'-----END PUBLIC KEY-----';
        $res=openssl_get_publickey($alipay_public_key);
        if($res)
        {
            $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        }
        else {
            echo "您的支付寶公鑰格式不正確!"."<br/>"."The format of your alipay_public_key is incorrect!";
            exit();
        }
        openssl_free_key($res);
        return $result;
    }
}

  

/* *
 * 類名:AlipayNotify
 * 功能:支付寶通知處理類
 * 詳細:處理支付寶各接口通知返回
 * 版本:1.0
 * 日期:2016-06-06
 * 說明:
 * 如下代碼只是爲了方便商戶測試而提供的樣例代碼,商戶能夠根據本身網站的須要,按照技術文檔編寫,並不是必定要使用該代碼。
 * 該代碼僅供學習和研究支付寶接口使用,只是提供一個參考
 *************************注意*************************
 * 調試通知返回時,可查看或改寫log日誌的寫入TXT裏的數據,來檢查通知返回是否正常
 */
class AlipayNotify {
    /**
     * HTTPS形式消息驗證地址
     */
    var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
    /**
     * HTTP形式消息驗證地址
     */
    var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
    var $alipay_config;
    
    function __construct($alipay_config){
        $this->alipay_config = $alipay_config;
    }
    
    function AlipayNotify($alipay_config) {
        $this->__construct($alipay_config);
    }
    
    /**
     * 獲取返回時的簽名驗證結果
     * @param $para_temp 通知返回來的參數數組
     * @param $sign 返回的簽名結果
     * @return 簽名驗證結果
     */
    function getSignVeryfy($para_temp, $sign) {
        $alipayapp = new \Alipayapp();
        //除去待簽名參數數組中的空值和簽名參數
        $para_filter = $alipayapp->paraFilter($para_temp);
        //對待簽名參數數組排序
        $para_sort = $alipayapp->argSort($para_filter);
        //把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串
        $prestr = $alipayapp->createLinkstring($para_sort);
        $isSgin = false;
        switch (strtoupper(trim($this->alipay_config['sign_type']))) {
            case "RSA" :
                $isSgin = $alipayapp->rsaVerify($prestr, trim($this->alipay_config['alipay_public_key']), $sign);
                break;
            default :
                $isSgin = false;
        }
        return $isSgin;
    }
    
    /**
     * 獲取遠程服務器ATN結果,驗證返回URL
     * @param $notify_id 通知校驗ID
     * @return 服務器ATN結果
     * 驗證結果集:
     * invalid命令參數不對 出現這個錯誤,請檢測返回處理中partner和key是否爲空
     * true 返回正確信息
     * false 請檢查防火牆或者是服務器阻止端口問題以及驗證時間是否超過一分鐘
     */
    function getResponse($notify_id) {
        $alipayapp = new \Alipayapp();
        $transport = strtolower(trim($this->alipay_config['transport']));
        $partner = trim($this->alipay_config['partner']);
        $veryfy_url = '';
        if($transport == 'https') {
            $veryfy_url = $this->https_verify_url;
        }
        else {
            $veryfy_url = $this->http_verify_url;
        }
        $veryfy_url = $veryfy_url."partner=" . $partner . "&notify_id=" . $notify_id;
        $responseTxt = $alipayapp->getHttpResponseGET($veryfy_url, $this->alipay_config['cacert']);
        return $responseTxt;
    }
}

  

 

       第三步配置參數:數組

        //配置參數
        public $alipay_config = array(
            //2088開頭
            'partner' => '',
            
            //商戶的私鑰,此處填寫原始私鑰去頭去尾,RSA公私鑰生成:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.nBDxfy&treeId=58&articleId=103242&docType=1
            'private_key' => '',
            
            //支付寶的公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm?keyType=partner
            'alipay_public_key' => '',
            
            //異步通知接口
            'service'=> 'mobile.securitypay.pay',
            
            //字符編碼格式 目前支持 gbk 或 utf-8
            'input_charset' => 'utf-8',
            
            //簽名方式 不需修改
            'sign_type' => 'RSA',
            
            //ca證書路徑地址,用於curl中ssl校驗
            //請保證cacert.pem文件在當前文件夾目錄中
            'cacert' =>  '/cacert.pem',
            
            //訪問模式,根據本身的服務器是否支持ssl訪問,若支持請選擇https;若不支持請選擇http
            'transport' => 'http',
        );

建立$alipa_config屬性,能夠放到你的配置文件中方便引入,也能夠直接放到Alipay類中,裏面只要前三項須要本身填寫,其他不變就好啦。服務器

    

        第四步就是建立Alipay類,類內定義兩個方法,一個是alipay_app(統一下單),alipay_app中的一些參數須要本身補全,參數介紹點我,另外一個是alipay_notify(支付成功後的異步回調)並引入第二步建立的兩個類文件,本人開發用的是TP5框架,支付類放在第三方類庫vendor目錄下面,能夠直接在控制器中用vendor()函數引入文件,例:vendor('Alipayapp.lib.alipay_notify');微信

 

        //調用統一下單接口生成預支付訂單並把數據返回給APP
        public function alipay_app(Request $request)
        {
            vendor('Alipayapp.lib.AlipayApp');
            $param = $request->param(); 
            $tade_no = $param['orderCode'];//訂單號 調用統一下單接口須要提供一個訂單號
            $order = new Order();   //實例化訂單
            $ret = $order->getOrderN2($tade_no);  //查詢訂單信息  此處爲我本身的查詢訂單信息方法,能夠替換爲你本身的
            
            $strOrg['partner']=$this->alipay_config['partner']; // 配置的partner
            $strOrg['seller_id']=$this->alipay_config['partner']; // 此處和partner值同樣便可
            $strOrg['out_trade_no']=$tade_no; // 訂單號
            $strOrg['subject']=""; // 商品的標題
            $strOrg['body']="";//商品名
            $strOrg['total_fee']=$ret['money'];// 金額
            $strOrg['notify_url']="";//回調地址,填寫回調方法的絕對路徑
            $strOrg['service']=$this->alipay_config['service'];
            $strOrg['payment_type']="1";
            $strOrg['_input_charset']="utf-8";
            $strOrg['it_b_pay']="30m";
            
            $alipay = new \Alipayapp();
            
            //將post接收到的數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串。
            $data=$alipay->createLinkstring($strOrg,true);//,true
            
            //將待簽名字符串使用私鑰簽名,且作urlencode. 注意:請求到支付寶只須要作一次urlencode.
            $rsa_sign=urlencode($alipay->rsaSign($data, $this->alipay_config['private_key']));
            
            //把簽名獲得的sign和簽名類型sign_type拼接在待簽名字符串後面。
            $data = $data.'&sign='.'"'.$rsa_sign.'"'.'&sign_type='.'"'.$this->alipay_config['sign_type'].'"';
            
            //返回給客戶端,建議在客戶端使用私鑰對應的公鑰作一次驗籤,保證不是他人傳輸。
            $datajson['mdata']=$data;
            echo json_encode($datajson);
        }

  

//支付成功後的回調方法
public function alipay_notify()
{
    vendor('Alipayapp.lib.alipay_notify');//引入支付通知類文件
    $alipayNotify = new \AlipayNotify($this->alipay_config);
    $order = new Order();   //實例化訂單
    if($alipayNotify->getResponse($_POST['notify_id']))   //判斷成功以後使用getResponse方法判斷是不是支付寶發來的異步通知。
    {
        if($alipayNotify->getSignVeryfy($_POST, $_POST['sign'])) {//使用支付寶公鑰驗籤
            //獲取支付寶的通知返回參數,可參考技術文檔中服務器異步通知參數列表
            //商戶訂單號
            $out_trade_no = $_POST['out_trade_no'];
            $ret = $order->getOrderN2($out_trade_no);  //查詢訂單信息
            $total_amount=$ret['money']; //訂單金額
            $total_fee = $_POST['total_fee']; //支付寶返回金額
            
            if($_POST['trade_status'] == 'TRADE_FINISHED') {
                //判斷該筆訂單是否在商戶網站中已經作過處理
                //若是沒有作過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序
                //若是有作過處理,不執行商戶的業務程序
                //注意:
                //退款日期超過可退款期限後(如三個月可退款),支付寶系統發送該交易狀態通知
                //請務必判斷請求時的out_trade_no、total_fee、seller_id與通知時獲取的out_trade_no、total_fee、seller_id爲一致的
                
                if($total_amount==$total_fee){
                    
                    //這裏進行數據庫操做,好比修改訂單狀態等
                
                }
                
            }else if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
                //判斷該筆訂單是否在商戶網站中已經作過處理,若是沒有作過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程
                //若是有作過處理,不執行商戶的業務程序
                //注意:
                //付款完成後,支付寶系統發送該交易狀態通知
                //請務必判斷請求時的out_trade_no、total_fee、seller_id與通知時獲取的out_trade_no、total_fee、seller_id爲一致的
                
                if($total_amount==$total_fee){
                
                    //這裏進行數據庫操做,好比修改訂單狀態等
                
                }
                
            }
            echo "success"; //請不要修改或刪除
        }else{ //驗證簽名失敗
            echo "sign fail";
        }
    }else{ //驗證是否來自支付寶的通知失敗
        echo "response fail";
    }
}

       上述getOrderN2方法是我查詢訂單信息用的,須要替換成你本身的查詢訂單方法,以上四步完成正常的話支付寶支付功能就能夠實現了,須要注意的是支付功能簡單的在本地測試是不能夠的,須要在服務器上測試,也有說用花生殼什麼的...這個我沒有研究,若是沒有成功查看報錯提示,在對接支付時有不少坑,遇到報錯不要急,逐步解決必定能夠實現支付功能的,若是本身實在解決不了的能夠找支付寶技術人員,若是你想查看支付寶異步通知時的返回值,能夠用 file_put_contents() 函數打印返回的參數,會在你指定的路徑生成一個文件,裏面就是返回的值啦。

相關文章
相關標籤/搜索