PHP服務端集成微信APP支付以及回調

上一篇說到支付寶APP支付,說到微信APP支付相對複雜一點,複雜在於微信支付參數的兩次加密返回支付參數;php

至於其餘和支付寶處理流程都相同nginx

流程:客戶端提供數據 ->  服務端處理生成支付參數返回給客戶端調起支付  ->  支付成功  ->  微信回調結果  ->  接受回調修改訂單狀態算法

微信官方文檔也說的比較清楚,微信APP開發者文檔sql

首先,新建一個微信支付類,命名爲appWxPay_class.php ,定義一些支付常量json

const appid      ="";  
const mch_id     ="";
const key        ="";
const trade_type = "APP";
const notify_url = "";api


post方法用於請求數組

//創建請求
public function http_post($url='',$post_data=array(),$header=array(),$timeout=30) { 
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳過證書檢查
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 從證書中檢查SSL加密算法是否存在
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
       
      $response = curl_exec($ch);
      curl_close($ch);
      return $response;
}微信

獲取客戶端ip
//獲取客戶端ip
public function get_client_ip($type = 0) {
        $type       =  $type ? 1 : 0;
        static $ip  =   NULL;
        if ($ip !== NULL) return $ip[$type];
        if($_SERVER['HTTP_X_REAL_IP']){//nginx 代理模式下,獲取客戶端真實IP
            $ip=$_SERVER['HTTP_X_REAL_IP'];     
        }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//客戶端的ip
            $ip     =   $_SERVER['HTTP_CLIENT_IP'];
        }elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//瀏覽當前頁面的用戶計算機的網關
            $arr    =   explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos    =   array_search('unknown',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip     =   trim($arr[0]);
        }elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip     =   $_SERVER['REMOTE_ADDR'];//瀏覽當前頁面的用戶計算機的ip地址
        }else{
            $ip=$_SERVER['REMOTE_ADDR'];
        }
        // IP地址合法驗證
        $long = sprintf("%u",ip2long($ip));
        $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
        return $ip[$type];
}app


//生成隨機數並返回
public function getNonceStr() {
        $code = "";
        for ($i=0; $i > 10; $i++) { 
            $code .= mt_rand(1000);        //獲取隨機數
        }
        $nonceStrTemp = md5($code);
        $nonce_str = mb_substr($nonceStrTemp, 5,37);      //MD5加密後截取32位字符
        return $nonce_str;
}
/**
     * 獲取參數簽名;
     * @param  Array  要傳遞的參數數組
     * @return String 經過計算獲得的簽名;
     */
    private function getSign($params) {        
 
        ksort($params);        //將參數數組按照參數名ASCII碼從小到大排序
 
        foreach ($params as $key => $item) {
            if (!empty($item)) {         //剔除參數值爲空的參數
                $newArr[] = $key.'='.$item;     // 整合新的參數數組
            }
        }        
        $stringA = implode("&", $newArr);         //使用 & 符號鏈接參數   
        $stringSignTemp = $stringA."&key=".self::key;        //拼接key    
        $stringSignTemp = MD5($stringSignTemp);       //將字符串進行MD5加密
        $sign = strtoupper($stringSignTemp);      //將全部字符轉換爲大寫
        return $sign;
    }
這裏附上微信官方給的一個簽名校驗工具:簽名校驗curl

//爲微信官方返回的數據類型作準備
    public function arrToString($params){
 
        ksort($params);        //將參數數組按照參數名ASCII碼從小到大排序
 
        foreach ($params as $key => $item) {
            if (!empty($item)) {         //剔除參數值爲空的參數
                $newArr[] = $key.'='.$item;     // 整合新的參數數組
            }
        }        
        $stringA = implode("&", $newArr);    
 
        return $stringA;
 
    }
/**
     * 拼裝請求的數據
     * @return  String 拼裝完成的數據
     */
    public function setSendData($data=array()) {
        $sTpl = "<xml>
                        <appid><![CDATA[%s]]></appid>
                        <body><![CDATA[%s]]></body>
                        <mch_id><![CDATA[%s]]></mch_id>
                        <nonce_str><![CDATA[%s]]></nonce_str>
                        <notify_url><![CDATA[%s]]></notify_url>
                        <out_trade_no><![CDATA[%s]]></out_trade_no>
                        <spbill_create_ip><![CDATA[%s]]></spbill_create_ip>
                        <total_fee><![CDATA[%d]]></total_fee>
                        <trade_type><![CDATA[%s]]></trade_type>
                        <sign><![CDATA[%s]]></sign>
                    </xml>";                          //xml數據模板
 
        $nonce_str = self::getNonceStr();        //調用隨機字符串生成方法獲取隨機字符串
        $data['appid']  = self::appid;
        $data['mch_id'] = self::mch_id;
        $data['nonce_str'] = $nonce_str;
        $data['spbill_create_ip'] = self::get_client_ip();
        $data['notify_url'] = self::notify_url;
        $data['trade_type'] = self::trade_type;      //將參與簽名的數據保存到數組
 
        // 注意:以上幾個參數是追加到$data中的,$data中應該同時包含開發文檔中要求必填的剔除sign之外的全部數據
        $sign = self::getSign($data);        //獲取簽名   
 
        $data = sprintf($sTpl,self::appid,$data['body'],self::mch_id,$nonce_str,self::notify_url,$data['out_trade_no'],$data['spbill_create_ip'],$data['total_fee'],self::trade_type,$sign);
          
        //生成xml數據格式       
        return $data; 
    }


//獲取支付參數
    public function sendRequest($data=array()) {
        $post_data = self::setSendData($data);            //獲取要發送的數據
 
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        $content = self::http_post($url,$post_data);        
 
        // $objectxml = (array)simplexml_load_string($content,'SimpleXMLElement',LIBXML_NOCDATA); //將微信返回的XML 轉換成數組
        $objectxml = self::xmlToObject($content);
 
        return $objectxml;      //返回請求到的數據
    }

    public function setNotifyUrl($url) {
        if (is_string($url)) {
            $this->notify_url = $url;
        }
    }
 
    /**
     * 獲取客戶端支付信息
     * @param  Array $data 參與簽名的信息數組
     * @return String       簽名字符串
     */
    public function getClientPay($data) {
        $sign = self::getSign($data);        // 生成簽名並返回
        return $sign;
    }

//生成第二次簽名,返回給客戶端
    public function secondRequest($firstObj){
 
        if ($firstObj->return_code == 'FAIL') {
            return $firstObj->return_msg;            // 若是微信返回錯誤碼爲FAIL,則表明請求失敗,返回失敗信息;
        } else {
            //若是上一次請求成功,那麼咱們將返回的數據從新拼裝,進行第二次簽名
            // $resignData = array(
            //     'appid'     =>  $firstObj->appid,
            //     'partnerId' =>    $firstObj->mch_id,
            //     'prepayId'  =>  $firstObj->prepay_id,
            //     'nonceStr'  =>  $firstObj->nonce_str,
            //     'timeStamp' =>  time(),
            //     'package'   =>  'Sign=WXPay'
            // );
 
            $resignData = array(
                'appid'     =>  $firstObj->appid,
                'partnerid' =>    $firstObj->mch_id,
                'prepayid'  =>  $firstObj->prepay_id,
                'noncestr'  =>  $firstObj->nonce_str,
                'timestamp' =>  time(),
                'package'   =>  'Sign=WXPay'
            );
 
            //二次簽名;
            $sign = self::getClientPay($resignData);
            
            $resignData['sign'] = $sign;
 
            // print_r($resignData);
            
            return $resignData;
        }
    }

//xml格式轉object
    public function xmlToObject($xmlStr) {
        if (!is_string($xmlStr) || empty($xmlStr)) {
            return false;
        }
        // 因爲解析xml的時候,即便被解析的變量爲空,依然不會報錯,會返回一個空的對象,因此,咱們這裏作了處理,當被解析的變量不是字符串,或者該變量爲空,直接返回false
        $postObj = simplexml_load_string($xmlStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        $postObj = json_decode(json_encode($postObj));
        //將xml數據轉換成對象返回
        return $postObj;
    }
 
    //獲取回調參數
    public function getNotifyData() {
        $postXml = $GLOBALS["HTTP_RAW_POST_DATA"];    // 接受通知參數;
        if (empty($postXml)) {
            return false;
        }
        $postObj = self::xmlToObject($postXml);      // 調用解析方法,將xml數據解析成對象
        if ($postObj === false) {
            return false;
        }
        if (!empty($postObj->return_code)) {
            if ($postObj->return_code == 'FAIL') {
                return false;
            }
        }
        return $postObj;          // 返回結果對象;
    }
 
 
    /**
     * 查詢訂單狀態
     * @param  Curl   $curl         工具類
     * @param  string $out_trade_no 訂單號
     * @return xml               訂單查詢結果
     */
    public function queryOrder($out_trade_no) {
        $nonce_str = self::getNonceStr();
        $data = array(
            'appid'        => self::appid,
            'mch_id'       => self::mch_id,
            'out_trade_no' => $out_trade_no,
            'nonce_str'    => $nonce_str,
        );
        $sign = self::getSign($data);
        $xml_data = '<xml>
                       <appid>%s</appid>
                       <mch_id>%s</mch_id>
                       <nonce_str>%s</nonce_str>
                       <out_trade_no>%s</out_trade_no>
                       <sign>%s</sign>
                    </xml>';
        $xml_data = sprintf($xml_data,self::appid,self::mch_id,$nonce_str,$out_trade_no,$sign);    
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";        
        $content = self::http_post($url,$xml_data);
        return $content;
    }

至此,準備工做結束,下面來調用了
//爲客戶端支付準備
      $data = array(
          "body"=> "商品購買",  //商品信息
          "total_fee"=> $goodsResult['final_sum']*100,   //支付價格
          "out_trade_no"=>$order_id,  //本身的訂單id,支付成功回調修改訂單狀態
      );
      //第一次加密
      $objectxml         = appWxPay_class::sendRequest($data);
      //第二次加密
      $wxPayArr          = appWxPay_class::secondRequest($objectxml);
      //返回給客戶端的支付參數,這裏我轉成了用&拼接,和支付寶同樣的,微信默認是數組
      $this->wxPayString = appWxPay_class::arrToString($wxPayArr);


支付成功接受微信回調參數,修改訂單操做

  //微信app支付回調   function appWxPayStatu(){         $postXml = file_get_contents("php://input");    // 接受通知參數;       $postObj = appWxPay_class::xmlToObject($postXml);      // 調用解析方法,將xml數據解析成對象         //追加日誌       file_put_contents(dirname(__file__)."/log/wxPay.log",$postXml.PHP_EOL,FILE_APPEND);        //確認支付成功           if ($postObj->return_code=="SUCCESS" && $postObj->result_code=="SUCCESS") {                      //修改支付訂單狀態           $order_id = $postObj->out_trade_no;           $orderGoodsDB = new IModel('order_goods');           $result = $orderGoodsDB->query("pay_status = 0 and order_id = ".$order_id);             //是否存在該訂單           if ($result) {                      $sqlData = array(                   "pay_status" => 1,                   "pay_time"   => ITime::getDateTime(),                   "pay_type"   => 6,               );               $orderGoodsDB->setData($sqlData);               $res = $orderGoodsDB->update('order_id = '.$order_id);                    //修改爲功               if ($res) {                                     //添加交易記錄                              $model = new IModel("niuScore_trans");                   $arr=array(                       "user_id"    => $orderArr["user_id"],                       "reduce"     => $obj->total_fee,                       "detail"     => "微信支付",                       "remain"     => 0,                       "type"       => "3",                       "trans_time" => ITime::getDateTime(),                   );                   $model->setData($arr);                   $model->add();                     $reply = "<xml>                       <return_code><![CDATA[SUCCESS]]></return_code>                       <return_msg><![CDATA[OK]]></return_msg>                     </xml>";                   echo $reply;      // 向微信後臺返回結果。                   exit;                                      }                        }         }        }  

相關文章
相關標籤/搜索