最近写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.youkuaiyun.com/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.youkuaiyun.com/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