微信APP支付

来源:互联网 发布:edm邮件营销 知乎 编辑:程序博客网 时间:2024/04/30 08:48

      最近写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

原创粉丝点击