微信小程序支付的紧张逻辑集中在后端,前端只需携带支付所需的数据要求后端接口然后根据返回结果做相应成功失落败处理即可。我在后端利用的是php,当然在这篇博客里我不打算贴一堆代码来解释支付的详细实现,而紧张会侧重于全体支付的流程和一些细节方面的东西。以是利用其他后端措辞的朋友有须要也是可以看一下的。很多时候开拓的需求和相应问题的办理真的要跳出措辞语法层面,去从系统和流程的角度考虑。好的,也不说什么废话了。进入正题。
一. 支付
支付紧张分为几个步骤:
前端携带支付须要的数据(商品id,购买数量等)发起支付要求后端在吸收到支付要求后,处理支付数据,然后携带处理后的数据要求 微信服务器 的 支付统一下单接口后端吸收到上一步要求微信服务器的返回数据,再次处理,然后返回前端让前端可以开始支付。前端进行支付动作前端支付完成后,微信服务器会向后端发送支付关照(也便是微信要见告你客户已经付过钱了),后端根据这个关照确定支付完成,然后就去做支付完成后的相应动作,比如修正订单状态,添加交易日志啊等等。
从这几个步骤可以看出,后端紧张的浸染便是将支付须要的数据传给微信服务器,再根据微信服务器的相应确定支付是否完成。
这个流程还是蛮随意马虎理解的。形象的说,前端便是个顾客,后端便是店家,微信服务器的统一下单接口就像收银员。顾客跟店家说,我是谁谁谁,现在我要付多少多少钱给你买什么什么。店家就跟收银员说,那个谁谁谁要付多少钱,你准备收钱吧。收银员收到钱后,就去见告店家,我已经收到钱了,你给他东西吧。
下面就详细的解释一下各个步骤的详细实现。
1. 前端要求支付
前端要求支付,便是大略的携带支付须要的数据,例如用户标识,支付金额,支付订单 ID 等等跟 你的业务逻辑有关 或者跟 下一步要求微信服务器支付统一下单接口须要的数据有关 的干系数据,利用微信小程序的 wx.request( ) 去要求后真个支付接口。
2. 后端要求微信服务器
后端吸收到前端发送的支付要求后,可以进行一下干系验证,例如判断一下用户有没有问题,支付金额对不对等等。
在验证没什么问题,可以向微信服务器申请支付之后,后端须要利用 微信规定的数据格式 去要求微信的支付统一下单接口。
微信规定的要求数据:
这须要较多代码实现。由于须要的数据个数较多,而且还须要加密并以 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 。
3.后端接管微信服务器返回数据
微信服务器在吸收到支付数据之后,如果数据没有问题,其会返回用于支付的相应数据,个中非常主要的是 名称为 prepay_id 的数据字段,须要将此数据返回前端,前端才能连续支付。
因此,在后端吸收到微信服务器的返回数据后,须要进行相应的处理,终极返回到前端如下数据:
appid 不需多说timeStamp 当前韶光戳nonceStr 随机字符串package 便是上面提到的 prepay_id,不过牢记格式如 “prepay_id= prepay_id_item“。否则会导致缺点。signType 加密办法,一样平常该当是 MD5paySign 对以上数据进行相应处理并加密。到这里,后真个支付接口已经完成了吸收前端支付要求,并返回了前端支付所需数据的功能。
4. 前端发起支付
前端在吸收到返回数据后,利用 wx.requestPayment() 来要求发起支付。此 API 须要的工具参数各项值便是我们上一步返回的各个数据。
5.后端接管微信服务器回调
前端完成支付后,微信服务器确认支付已经完成。就会向第一步中设置的回调地址发送关照。后真个吸收回调接口在吸收到关照后,就可以判断支付是否完成,从而决定后续动作。
须要把稳的是,在吸收到微信服务器的回调关照后,根据关照的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; }}}