PHP服务端集成微信APP支付以及回调

时光怂恿深爱的人放手 提交于 2019-12-04 23:20:10

上一篇说到支付宝APP支付,说到微信APP支付相对复杂一点,复杂在于微信支付参数的两次加密返回支付参数;

至于其他和支付宝处理流程都相同

流程:客户端提供数据 ->  服务端处理生成支付参数返回给客户端调起支付  ->  支付成功  ->  微信回调结果  ->  接受回调修改订单状态

微信官方文档也说的比较清楚,微信APP开发者文档

首先,新建一个微信支付类,命名为appWxPay_class.php ,定义一些支付常量

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


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];
}


//生成随机数并返回
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;
    }
这里附上微信官方给的一个签名校验工具:签名校验

//为微信官方返回的数据类型做准备
    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;                       
              }             
          }  
      }     
  }
 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!