1. 程式人生 > >PHP APP端微信支付

PHP APP端微信支付

lencod tran HA AR post方式 創建 elseif verify nsf

前面已經寫了手機APP支付寶支付,今天再把手機APP微信支付補上,前期的準備工作在這裏就不多說了,可以參考微信支付開發文檔,一定要仔細閱讀開發文檔,可以讓你少踩點坑;準備工作完成後就是配置參數,調用統一下單接口,支付後異步回調三部曲啦;

1.我封裝好的一個支付類文件,多余的東西都去除掉了,並且把配置參數放到了這個支付類中,只需要修改Weixinpayandroid方法內的幾個參數就可以直接復制使用:

class Wxpayandroid
{
    //參數配置
    public $config = array(
                ‘appid‘ => "",    /*微信開放平臺上的應用id*/
                ‘mch_id‘ => "",   /*微信申請成功之後郵件中的商戶id*/
                ‘api_key‘ => "",    /*在微信商戶平臺上自己設定的api密鑰 32位*/
            );
    
    //服務器異步通知頁面路徑(必填)
    public $notify_url = ‘‘;
    
    //商戶訂單號(必填,商戶網站訂單系統中唯一訂單號)
    public $out_trade_no = ‘‘;
    //商品描述(必填,不填則為商品名稱)
    public $body = ‘‘;
    
    //付款金額(必填)
    public $total_fee = 0;
    
    //自定義超時(選填,支持dhmc)
    public $time_expire = ‘‘;
    
    private $WxPayHelper;
    
    public function Weixinpayandroid($total_fee,$tade_no)
    {
        $this->total_fee = intval($total_fee * 100);//訂單的金額 1元
        $this->out_trade_no = $tade_no;// date(‘YmdHis‘) . substr(time(), - 5) . substr(microtime(), 2, 5) . sprintf(‘%02d‘, rand(0, 99));//訂單號
        $this->body = ‘wxpay‘;//支付描述信息
        $this->time_expire = date(‘YmdHis‘, time() + 86400);//訂單支付的過期時間(eg:一天過期)
        $this->notify_url = "http://www.ceshi.com/notifyandroid";//異步通知URL(更改支付狀態)
        //數據以JSON的形式返回給APP
        $app_response = $this->doPay();  
        if (isset($app_response[‘return_code‘]) && $app_response[‘return_code‘] == ‘FAIL‘) {
            $errorCode = 100;
            $errorMsg = $app_response[‘return_msg‘];
            $this->echoResult($errorCode, $errorMsg);
        } else {
            $errorCode = 0;
            $errorMsg = ‘success‘;
            $responseData = array(
                ‘notify_url‘ => $this->notify_url,
                ‘app_response‘ => $app_response,
            );
            $this->echoResult($errorCode, $errorMsg, $responseData);
        }
    }
    
    //接口輸出
    function echoResult($errorCode = 0, $errorMsg = ‘success‘, $responseData = array())
    {
        $arr = array(
            ‘errorCode‘ => $errorCode,
            ‘errorMsg‘ => $errorMsg,
            ‘responseData‘ => $responseData,
        );
         exit(json_encode($arr));      //exit可以正常發送給APP json數據
        // return json_encode($arr);  //在TP5中return這個json數據,APP接收到的是null,無法正常吊起微信支付
    }
    
    function getVerifySign($data, $key) 
    {
        $String = $this->formatParameters($data, false);
        //簽名步驟二:在string後加入KEY
        $String = $String . "&key=" . $key;
        //簽名步驟三:MD5加密
        $String = md5($String);
        //簽名步驟四:所有字符轉為大寫
        $result = strtoupper($String);
        return $result;
    }
    
    function formatParameters($paraMap, $urlencode) 
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if($k=="sign"){
                continue;
            }
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar;
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }
    
    /**
     * 得到簽名
     * @param object $obj
     * @param string $api_key
     * @return string
     */
    function getSign($obj, $api_key)
    {
        foreach ($obj as $k => $v)
        {
            $Parameters[strtolower($k)] = $v;
        }
        //簽名步驟一:按字典序排序參數
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //簽名步驟二:在string後加入KEY
        $String = $String."&key=".$api_key;
        //簽名步驟三:MD5加密
        $result = strtoupper(md5($String));
        return $result;
    }
    
    /**
     * 獲取指定長度的隨機字符串
     * @param int $length
     * @return Ambigous <NULL, string>
     */
    function getRandChar($length){
       $str = null;
       $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
       $max = strlen($strPol)-1;
       for($i=0;$i<$length;$i++){
            $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介於min和max兩個數之間的一個隨機整數
       }
       return $str;
    }
    
    /**
     * 數組轉xml
     * @param array $arr
     * @return string
     */
    function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key=>$val)
        {
             if (is_numeric($val))
             {
                $xml.="<".$key.">".$val."</".$key.">"; 
             }
             else
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";  
        }
        $xml.="</xml>";
        return $xml; 
    }
    
    /**
     * 以post方式提交xml到對應的接口url
     *
     * @param string $xml  需要post的xml數據
     * @param string $url  url
     * @param bool $useCert 是否需要證書,默認不需要
     * @param int $second   url執行超時時間,默認30s
     * @throws WxPayException
     */
    function postXmlCurl($xml, $url, $second=30, $useCert=false, $sslcert_path=‘‘, $sslkey_path=‘‘)
    {
        $ch = curl_init();
        //設置超時
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch,CURLOPT_URL, $url);
        //設置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求結果為字符串且輸出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
    
        if($useCert == true){
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗
            //設置證書
            //使用證書:cert 與 key 分別屬於兩個.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,‘PEM‘);
            curl_setopt($ch,CURLOPT_SSLCERT, $sslcert_path);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,‘PEM‘);
            curl_setopt($ch,CURLOPT_SSLKEY, $sslkey_path);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //運行curl
        $data = curl_exec($ch);
         
        //返回結果
        if($data){
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
    
            curl_close($ch);
            return false;
        }
    }
    
    /**
     * 獲取當前服務器的IP
     * @return Ambigous <string, unknown>
     */
    function get_client_ip()
    {
        if (isset($_SERVER[‘REMOTE_ADDR‘])) {
            $cip = $_SERVER[‘REMOTE_ADDR‘];
        } elseif (getenv("REMOTE_ADDR")) {
            $cip = getenv("REMOTE_ADDR");
        } elseif (getenv("HTTP_CLIENT_IP")) {
            $cip = getenv("HTTP_CLIENT_IP");
        } else {
            $cip = "127.0.0.1";
        }
        return $cip;
    }
 
    /**
     * 將數組轉成uri字符串
     * @param array $paraMap
     * @param bool $urlencode
     * @return string
     */
    function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            $buff .= strtolower($k) . "=" . $v . "&";
        }
        $reqPar;
        if (strlen($buff) > 0) 
        {
            $reqPar = substr($buff, 0, strlen($buff)-1);
        }
        return $reqPar;
    }
    
    /**
     * XML轉數組
     * @param unknown $xml
     * @return mixed
     */
    function xmlToArray($xml) 
    {
        //將XML轉為array
        $array_data = json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);
        return $array_data;
    }
    public function chkParam() 
    {
        //用戶網站訂單號
        if (empty($this->out_trade_no)) {
            die(‘out_trade_no error‘);
        }   
        //商品描述
        if (empty($this->body)) {
            die(‘body error‘);
        }
        if (empty($this->time_expire)){
            die(‘time_expire error‘);
        }
        //檢測支付金額
        if (empty($this->total_fee) || !is_numeric($this->total_fee)) {
            die(‘total_fee error‘);
        }
        //異步通知URL
        if (empty($this->notify_url)) {
            die(‘notify_url error‘);
        }
        if (!preg_match("#^http:\/\/#i", $this->notify_url)) {
            $this->notify_url = "http://" . $_SERVER[‘HTTP_HOST‘] . $this->notify_url;
        }
        return true;
    }
    
    /**
     * 生成支付(返回給APP)
     * @return boolean|mixed
     */
    public function doPay() {
        //檢測構造參數
        $this->chkParam();
        return $this->createAppPara();
    }
    
    /**
     * APP統一下單
     */
    private  function createAppPara() 
    {
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        
        $data["appid"]        = $this->config[‘appid‘];//微信開放平臺審核通過的應用APPID
        $data["body"]         = $this->body;//商品或支付單簡要描述
        $data["mch_id"]       = $this->config[‘mch_id‘];//商戶號
        $data["nonce_str"]    = $this->getRandChar(32);//隨機字符串
        $data["notify_url"]   = $this->notify_url;//通知地址
        $data["out_trade_no"] = $this->out_trade_no;//商戶訂單號
        $data["spbill_create_ip"] = $this->get_client_ip();//終端IP
        $data["total_fee"]        = $this->total_fee;//總金額
        $data["time_expire"]      = $this->time_expire;//交易結束時間
        $data["trade_type"]       = "APP";//交易類型
        $data["sign"]             = $this->getSign($data, $this->config[‘api_key‘]);//簽名
        $xml        = $this->arrayToXml($data);
        $response   = $this->postXmlCurl($xml, $url);
        //將微信返回的結果xml轉成數組
        $responseArr = $this->xmlToArray($response);
        if(isset($responseArr["return_code"]) && $responseArr["return_code"]==‘SUCCESS‘){
            return  $this->getOrder($responseArr[‘prepay_id‘]);
        }
        return $responseArr;
    }
    
    /**
     * 執行第二次簽名,才能返回給客戶端使用
     * @param int $prepayId:預支付交易會話標識
     * @return array
     */
    public function getOrder($prepayId)
    {
        $data["appid"]      = $this->config[‘appid‘];
        $data["noncestr"]   = $this->getRandChar(32);
        $data["package"]    = "Sign=WXPay";
        $data["partnerid"]  = $this->config[‘mch_id‘];
        $data["prepayid"]   = $prepayId;
        $data["timestamp"]  = time();
        $data["sign"]       = $this->getSign($data, $this->config[‘api_key‘]);
        $data["packagestr"] = "Sign=WXPay";
        return $data;
    }
    
    /**
     * 異步通知信息驗證
     * @return boolean|mixed
     */
    public function verifyNotify()
    {
        $xml = isset($GLOBALS[‘HTTP_RAW_POST_DATA‘]) ? $GLOBALS[‘HTTP_RAW_POST_DATA‘] : ‘‘;  
        if(!$xml){
            return false;
        }
        $wx_back = $this->xmlToArray($xml);
        if(empty($wx_back)){
            return false;
        }
        $checkSign = $this->getVerifySign($wx_back, $this->config[‘api_key‘]);     
        if($checkSign=$wx_back[‘sign‘]){
            return $wx_back;
        }else{
            return false;
        }   
    }
}

  

2.創建控制器定義統一下單接口和支付後的異步回調接口:

  //異步通知接口
    public function notifyandroid()
    {
        $wxpayandroid = new \Wxpayandroid;     //實例化微信支付類
        $verify_result = $wxpayandroid->verifyNotify(); 
        if ($verify_result[‘return_code‘]==‘SUCCESS‘ && $verify_result[‘result_code‘]==‘SUCCESS‘) {
          //商戶訂單號
          $out_trade_no = $verify_result[‘out_trade_no‘];
          //交易號
          $trade_no     = $verify_result[‘transaction_id‘];
          //交易狀態
          $trade_status = $verify_result[‘result_code‘];
          //支付金額
          $total_fee    = $verify_result[‘total_fee‘]/100;
          //支付過期時間
          $pay_date     = $verify_result[‘time_end‘];
          $order = new Order();
          $ret = $order->getOrderN2($out_trade_no); //獲取訂單信息
          $total_amount=$ret[‘money‘];
          if ($total_amount==$total_fee) {
              // 驗證成功 修改數據庫的訂單狀態等 $result[‘out_trade_no‘]為訂單號
              //此處寫自己的邏輯代碼
          }
            exit(‘<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>‘);
        }else{
            exit(‘<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>‘);
        }
    }

    //調用統一下單接口生成預支付訂單並把數據返回給APP
    public function wxpayandroid(Request $request)
    {
        $param = $request->param(); //接收值

        $tade_no = $param[‘orderCode‘];
        $order = new Order();   //實例化訂單
        $ret = $order->getOrderN2($tade_no);  //查詢訂單信息
        $total_fee = $ret[‘money‘]; //訂單總金額
        
        $wxpayandroid = new \Wxpayandroid;     //實例化微信支付類
        $res = $wxpayandroid->Weixinpayandroid($total_fee,$tade_no);    //調用weixinpay方法
        
    }

封裝一個支付類文件,並把配置參數放到支付類內,再定義控制器創建兩個方法,這樣兩步就可以把手機APP微信支付搞定啦。

PHP APP端微信支付