PHP实现微信支付及退款流程实例

微信小程序支付的主要逻辑集中在后端。前端只需要携带支付所需的数据请求后端接口,然后根据返回的结果执行相应的成功失败处理。本文的后端使用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 方法发送到 微信支付统一下单接口

3.后端接受微信服务器返回数据

收到付款数据后,如果数据没有问题,微信服务器将返回相应的付款数据。 最重要的一个是名为prepay_id的数据字段,需要在前端可以继续支付之前将其返回到前端。

因此,在后端收到微信服务器的返回数据后,需要进行相应的处理,最后返回前端,如下:

appid 这是必须的

timeStamp 当前的时间戳

nonceStr 随机的字符串

package 就是上面提到的 prepay_id,不过切记格式如 “prepay_id= prepay_id_item“。否则会导致错误。

signType 加密方式,一般应该都是 MD5

paySign 对应并加密上述数据。

此时,后端支付界面已完成接收前端支付请求并返回前端支付所需数据的功能。

4. 前端发起支付

前端在接收到返回数据后,使用 wx.requestPayment() 来请求发起支付。此 API 需要的对象参数各项值就是我们上一步返回的各个数据。

5.后端接受微信服务器回调

在前端完成付款后,微信服务器确认付款已完成。 通知将发送到第一步中设置的回调地址。 在接收到通知之后,后端接收回调接口可以确定支付是否完成,从而确定后续动作。

需要说明的是,在接收到微信服务器的回调通知后,根据通知的result_code字段确定支付是否成功。 收到成功通知后,后端需要返回成功数据,通知微信服务器已收到回调通知。 否则,微信服务器将继续向后端发送消息。 另外,微信的通知是以XML格式发送的,接受处理时需要注意。

微信的大致的支付流程就是这样。以下是PHP语法的微信支付类,可以按照上面的步骤介绍,加深理解。在需要支付时,直接传入参数实例化此类再调用类的 pay 方法即可。

//微信支付类class WeiXinPay{  //=======【基本信息设置】=====================================  //微信公众号身份的唯一标识  protected $APPID = appid;//填写您的appid。微信公众平台里的  protected $APPSECRET = secret;  //受理商ID,身份标识  protected $MCHID = '11111111';//商户id  //商户支付密钥Key  protected $KEY = '192006250b4c09247ec02edce69f6a2d';  //回调通知接口  protected $APPURL =   'https://www.wxsshop.com/success';  //交易类型  protected $TRADETYPE = 'JSAPI';  //商品类型信息  protected $BODY = '微销神/化妆品';  //微信支付类的构造函数  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。

退款请求需要的参数如下(多个参数在支付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类 均封装直接可用。

因为微信支付和退款涉及的东西较为繁杂,许多人在直接查看官方文件时可能会感到困惑, 因此,阅读本文后了解过程和要点,请转到微信官方文档。 一方面,您可以更清楚地了解小程序的付款和退款流程。 另一方面,由于空间有限和作者能力有限,本文当然没有时间考虑或存在一些缺陷。 为了安全起见,您仍需要查看官方开发文档。 毕竟,支付它并不是一件小事。

未经允许不得转载:PHP100中文网 - 中国第一档PHP资源分享门户 » PHP实现微信支付及退款流程实例

赞 (1) 打赏

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏