
微信开发-微信小程序支付(PHP实现)
本文用于让读者快速上手微信小程序支付
提前准备
1:appid
2:商户号
3:商户密钥
大致的流程:就是小程序发起https请求将启动时获得的openId传递
给后端的处理程序,返回微信函数wx.requestPayment必要的字段,然后填进去,调用函数wx.requestPayment即可
首先我们来看看微信开发者文档中的wx.requestPayment需要的必要字段
,共有5个,都是需要与我们的后端进行通信,后端再生成订单,一系列的验证打包数据进行填入,所以这里着重讲后端的处理过程,前端小程序就调用程序,填入参数就完事了。
后端实现生成订单(PHP)
①初始化配置(initWx.php)
//接受参数 $openid=$_POST['openid']; //配置 $appid='wxxxxxxxxx'; //appid $secret= 'xxxxxxxx'; //app密钥 $key = 'xxxxxxxx'; //商户密钥 $mch_id ='12345678'; //商户id $fee='1'; //支付金额,单位(分) //url $notifyURL='https://xxx/notify.php'; //回调程序地址,用于用户支付成功后,微信会与这个notify文件进行通信,告诉结果 $unifiedorderURL='https://api.mch.weixin.qq.com/pay/unifiedorder';//统一订单地址 $description='xxx消费'; //商品号,10位现在时间,22位随机数,自己随便创建,不长于32位 $out_trade_no=time(); $out_trade_no.=WxPay::createNoncestr(22); $weixinpay=new WxPay($appid,$openid,$mch_id,$key,$out_trade_no,$description,$fee,$notifyURL,$unifiedorderURL,'JSAPI');//新建一个对象,最后一个参数JSAPI是怎么回事呢。 <img class="alignnone size-medium wp-image-494" src="http://bayaojiu.com/blog/wp-content/uploads/2018/01/微信图片_20180107135806-300x43.png" alt="" width="300" height="43" data-mce-src="http://bayaojiu.com/blog/wp-content/uploads/2018/01/微信图片_20180107135806-300x43.png">根据微微信接口说明,公众号支付,小程序支付均是JSAPI类型, $return =$weixinpay->pay();//调用对象中的pay()方法 echo json_encode($return);//将pay方法的数据返回给小程序
WxPay类里面有什么呢,不想看流程的同学可以直接跳到下面的②最终WxPay类。
——————————–流程分解
第一个肯定是将数据存储是吧
protected $appid; protected $mch_id; protected $key; protected $openid; protected $out_trade_no; protected $body; protected $total_fee; protected $notifyURL; protected $unfiedorderURL; protected $trade_type; public function __construct($appid, $openid, $mch_id, $key,$out_trade_no,$body,$total_fee,$notifyURL,$unifiedorderURL,$trade_type){ $this->appid=$appid; $this->mch_id=$mch_id; $this->openid = $openid; $this->key = $key; $this->out_trade_no = $out_trade_no; $this->body = $body; $this->total_fee = $total_fee; $this->notifyURL=$notifyURL; $this->unfiedorderURL=$unifiedorderURL; $this->trade_type=$trade_type; }
OK,构造函数写完,就开始写我们刚才所说的pay()函数了,根据微信的文档,首先,要需要进行统一下单,即不管你是扫码支付,小程序支付,公众号支付,刷卡都必须需要这个过程,统一下单的URL是就是上面的$unifiedorderURL=’https://api.mch.weixin.qq.com/pay/unifiedorder’;(统一订单地址),我们要将统一下单必要的参数传入即可
微信统一下单必要参数,共有10个
其他参数都基本具有了,随机字符串就使用字符串数组随机发生成就OK了,简单。剩下比较容易出错的就是签名,sign,其实所谓的签名就是将上面的一些数据进行键名键值对应法,就是我们HTTP传递查询部件(name=yaojiu&sex=male)这样组合而已,按照微信规定组合一串东西后,进行MD5加密,然后这串就是sign签名,说白了就是防止数据丢失,然后检验数据的MD5的完整性的意思。
开工
//将除了sign外的所有字段放入数组,若不想进行排序,则放入数组的时候自己按字段名称a-z小到大放入数组即可 $parameters=array( 'appid'=>$this->appid, 'mch_id'=>$this->mch_id, 'nonce_str'=>$this->createNoncestr(), 'body'=>$this->body, 'out_trade_no'=>$this->out_trade_no, 'total_fee'=>$this->total_fee, 'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'], 'notify_url'=> $this->notifyURL, 'trade_type'=>$this->trade_type ); //我们的支付类型是小程序,是JSAPI,其他两种支付方式不需要绑定oepnid,所以我们动态加入openid if($this->trade_type=='JSAPI') $parameters['openid']=$this->openid;
nonce_str就是随机字符串而已,我们写个生成随机字符串的方法给它
public static function createNoncestr($strlength=32){ //创建随机字符串 $strings='abcdefghijklmnopqrstuvwxyz0123456789'; $noncestr=''; for($i=0;$i<$strlength;$i++) $noncestr.=$strings[mt_rand(0,35)];//mt_rand的效率是rand的4倍 return $noncestr; }
到了这里,数组已经装好了,然后要按照(键值键名对应)再组装,前面数组排好了就很简单了,我们创建一个方法给它
private function formatBizQueryParaMap($paraMap,$urlencode){//是否需要urlencode转义 //格式化参数 $buff=""; ksort($paraMap); foreach ($paraMap as $k=>$v){ if($urlencode){ $v=urlencode($v); } $buff.=$k.'='.$v.'&'; //组合成键值键名对应 } return $buff; }
到这步后,记得还要加上key字段,加上key字段,加上key字段,重要的事情说三遍,key字段就是商户密钥,然后调用MD5函数生成的字符串再进行大写转化即可得到sign字段
//步骤一:按字典排序参数 $string=$this->formatBizQueryParaMap($copyParameters,false); //步骤二:在string后面加key $string.='key='.$this->key; //步骤三:md5加密 $string=md5($string); //步骤四:转大小写 return strtoupper($string);
至此,10个字段全部获得,进行对微信同一订单接口进行通信
不过还需要将数组里10个字段的信息组合成XML的结构,即下面的机构,也是简单,直接取出去字符组合就可以了
<xml> <appid><appid/> </xml>
写个函数给它
public static function arrayToXml($arr){ //数组转换成xml $xml="<xml>"; foreach ($arr as $k=>$v){ $xml.='<'.$k.'>'.$v.'</'.$k.'>'; } $xml.='</xml>'; return $xml; }
然后组合好后,我是使用CURL进行通信的
private function postXmlCurl($xml,$url,$second=30){ $ch=curl_init(); //超时 curl_setopt($ch,CURLOPT_TIMEOUT,$second); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验 curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); curl_setopt($ch, CURLOPT_TIMEOUT, 40); set_time_limit(0); $data=curl_exec($ch); if($data){ curl_close($ch); return $data;//成功后返回$data } else{ $error=curl_errno($ch); curl_close($ch); throw new Exception("出错,出错码".$error); } }
然后微信返回的结果也是xml格式的,我们需要转换为数组
public static function xmlToArray($xml){ //xml转Array //禁止引用外部实体 libxml_disable_entity_loader(true); $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); $val=json_decode(json_encode($xmlstring),true);//将simplexml对象转换为json,再又json转换为数组 return $val; }
数组里面prepay_id字段就是前面所有步骤的最终结果。
继续
微信规定,最终还必须要appId,timeStamp,nonceStr,package,signType这五个字段进行MD5加密
,package字段就是上面的prepay_id,其他的填上去就完事了
$parameters=array( 'appId'=>$this->appid, 'timeStamp'=>''.time().'', 'nonceStr'=>$this->createNoncestr(), 'package' =>'prepay_id='.$unifiedorader['prepay_id'], 'signType'=>'MD5' ); //添加签名字符串 $parameters['paySign']=$this->getSign($parameters); //添加商户订单号,用于以后查询订单是否完成支付之类的查询 $parameters['out_trade_no']=$this->out_trade_no; return $parameters;
然后就将这些数据进行json_encode转换,发给前端小程序就OK了
——————————–流程分解
②最终WxPay类(WxPay.php)
<?php /** * Created by PhpStorm. * User: Liang * Date: 12/7/2017 * Time: 11:53 PM */ class WxPay{ protected $appid; protected $mch_id; protected $key; protected $openid; protected $out_trade_no; protected $body; protected $total_fee; protected $notifyURL; protected $unfiedorderURL; protected $trade_type; public function __construct($appid, $openid, $mch_id, $key,$out_trade_no,$body,$total_fee,$notifyURL,$unifiedorderURL,$trade_type){ $this->appid=$appid; $this->mch_id=$mch_id; $this->openid = $openid; $this->key = $key; $this->out_trade_no = $out_trade_no; $this->body = $body; $this->total_fee = $total_fee; $this->notifyURL=$notifyURL; $this->unfiedorderURL=$unifiedorderURL; $this->trade_type=$trade_type; } public function pay(){ return $this->weixinapp(); } private function weixinapp(){ //统一下单接口 $unifiedorader=$this->unifiedorder(); $parameters=array( 'appId'=>$this->appid, 'timeStamp'=>''.time().'', 'nonceStr'=>$this->createNoncestr(), 'package' =>'prepay_id='.$unifiedorader['prepay_id'], 'signType'=>'MD5' ); $parameters['paySign']=$this->getSign($parameters); //返回商户号 $parameters['out_trade_no']=$this->out_trade_no; return $parameters; } public function unifiedorder(){ $parameters=array( 'appid'=>$this->appid, 'mch_id'=>$this->mch_id, 'nonce_str'=>$this->createNoncestr(), 'body'=>$this->body, 'out_trade_no'=>$this->out_trade_no, 'total_fee'=>$this->total_fee, 'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'], 'notify_url'=> $this->notifyURL, 'trade_type'=>$this->trade_type ); if($this->trade_type=='JSAPI') $parameters['openid']=$this->openid; ksort($parameters); $parameters['sign']=$this->getSign($parameters); $xmlData=$this->arrayToXml($parameters); $return=$this->xmlToArray($this->postXmlCurl($xmlData,$this->unfiedorderURL,60)); return $return; } private function postXmlCurl($xml,$url,$second=30){ $ch=curl_init(); //超时 curl_setopt($ch,CURLOPT_TIMEOUT,$second); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验 curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); curl_setopt($ch, CURLOPT_TIMEOUT, 40); set_time_limit(0); $data=curl_exec($ch); if($data){ curl_close($ch); return $data; } else{ $error=curl_errno($ch); curl_close($ch); throw new Exception("出错,出错码".$error); } } public static function xmlToArray($xml){ //xml转Array libxml_disable_entity_loader(true); $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); $val=json_decode(json_encode($xmlstring),true); return $val; } public static function arrayToXml($arr){ //数组转换成xml $xml="<xml>"; foreach ($arr as $k=>$v){ $xml.='<'.$k.'>'.$v.'</'.$k.'>'; } $xml.='</xml>'; return $xml; } public static function createNoncestr($strlength=32){ //创建随机字符串 $strings='abcdefghijklmnopqrstuvwxyz0123456789'; $noncestr=''; for($i=0;$i<$strlength;$i++) $noncestr.=$strings[mt_rand(0,35)]; return $noncestr; } private function getSign($obj){ //生成签名 foreach ($obj as $k=>$v){ $copyParameters[$k]=$v; } //步骤一:按字典排序参数 $string=$this->formatBizQueryParaMap($copyParameters,false); //步骤二:在string后面加key $string.='key='.$this->key; //步骤三:md5加密 $string=md5($string); //步骤四:转大小写 return strtoupper($string); } private function formatBizQueryParaMap($paraMap,$urlencode){ //格式化参数 $buff=""; ksort($paraMap); foreach ($paraMap as $k=>$v){ if($urlencode){ $v=urlencode($v); } $buff.=$k.'='.$v.'&'; } return $buff; } }
③回调地址(notify.php)
//获取参数,且转换成数组 $postXml = file_get_contents('php://input'); $arr =WxPay::xmlToArray($postXml); if($redis->exists($arr['out_trade_no'])) return; //返回微信SUCCESS,不然微信会一天内再次进入这个地址多次,直到你发送SUCCESS $returnArr=array(); if(checkSign($arr,$key)&&$arr['total_fee']==$fee){ $returnArr['return_code']='SUCCESS'; $returnArr['return_msg']='OK'; } else{ if($arr['total_fee']!=$fee) logWrite('fee is not equals original fee,now fee is '.$fee.',the base is '.$arr['total_fee']); $returnArr['return_code']='FAIL'; $returnArr['return_msg']='签名失败'; } $xml=WxPay::arrayToXml($returnArr); echo $xml; //后面就是你自己的业务流程了,支付成功该干嘛就干嘛
④最后说一下前端小程序
clickMe: function (event) { //事件触发,调起request请求,与php文件通信 wx.request({ //上面php文件的地址 url: 'https://xxx/initWx.php', method:'POST', header:{'content-type':'application/x-www-form-urlencoded'}, data: {openid:app.globalData.openid},//传递openid success:function(res){ //成功的话,就将返回的数据取出 console.log(res) wx.requestPayment({//调用函数,取出返回的数据填入字段即可 timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: res.data.signType, paySign: res.data.paySign, success:function(){ console.log(res) }, fail:function(res){ console.log(res) } }) } }) }
参考文档:
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5