Home >Backend Development >PHP Tutorial >Detailed explanation of examples of implementing WeChat payment and refund process in PHP

Detailed explanation of examples of implementing WeChat payment and refund process in PHP

coldplay.xixi
coldplay.xixiforward
2020-06-08 13:55:183057browse

Detailed explanation of examples of implementing WeChat payment and refund process in PHP

Example of PHP implementation of WeChat payment and refund process

The main logic of WeChat mini program payment is concentrated on the back end , the front-end only needs to carry the data required for payment to request the back-end interface and then handle the corresponding success or failure based on the returned results. The backend of this article uses PHP, focusing on the entire payment process and some details. Therefore, friends who use other back-end languages ​​​​can take a look if necessary. Many times, development needs and corresponding problem solutions really need to go beyond the language syntax level and be considered from the perspective of systems and processes.

1. WeChat payment

Payment is mainly divided into several steps:

The front end carries the data required for payment (product id, purchase quantity, etc.) Initiate a payment request

After receiving the payment request, the backend processes the payment data, and then carries the processed data to request the unified payment order interface of the WeChat server

The backend receives the previous request The return data from the WeChat server is processed again, and then returned to the front end so that the front end can start payment.

Front-end performs payment action

After the front-end payment is completed, the WeChat server will send a payment notification to the back-end (that is, WeChat wants to tell you that the customer has paid), and the back-end will act according to this notification Make sure the payment is completed, and then take the corresponding actions after the payment is completed, such as modifying the order status, adding transaction logs, etc.

It can be seen from these steps that the main function of the backend is to transmit the data required for payment to the WeChat server, and then determine whether the payment is completed based on the response of the WeChat server.

This process is quite easy to understand. Figuratively speaking, the front-end is a customer, the back-end is the store, and the unified ordering interface of the WeChat server is like a cashier. The customer tells the store, who am I, and now I have to pay as much as I want to buy you such and such. The store owner told the cashier, "How much does so-and-so have to pay? Be prepared to collect the money." After the cashier received the money, he went to tell the store that I had received the money and you could give him something.

The following is a detailed explanation of the specific implementation of each step.

1. Front-end request for payment

The front-end request for payment simply carries the data required for payment, such as user ID, payment amount, payment order ID, etc. It is related to your business logic ** Or related data related to ** the next step of requesting the WeChat server payment unified ordering interface**, use wx.request() of the WeChat applet to request the back-end payment interface.

2. The backend requests the WeChat server

After the backend receives the payment request sent by the frontend, it can perform related verifications, such as determining whether the user has any problems, whether the payment amount is correct, etc. .

After verifying that there is no problem and you can apply for payment to the WeChat server, the backend needs to use the data format specified by WeChat to request WeChat's unified payment order interface.

Request data specified by WeChat:

This requires more code to implement. Because the amount of data required is large, it also needs to be encrypted and sent in XML format.

First of all, the following data are parameters that must be provided to the WeChat server when using mini program payment.

Mini program appid. There is probably no one who writes small programs who doesn’t know this. . .

User ID openid. That is the user's mini program ID. I explained how to obtain it in my last blog.

Merchant number mch_id. After successfully applying for WeChat payment merchant certification, the email sent to you by WeChat will contain

merchant order number out_trade_no. The order number

generated by the merchant for this payment is the total amount total_fee. The total amount of the order, a very important point is that the unit is cents, so pay special attention to it.

WeChat server callback notification interface address notify_url. After WeChat confirms that the money has arrived, it will send multiple messages to this address to tell you that the customer has paid. You need to return a message to WeChat to indicate that you have received the notification. . This address cannot have a port number, and must be able to directly accept POST method requests.

Transaction type trade_type. The WeChat applet payment value is unified as JSAPI

Product Information Body. Similar to the format of "Tencent-Game"

Terminal IP address spbill_create_ip. Terminal address IP, which is the IP address requesting payment.

Random string nonce_str. A string randomly generated by the backend is required to ensure data security. WeChat requires no longer than 32 bits.

signature sign. Use all the above parameters to process the encryption and generate the signature accordingly. (The specific processing method can be seen in the code below, which can be reused directly.)

After processing all the above data, organize the data in XML format and send it to the WeChat payment unified order interface using the POST method

3. The backend accepts the data returned by the WeChat server

After the WeChat server receives the payment data, if there is no problem with the data, it will return the corresponding data for payment, of which the most important thing is the name prepay_id The data field needs to be returned to the front end before the front end can continue to pay.

Therefore, after the back-end receives the return data from the WeChat server, it needs to perform corresponding processing, and finally return the following data to the front-end:

appid No need to say more

timeStamp current timestamp

nonceStr random string

package is the prepay_id mentioned above, but remember that the format is such as "prepay_id= prepay_id_item". Otherwise an error will result.

signType encryption method, generally should be MD5

paySign processes and encrypts the above data accordingly.

At this point, the back-end payment interface has completed the function of receiving the front-end payment request and returning the data required for front-end payment.

4. Front-end initiates payment

After receiving the return data, the front-end uses wx.requestPayment() to request payment. The values ​​of the object parameters required by this API are the data returned in the previous step.

5. The backend accepts the WeChat server callback

After the frontend completes the payment, the WeChat server confirms that the payment has been completed. A notification will be sent to the callback address set in the first step. After receiving the notification, the back-end receiving callback interface can determine whether the payment is completed and determine subsequent actions.

It should be noted that after receiving the callback notification from the WeChat server, it is judged whether the payment is successful based on the result_code field of the notification. After receiving the successful notification, the backend needs to return success data to the WeChat server to inform the WeChat server that the callback notification has been received. Otherwise, the WeChat server will keep sending messages to the backend. In addition, WeChat notifications are sent in XML format, so you need to pay attention when accepting and processing them.

The general payment process of WeChat is like this. The following is the WeChat payment class of PHP syntax. You can refer to the above steps to deepen your understanding. When you need to pay, you can directly pass in the parameters to instantiate this class and then call the pay method of the class.

//WeChat payment class

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, &#39;SimpleXMLElement&#39;, 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;
     }
}

The above is the relevant process of WeChat payment. After clarifying the ideas, the process is relatively clear and simple. The point is that you need to pay attention to some details, such as data format, encryption method, etc.

Let’s talk about the specific implementation of WeChat Mini Program refund

2. WeChat Refund

The refund process of Mini Program is similar to payment. But there are some differences in details.

The first step of refunding is usually as follows:

After the user clicks the refund button on the front end, the back end receives the user's refund request and presents it to the merchant through the mall backend. After the merchant confirms that the refund is allowed , the backend then initiates a request to the WeChat refund interface to request a refund.

After the backend sends a request to the WeChat refund interface, it gets the response information, determines whether the refund is completed, and then changes the order status and other business logic based on whether the refund is completed.

The steps for refund are relatively simple compared to WeChat payment.

It is worth noting the following two points:

1. After requesting a refund from the WeChat refund interface, you can directly determine whether the refund has been completed based on the response received. There is no need to set up a special callback interface to wait for WeChat notifications. Of course, if necessary, you can also set up a callback interface on the WeChat merchant platform to accept WeChat callbacks, but it is not necessary.

2. The refund request requires the security certificate provided by WeChat to be installed on the requesting server. That is to say, when initiating a refund request compared to a payment request, the request method cannot be reused during the request, because WeChat refunds need to carry the certificate. Upon request, this certificate can be downloaded from the WeChat merchant platform after successfully applying for a WeChat merchant account. The certificate for the PHP development environment under Linux only needs to be placed in the cert folder in the root directory of the website. Other development environments may require import operations.

The following will explain the specific steps of refund

1. The user initiates a refund request

The user initiates a refund request on the front end, and the backend receives the refund request and The corresponding order is marked as applying for refund and is displayed in the background. After the merchant checks it, if it agrees to the refund, it will proceed with the corresponding operation. Only then will the real refund process be entered.

2. The merchant initiates a refund request

After the merchant agrees to the refund, the backend will initiate a request to the refund API provided by WeChat.

It is the same as requesting the WeChat payment API. The refund request also needs to sign the required parameters and send it in XML Refund API to WeChat

The parameters required for a refund request are as follows (multiple parameters are also used in payment API requests):

Parameters:

appid

mch_id merchant number. After successfully applying for WeChat payment merchant certification, the email sent to you by WeChat will contain the

out_trade_no merchant order number. The order number generated when the refund order is paid

out_refund_no Refund order number. The refund order number generated by the backend needs to be unique, because multiple identical refund order numbers will only refund once.

total_fee Total amount. The total amount of the order, in cents.

refund_fee Refund amount. The amount to be refunded, the unit is also in minutes

op_user_id operator. It can be the same as the merchant number

nonce_str random string. Same as payment request

signature. Use all the above parameters to process the encryption and generate the signature accordingly. (The specific processing method is the same as payment and can be reused directly.)

Note: I kept getting a signature error because I did not pass in the key (secret key) parameter during instantiation.

三. 退款完成

在发起退款请求后,就可以直接根据请求的响应XML中的 result_code字段来判断退款是否成功,从而对订单状态进行处理和后续操作。不需要像支付那样等待另一个接口的通知来确定请求状态。当然如上文所说,如果需要微信服务器发送通知到后端的话,可以到微信商户平台进行设置。

退款因为流程与支付大同小异,因此退款的PHP类我选择了直接继承支付类,

代码如下,注意区分退款请求方法postXmlSSLCurl和支付请求方法postXmlCurl的区别,这也就是上文提到的退款需要的双向证书的使用。

class WinXinRefund extends WeiXinPay{
  protected \$SSLCERT_PATH = &#39;cert/apiclient_cert.pem&#39;;//证书路径
  protected \$SSLKEY_PATH = &#39;cert/apiclient_key.pem&#39;;//证书路径
  protected \$opUserId = &#39;1234567899&#39;;//商户号
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(
    &#39;appid&#39;=> $this->APPID,
    &#39;mch_id&#39;=> $this->MCHID,
    &#39;nonce_str&#39;=> $this->createNoncestr(),
    &#39;out_refund_no&#39;=> $this->outRefundNo,
    &#39;out_trade_no&#39;=> $this->outTradeNo,
    &#39;total_fee&#39;=> $this->totalFee,
    &#39;refund_fee&#39;=> $this->refundFee,
    &#39;op_user_id&#39; => $this->opUserId,
  );
  $parma[&#39;sign&#39;] = $this->getSign($parma);
  $xmldata = $this->arrayToXml($parma);
  $xmlresult = $this->postXmlSSLCurl($xmldata,&#39;https://api.mch.weixin.qq.com/secapi/pay/refund&#39;);
  $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, &#39;8.8.8.8&#39;);
  //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,&#39;PEM&#39;);
  curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);
  //默认格式为PEM,可以注释
  curl_setopt($ch,CURLOPT_SSLKEYTYPE,&#39;PEM&#39;);
  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可不是小事。

推荐教程:《PHP视频教程

The above is the detailed content of Detailed explanation of examples of implementing WeChat payment and refund process in PHP. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:liqingbo.cn. If there is any infringement, please contact admin@php.cn delete