支付主要分为几个步骤:php
- 前端携带支付须要的数据(商品id,购买数量等)发起支付请求
- 后端在接收到支付请求后,处理支付数据,而后携带处理后的数据请求 微信服务器 的 支付统一下单接口
- 后端接收到上一步请求微信服务器的返回数据,再次处理,而后返回前端让前端能够开始支付。
- 前端进行支付动做
- 前端支付完成后,微信服务器会向后端发送支付通知(也就是微信要告诉你客户已经付过钱了),后端根据这个通知肯定支付完成,而后就去作支付完成后的相应动做,好比修改订单状态,添加交易日志啊等等。
从这几个步骤能够看出,后端主要的做用就是将支付须要的数据传给微信服务器,再根据微信服务器的响应肯定支付是否完成。 这个流程仍是蛮容易理解的。形象的说,前端就是个顾客,后端就是店家,微信服务器的统一下单接口就像收银员。顾客跟店家说,我是谁谁谁,如今我要付多少多少钱给你买什么什么。店家就跟收银员说,那个谁谁谁要付多少钱,你准备收钱吧。收银员收到钱后,就去告诉店家,我已经收到钱了,你给他东西吧。
下面就详细的说明一下各个步骤的具体实现。html
前端请求支付,就是简单的携带支付须要的数据,例如用户标识,支付金额,支付订单 ID 等等跟 **你的业务逻辑有关** 或者跟 **下一步请求微信服务器支付统一下单接口须要的数据有关** 的相关数据,使用微信小程序的 wx.request( ) 去请求后端的支付接口。
后端接收到前端发送的支付请求后,能够进行一下相关验证,例如判断一下用户有没有问题,支付金额对不对等等。 在验证没什么问题,能够向微信服务器申请支付以后,后端须要使用 微信规定的数据格式 去请求微信的支付统一下单接口。
微信规定的请求数据:
这须要较多代码实现。由于须要的数据个数较多,并且还须要加密并以 XML 格式发送。
首先,有如下数据是使用小程序支付必须提供给微信服务器的参数。前端
- 小程序 appid。写小程序的大概没有不知道这个的。。。
- 用户标识 openid。也就是用户的小程序标识,在我上篇博客中说明了如何获取。
- 商户号 mch_id 。申请开通微信支付商户认证成功后微信发给你的邮件里有
- 商户订单号 out_trade_no 。商户为此次支付生成的订单号
- 总金额 total_fee 。订单总金额,很重要的一点是单位是分,要特别注意。
- 微信服务器回调通知接口地址 notify_url。微信确认钱已经到帐后,会往这个地址屡次发送消息,告诉你顾客已经付完钱了,你须要返回消息给微信表示你已经收到了通知。。这个地址不能有端口号,同时要能直接接受POST方法请求。
- 交易类型 trade_type 。微信小程序支付此值统一为 JSAPI
- 商品信息 Body。相似"腾讯-游戏"这种格式
- 终端IP地址 spbill_create_ip 。终端地址IP,也就是请求支付的 IP 地址。
- 随机字符串 nonce_str 。须要后端随机生成的字符串用于保证数据安全。微信要求不长于32位。
- 签名 sign 。使用上面的全部参数进行相应处理加密生成签名。(具体处理方式可见下文代码,可直接复用。)
在处理好以上全部数据后,将这些数据以 XML 格式整理并以 POST 方法发送到 微信支付统一下单接口 https://api.mch.weixin.qq.com/pay/unifiedorder 。json
微信服务器在接收到支付数据以后,若是数据没有问题,其会返回用于支付的相应数据,其中很是重要的是 名称为 prepay_id 的数据字段,须要将此数据返回前端,前端才能继续支付。小程序
所以,在后端接收到微信服务器的返回数据后,须要进行相应的处理,最终返回到前端以下数据:后端
appid 不需多说微信小程序
- timeStamp 当前时间戳
- nonceStr 随机字符串
- package 就是上面提到的 prepay_id,不过切记格式如 “prepay_id= prepay_id_item“。不然会致使错误。
- signType 加密方式,通常应该是 MD5
paySign 对以上数据进行相应处理并加密。api
到这里,后端的支付接口已经完成了接收前端支付请求,并返回了前端支付所需数据的功能。数组
前端在接收到返回数据后,使用 wx.requestPayment() 来请求发起支付。此 API 须要的对象参数各项值就是咱们上一步返回的各个数据。安全
前端完成支付后,微信服务器确认支付已经完成。就会向第一步中设置的回调地址发送通知。后端的接收回调接口在接收到通知后,就能够判断支付是否完成,从而决定后续动做。
须要注意的是,在接收到微信服务器的回调通知后,根据通知的result_code字段判断支付是否成功。在接受到成功的通知后,后端须要返回success数据向微信服务器告知已获得回调通知。不然微信服务器会不停的向后端发送消息。另外微信的通知是以XML格式发送的,在接受处理时须要注意。
微信的大概支付流程就是这样。如下是PHP语法的微信支付类,能够比照上面的步骤介绍,加深理解。在须要支付时,直接传入参数实例化此类再调用类的 pay 方法便可。
//微信支付类 class WeiXinPay{ //=======【基本信息设置】===================================== //微信公众号身份的惟一标识 protected $APPID = appid;//填写您的appid。微信公众平台里的 protected $APPSECRET = secret; //受理商ID,身份标识 protected $MCHID = '11111111';//商户id //商户支付密钥Key protected $KEY = '192006250b4c09247ec02edce69f6a2d'; //回调通知接口 protected $APPURL = 'https://smart.afei.com/receivesuc'; //交易类型 protected $TRADETYPE = 'JSAPI'; //商品类型信息 protected $BODY = 'wx/book'; //微信支付类的构造函数 function __construct($openid,$outTradeNo,$totalFee){ $this->openid = $openid; //用户惟一标识 $this->outTradeNo = $outTradeNo; //商品编号 $this->totalFee = $totalFee; //总价 } //微信支付类向外暴露的支付接口 public function pay(){ $result = $this->weixinapp(); return $result; } //对微信统一下单接口返回的支付相关数据进行处理 private function weixinapp(){ $unifiedorder=$this->unifiedorder(); $parameters=array( 'appId'=>$this->APPID,//小程序ID 'timeStamp'=>''.time().'',//时间戳 'nonceStr'=>$this->createNoncestr(),//随机串 'package'=>'prepay_id='.$unifiedorder['prepay_id'],//数据包 'signType'=>'MD5'//签名方式 ); $parameters['paySign']=$this->getSign($parameters); return $parameters; } /* *请求微信统一下单接口 */ private function unifiedorder(){ $parameters = array( 'appid' => $this->APPID,//小程序id 'mch_id'=> $this->MCHID,//商户id 'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'],//终端ip 'notify_url'=>$this->APPURL, //通知地址 'nonce_str'=> $this->createNoncestr(),//随机字符串 'out_trade_no'=>$this->outTradeNo,//商户订单编号 'total_fee'=>floatval($this->totalFee), //总金额 'open_id'=>$this->openid,//用户openid 'trade_type'=>$this->TRADETYPE,//交易类型 'body' =>$this->BODY, //商品信息 ); $parameters['sign'] = $this->getSign($parameters); $xmlData = $this->arrayToXml($parameters); $xml_result = $this->postXmlCurl($xmlData,'https://api.mch.weixin.qq.com/pay/unifiedorder',60); $result = $this->xmlToArray($xml_result); return $result; } //数组转字符串方法 protected function arrayToXml($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; } protected function xmlToArray($xml){ $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $array_data; } //发送xml请求方法 private static 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); //严格校验 //设置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_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); curl_setopt($ch, CURLOPT_TIMEOUT, 40); set_time_limit(0); //运行curl $data = curl_exec($ch); //返回结果 if ($data) { curl_close($ch); return $data; } else { $error = curl_errno($ch); curl_close($ch); throw new WxPayException("curl出错,错误码:$error"); } } /* * 对要发送到微信统一下单接口的数据进行签名 */ protected function getSign($Obj){ foreach ($Obj as $k => $v){ $Parameters[$k] = $v; } //签名步骤一:按字典序排序参数 ksort($Parameters); $String = $this->formatBizQueryParaMap($Parameters, false); //签名步骤二:在string后加入KEY $String = $String."&key=".$this->KEY; //签名步骤三:MD5加密 $String = md5($String); //签名步骤四:全部字符转为大写 $result_ = strtoupper($String); return $result_; } /* *排序并格式化参数方法,签名时须要使用 */ protected function formatBizQueryParaMap($paraMap, $urlencode) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if($urlencode) { $v = urlencode($v); } //$buff .= strtolower($k) . "=" . $v . "&"; $buff .= $k . "=" . $v . "&"; } $reqPar; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff)-1); } return $reqPar; } /* * 生成随机字符串方法 */ protected function createNoncestr($length = 32 ){ $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str =""; for ( $i = 0; $i < $length; $i++ ) { $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1); } return $str; } }
以上就是微信支付的相关流程。在理清思路后,流程仍是比较清晰和简单的。重点在于须要注意一些细节问题,例如数据格式,加密方法等。
下面说一下微信小程序退款的具体实现
小程序退款的流程和付款类似,但有一些细节上的不一样。
首先退款的步骤一般以下:
- 用户前端点击退款按钮后,后端接收到用户的退款请求经过商城后台呈现给商户,商户肯定容许退款后,后端再发起向微信退款接口的请求来请求退款。
- 后端向微信退款接口发送请求后,获得响应信息,肯定退款是否完成,根据退款是否完成再去进行改变订单状态等业务逻辑。
退款的步骤相对微信支付来讲比较简单。
值得注意的有如下两点:
1.向微信退款接口请求退款后,根据获得的响应是能够直接肯定退款是否完成的。再也不须要设置专门的回调接口等待微信通知。固然若是须要也是能够在微信商户平台设置回调接口接受从而接受微信回调的,但并非必须的。
2.退款请求须要在请求服务器安装微信提供的安全证书,也就是说,发起退款请求相比较支付请求在请求时请求方法不能复用,由于微信退款须要携带证书的请求,此证书可在申请微信商户号成功后从微信商户平台自行下载,Linux下的PHP开发环境的证书只须要放在网站根目录的cert文件夹中便可。其余开发环境可能须要导入操做。
下面讲解一下退款的具体步骤
用户在前端发起退款请求,后端接收到退款请求,将相应订单标记为申请退款,展现在后台.商户查看后,若是赞成退款再进行相应操做.此后才进入真正的退款流程.
商户赞成退款后,后端即向微信提供的退款 API 发起请求. 同请求微信支付API同样.退款请求也须要将须要的参数进行签名后以XML发送到微信的退款API [https://api.mch.weixin.qq.com/pay/refund](https://api.mch.weixin.qq.com/pay/refund)
退款请求须要的参数以下(多个参数在支付API请求时也有使用):
- 小程序 appid。
- 商户号 mch_id 。申请开通微信支付商户认证成功后微信发给你的邮件里有
- 商户订单号 out_trade_no 。退款订单在支付时生成的订单号
- 退款订单号 out_refund_no 。由后端生成的退款单号,须要保证惟一,由于多个一样的退款单号只会退款一次。
- 总金额 total_fee 。订单总金额,单位为分。
- 退款金额 refund_fee 须要退款的金额,单位一样为分
- 操做员 op_user_id .与商户号相同便可
- 随机字符串 nonce_str 。同支付请求
- 签名 sign 。使用上面的全部参数进行相应处理加密生成签名。(具体处理方式与支付相同,可直接复用。)
在发起退款请求后,就能够直接根据请求的响应XML中的 result_code字段来判断退款是否成功,从而对订单状态进行处理和后续操做。不须要像支付那样等待另外一个接口的通知来肯定请求状态。固然如上文所说,若是须要微信服务器发送通知到后端的话,能够到微信商户平台进行设置。
退款由于流程与支付大同小异,所以退款的PHP类我选择了直接继承支付类,
代码以下,注意区分退款请求方法postXmlSSLCurl和支付请求方法postXmlCurl的区别,这也就是上文提到的退款须要的双向证书的使用。
````
class WinXinRefund extends WeiXinPay{ protected \$SSLCERT_PATH = 'cert/apiclient_cert.pem';//证书路径 protected \$SSLKEY_PATH = 'cert/apiclient_key.pem';//证书路径 protected \$opUserId = '1234567899';//商户号 function __construct($openid,$outTradeNo,$totalFee,$outRefundNo,$refundFee){ //初始化退款类须要的变量 $this->openid = $openid; $this->outTradeNo = $outTradeNo; $this->totalFee = $totalFee; $this->outRefundNo = $outRefundNo; $this->refundFee = $refundFee; } public function refund(){ //对外暴露的退款接口 $result = $this->wxrefundapi(); return $result; } private function wxrefundapi(){ //经过微信api进行退款流程 $parma = array( 'appid'=> $this->APPID, 'mch_id'=> $this->MCHID, 'nonce_str'=> $this->createNoncestr(), 'out_refund_no'=> $this->outRefundNo, 'out_trade_no'=> $this->outTradeNo, 'total_fee'=> $this->totalFee, 'refund_fee'=> $this->refundFee, 'op_user_id' => $this->opUserId, ); $parma['sign'] = $this->getSign($parma); $xmldata = $this->arrayToXml($parma); $xmlresult = $this->postXmlSSLCurl($xmldata,'https://api.mch.weixin.qq.com/secapi/pay/refund'); $result = $this->xmlToArray($xmlresult); return $result; } //须要使用证书的请求 function postXmlSSLCurl($xml,$url,$second=30) { $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); //设置证书 //使用证书:cert 与 key 分别属于两个.pem文件 //默认格式为PEM,能够注释 curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH); //默认格式为PEM,能够注释 curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH); //post提交方式 curl_setopt($ch,CURLOPT_POST, true); curl_setopt($ch,CURLOPT_POSTFIELDS,$xml); $data = curl_exec($ch); //返回结果 if($data){ curl_close($ch); return $data; } else { $error = curl_errno($ch); echo "curl出错,错误码:$error"."<br>"; curl_close($ch); return false; } }}
以上就是关于微信支付和退款的流程及相关知识的介绍。文中的 PHP类 均封装直接可用。 由于微信支付和退款涉及的东西较为繁杂,不少人直接看官方文档可能会一头雾水,因此看过此文了解流程和要点后,再去看微信官方文档。一方面能够更清晰的了解小程序的支付和退款流程。另外一方面,本文由于篇幅有限及做者能力有限,确定有无暇顾及或有所纰漏之处。为求稳妥,仍是须要多看看官方开发文档。毕竟事涉支付,出个BUG可不是小事。 最后扯点闲话吧。这篇博客原本应该在三个月前就发表的,也算当时我从一无所知到独立完成微信小程序商城先后端的总结系列的第一篇。可是公司忽然出现人员和项目的变更,致使管理和项目上都混乱不堪,再加上我的的惰性,致使此篇博客一直拖到三个月后的今天才断断续续写完。这三个月个人心态由于各类事起起伏伏,也很有一番风味。 借用李志的一句歌词结束这篇博客吧。下一篇是何时也说不定了,我苦笑。
我不再会把本身,愚蠢的交给过去。个人生活和个人想法,今后相隔万里。