微信APP支付

      最近写APP的时候重新研究了一下微信APP支付,一直也没时间总结。借着今天不算忙,趁机总结一下。

一、基本流程

1、微信官方文档图:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1

2、我们自己解释一下:

(1)用户下单—》访问统一下单接口,生成微信返回的数据。由于APP端要用这些数据再次访问微信服务器,因此需要进行二次签名,把二次签名的结果返回到APP

(2)用户跳转到微信的输入密码页面,输入密码之后,微信会服务器会访问我们的回调网址

(3)回调地址中,我们把微信传过来的数据重新生成签名,并且和原来的签名对比,如果签名一致的话,
就返回success的xml数据。这里必须要用微信官方要求的xml格式的数据

这里的支付流程和微信公众号支付很像,只不过一个是APP端,一个是公众号。
大家可以参考我写的公众号支付:http://blog.csdn.net/ljfphp/article/details/76963026

二、用代码演示过程

1、我这里Libs中引用的还是之前微信公众号支付的SDK,只不过新封装了一个WechatAppPay.php类。所以后续的很多方法都在这个类中,具体的代码请往下继续观看。

2、把微信SDK引入到我们的项目中。这边用的是laravel框架

这里写图片描述

      这边先引入微信支付的SDK,里面有我们写的一些方法,是能够用得上的。

3、用户下单—》统一下单接口

(1)我们先走自己写好的路由。处理传过来的参数(钱,商品id等),然后我们生成一个订单号,再把订单号,钱,body等信息传到统一下单接口那边

这里写图片描述

这边注意按照微信官方文档的要求,传递参数。

(2)统一下单接口代码

/**
  * 生成App所需预订单参数
  * @param string body  商品名称
  * @param string out_trade_no  订单号
  * @param int    total_fee  价格,单位分
  * @param array  APP端所需的数据
  *  示例返回
  *    [
  *      'appid' => 'wx6e9cb610a916f841',
  *      'partnerid' => '123456',
dsd  *      'noncestr' => 'ydM3lFIJzk3TFgL7',
  *      'prepayid' => 'wx201710302056228799e6af3f0129228464',
  *      'timestamp' => '1509368182',
  *      'package' => 'Sign=WXPay',
  *      'sign' => '4F51BC7BFDD5A8D005554D1D206DE12D',
  *    ]
  */
  public function getPrePayOrder($body, $out_trade_no, $total_fee){
    $request_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    $notify_url = $this->config["notify_url"];
    $onoce_str = $this->create_noncestr();
    $data["appid"] = $this->config["appid"];
    $data["body"] = $body;
    $data["mch_id"] = $this->config['mch_id'];
    $data["nonce_str"] = $onoce_str;
    $data["notify_url"] = $notify_url;
    $data["out_trade_no"] = $out_trade_no;
    $data["spbill_create_ip"] = $this->get_client_ip();
    $data["total_fee"] = $total_fee;
    $data["trade_type"] = "APP";
    $sign = $this->get_sign($data);
    $data["sign"] = $sign;
    $xml = $this->array_to_xml($data);
    $response = $this->post_xml_curl($xml, $request_url);
    // 将微信返回的结果xml转成数组
    $response = $this->xml_to_array($response);
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // 这数据要App请求微信的时候用,又因为请求微信的都需要签名
      //     所以又要签名了。这就是二次签名
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      $info['appid'] = $response['appid'];
      $info['partnerid'] = $response['mch_id'];
      $info['noncestr'] = $response['nonce_str'];
      $info['prepayid'] = $response['prepay_id'];
      $info['timestamp'] = ''. time() .'';
      $info['package'] = 'Sign=WXPay'; // 官方默认值
      // 因为这是新的要用到的数据,所以又要签名了。这就是二次签名
      $info['sign'] = $this->get_sign($info);
    // 返回APP可直接用的数据
    return $info;
  }

这里我们定义了构造函数,用来加载一些必要的参数

/**
  * 构造函数,完成初始化配置
  */
  public function __construct(){
    $this->config = [
      'appid' => env('wechat_appid'),  
      'mch_id' => env('wechat_mchid'),   
      'api_key' => env('wechat_api_key'),  
      'notify_url' => env('wechat_notify_url')
    ];
  }

解释:按照统一下单接口要求的参数,我们来一一生成这些参数,文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

特别注意:我们在调用统一接口之后,这里又进行了二次签名,因为APP客户端每次访问微信服务器都需要这个签名数据。所以我们提前进行二次签名,并且把签名后的数据返回给客户端。注意返回数据的格式需要时json格式的。一定要把数组json_encode一下。

以上的一些方法我也都给出大家

/**
  * 产生随机字符串,不长于32位
  * @param int len 随机字符串长度
  * @return string
  */
  private function create_noncestr($len = 32 ){
    $str = '0123456789qwertyuiopasdfghjklzxcvbnm';
    return substr( str_shuffle($str) , 0 , $len  );
  }

  /**
  * 以POST方式提交xml到对应的接口url
  * @param string xml  XML字符串
  * @param string url  请求的对应接口地址
  * @param int    second  超时设置
  * @param string
  */
  private function post_xml_curl($xml, $url, $second=30){
    //初始化curl     
    $ch = curl_init();
    //设置超时
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    // 这里设置代理,如果有的话
    // curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
    // curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
    //设置header
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    //post提交方式
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    //运行curl
    $data = curl_exec($ch);
    curl_close($ch);
    //返回结果
    return $data;
  }

  /**
  * 获取当前服务器的IP
  * @return string
  */
  private function get_client_ip(){
    if ($_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 = "unknown";
    }
    return $cip;
  }

  /**
  * 数组 转 XML字符串
  * @param array arr  待转的数组
  * @return string 
  */
  public function array_to_xml($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;
  }

  /**
  * XML字符串 转 数组
  * @param string xml  待转的XML
  * @return array 
  */
  public function xml_to_array($xml){
    //将XML转为array     
    $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    return $array_data;
  }

4、统一下单成功之后。用户会在APP端进行输入密码,确认支付等操作。这个时候,微信服务器会调用咱们的回调函数地址。以下是具体代码
(1)回调函数

  public function anyPayWechatCallback(Request $request){
    // 测试 价格待修改
    $xml_string = file_get_contents('php://input');
    Log::info('来自微信的通知');
    Log::info($xml_string);
    return $this->wechat->any_pay_callback($xml_string);
  }

      这里通过file_get_contents(‘php://input’);获取微信服务器自带的xml数据,关于php://input的用法,请参考我的博客:http://blog.csdn.net/ljfphp/article/details/78552961

然后我们进入回调函数的逻辑页
(2)回调函数

这里写图片描述

这边注意看标注

(3)具体的回调逻辑

 /**
  * 生成支付消息,异步通知
  * @param string xml  微信端发来的XML通知
  * @return array 返回接收通知结果false表示失败,与对应的xml 
  *  示例返回:签名验证结果
  *    失败时   ["status": false, "xml": 对应XML字符串 ]
  *    成功时   ["status": true,  "xml": 对应XML字符串 ]
  */
  public function handleOrderNotify($xml){
  //先把xml数据转变为数组格式的
    $tmp_arr = $this->xml_to_array($xml);
    //我们把微信带过来的数据进行重新签名,得到新的签名数据$sign
    $sign= $this->get_sign($tmp_arr);
    //这边是把新的$sign和微信传过来的签名进行对比。如果一致的话,代表支付成功
    if ( $sign === $tmp_arr['sign']) {
    //支付成功,此时我们需要返回成功
      $res['return_code'] = 'SUCCESS';
      $res['return_msg'] = 'OK';
      $arr['status'] = true;
    }else{
    //支付失败,我们返回失败。必须按格式
      $res['return_code'] = '';
      $res['return_msg'] = '';
      $return = ['return_code'=>'FAIL','return_msg'=>'签名失败'];
      $arr['status'] = false;
    }
    // 拼接 XML
    //把数据拼接为xml格式的。因为微信服务器只能识别xml格式数据的结果。
    $arr['xml'] = '<xml>';
    foreach($res as $k=>$v){
      $arr['xml'].='<'.$k.'><![CDATA['.$v.']]></'.$k.'>';
    }
    $arr['xml'] .= '</xml>';
    //返回拼接好的xml数据
    return $arr;
  }

      这边具体的看注释吧。需要注意的是,我们要在返回xml数据给微信服务器之前,把我们需要进行的业务逻辑都写好。进行日志操作等。

5、此时微信服务器收到我们返回给它的xml数据。一般来说,走到这一步就已经成功了。记得加上自己的业务逻辑。

      这边建议最好是先看微信给的官方文档,然后知道大致的步骤之后,再按照我的这篇博客进行支付操作。给出的代码都是经过我自己测试的,不会有什么问题。
有什么问题的话,请给我留言。谢谢

end

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁柱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值