1. 程式人生 > >***微信公眾號支付+微信H5支付+微信掃碼支付+小程序支付+APP微信支付解決方案總結

***微信公眾號支付+微信H5支付+微信掃碼支付+小程序支付+APP微信支付解決方案總結

ati asc alt creat chapter edit 隨機字符串 glob 測試

最近負責的一些項目開發,都用到了微信支付(微信公眾號支付、微信H5支付、微信掃碼支付、APP微信支付)。在開發的過程中,在調試支付的過程中,或多或少都遇到了一些問題,今天總結下,分享,留存。

先說註意的第一點,所有支付的第一步都是請求統一下單,統一下單,統一下單,請求URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder。統一下單的目的是拿到預支付交易會話標識prepay_id,這個是必須的。所有的支付調用都是通過prepay_id來識別。

再說一個微信官方提供的一個很重要的工具,微信支付接口簽名校驗工具(網址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1

),此工具旨在幫助開發者檢測調用【微信支付接口API】時發送的請求參數中生成的簽名是否正確,提交相關信息後可獲得簽名校驗結果。特別實用!特別實用!特別實用!簽名只要正確了,一切就OK了!

還有就是官方提供的幾種支付方式的對比說明,如下圖所示。

技術分享圖片

第一部分 微信公眾號支付

微信公眾號支付需要配置的參數有:APPID(微信公眾號開發者ID)、APPSECRET(微信公眾號開發者密碼)、MCHID(商戶ID)、KEY(商戶密鑰)。

微信公眾號支付應用的場景是在微信內部的H5環境中是用的支付方式。因為要通過網頁授權獲取用戶的OpenId,所以必須要配置網頁授權域名。同時要配置JS接口安全域名,如下圖所示:

技術分享圖片

以PHP為例,使用官方demo一個最常見的問題就是500錯誤,回調沒反應,這個一般情況下是xml數據解析出現的問題(錯誤在這裏WxPay.Data.php中WxPayDataBase類的FromXml()方法),解決方案如下:

技術分享圖片
public function FromXml($xml) {   
    if(!$xml) {  
        throw new WxPayException("xml數據異常!");  
    }  
    //將XML轉為array  
    //禁止引用外部xml實體  
    libxml_disable_entity_loader(true); //這句導致出現上述問題  
    $this->values = json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);       
    return $this->values;  
}  
技術分享圖片

問題就出現在libxml_disable_entity_loader(),它的作用是設置是否禁止從外部加載XML實體,設為true就是禁止。可以使用將代碼改成以下內容進行解決:

技術分享圖片
public function FromXml($xml) {  
    if(!$xml) {  
        throw new WxPayException("xml數據異常!");  
    }  
    //將XML轉為array  
    //禁止引用外部xml實體  
    $disableLibxmlEntityLoader = libxml_disable_entity_loader(true); //改為這句  
    $this->values = json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);  
    libxml_disable_entity_loader($disableLibxmlEntityLoader); //添加這句  
    return $this->values;  
}  
技術分享圖片

本人也嘗試過這樣一個簡單的方案,如下,直接屏蔽(在低版本PHP5.2中測試通過)libxml_disable_entity_loader():

技術分享圖片
public function FromXml($xml) {   
    if(!$xml) {  
        throw new WxPayException("xml數據異常!");  
    }  
    //將XML轉為array  
    //禁止引用外部xml實體  
    //libxml_disable_entity_loader(true); //或者是把這句屏蔽
    $this->values = json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);       
    return $this->values;  
}  
技術分享圖片

把這個解決之後就OK了。

還有一個問題就是“curl出錯,錯誤碼:60”,這個錯誤是由於服務器和PHP版本導致的,最近一次出現是在一個PHP5.2版本的原生項目上。微信官方對支付的數據傳輸提出了三點建議:

◆ 使用HTTPS確保網絡傳輸安全性。
◆ 禁用SSL等不安全協議和算法,建議使用TLS1.2。
◆ 不要輕易的嘗試設計和實現自己的加密傳輸算法,幾乎都會存在問題。

具體的錯誤信息在日誌裏面是這樣的:Fatal error: Uncaught exception ‘WxPayException‘ with message ‘curl出錯,錯誤碼:60‘ in ,目前的解決方案如下:

最原始的3.0demo裏面,找到WxPay.JsApiPay.php文件的99行:

curl_setopt($ch, CURLOP_TIMEOUT, 30); 

最早的example代碼裏少了一個“T”,這個問題官方已經解決,正確代碼應該是如下的:

curl_setopt($ch, CURLOPT_TIMEOUT, 30);

還有就是安全校驗的問題,在官方demo WxPay.Api.php 文件中找到如下代碼:

curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗

將上述代碼做出如下修改:

技術分享圖片
if(stripos($url,"https://")!==FALSE){
        curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        }    else    {
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗
} 
技術分享圖片

這樣的話,curl出錯,錯誤碼:60這個問題就可以解決了。

第二部分 微信H5支付

微信H5支付是微信官方2017年上半年剛剛對外開放的支付模式,它主要應用於在手機網站在移動瀏覽器(非微信環境)調用微信支付的場景。底層的技術以及支付鏈接本質上是財付通。

註意:微信H5支付需要在微信支付商戶平臺單獨申請開通,否則無法使用。

微信H5支付的流程比較簡單,就是拼接請求的xml數據,進行統一下單,獲取到支付的mweb_url,然後請求這個url網址就行。請求使用curl函數,使用的時候需要註意設置header參數。

技術分享圖片
    $headers = array();  
    $headers[] = ‘Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8‘;   
    $headers[] = ‘Connection: Keep-Alive‘;   
    $headers[] = ‘Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3‘;  
    $headers[] = ‘Accept-Encoding: gzip, deflate‘;  
    $headers[] = ‘User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20100101 Firefox/22.0‘; 
技術分享圖片

下面直接奉上整個流程源碼。

技術分享圖片
$userip = $_SERVER["REMOTE_ADDR"]; //獲得用戶設備IP
$appid = "wx24d9dbdf00000";//微信
$mch_id = "888888888";//微信官方的
$key = "FSDFSD2356DSD00";//自己設置的微信商家key

$nonce_str=MD5($out_trade_no);//隨機字符串
$total_fee = $total_fee*100; //金額
$spbill_create_ip = $userip; //IP
$notify_url = "http://www.bojuwang.net/"; //回調地址
$trade_type = ‘MWEB‘;//交易類型 具體看API 裏面有詳細介紹

 
 $scene_info =‘{"h5_info":{"type":"Wap","wap_url":"http://www.hnyjzpw.com","wap_name":"支付"}}‘;//場景信息 必要參數
 $signA ="appid=$appid&body=$body&mch_id=$mch_id&nonce_str=$nonce_str&notify_url=$notify_url&out_trade_no=$out_trade_no&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_fee&trade_type=$trade_type";

 $strSignTmp = $signA."&key=$key"; //拼接字符串  註意順序微信有個測試網址 順序按照他的來 直接點下面的校正測試 包括下面XML  是否正確
 $sign = strtoupper(MD5($strSignTmp)); // MD5 後轉換成大寫

 $post_data="<xml><appid>$appid</appid><body>$body</body><mch_id>$mch_id</mch_id><nonce_str>$nonce_str</nonce_str><notify_url>$notify_url</notify_url><out_trade_no>$out_trade_no</out_trade_no><scene_info>$scene_info</scene_info><spbill_create_ip>$spbill_create_ip</spbill_create_ip><total_fee>$total_fee</total_fee><trade_type>$trade_type</trade_type><sign>$sign</sign>
</xml>";//拼接成XML 格式


$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信傳參地址

$dataxml = http_post($url,$post_data,$headers); 
$objectxml = (array)simplexml_load_string($dataxml,‘SimpleXMLElement‘,LIBXML_NOCDATA); //將微信返回的XML 轉換成數組
function http_post($url=‘‘,$post_data=array(),$header=array(),$timeout=30) {
    
    $ch = curl_init();  
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳過證書檢查  
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  // 從證書中檢查SSL加密算法是否存在  
    curl_setopt($ch, CURLOPT_URL, $url);  
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);  
    curl_setopt($ch, CURLOPT_POST, true);  
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);   
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);  
    
    $response = curl_exec($ch);  

    curl_close($ch);

    return $response;
}

if($objectxml[‘return_code‘] == ‘SUCCESS‘){
    $mweb_url= $objectxml[‘mweb_url‘];
    // header("Location:$mweb_url");
}

$redirect_url = urlencode("http://www.bojuwang.net/");
技術分享圖片

H5支付的回調代碼如下,註意xml數據的接收。這是一個很大的坑,PHP需要使用 $GLOBALS[‘HTTP_RAW_POST_DATA‘]解析微信支付結果返回的xml。

技術分享圖片
$xml = $GLOBALS[‘HTTP_RAW_POST_DATA‘];
$dataxml = json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);//轉成數組,

if($dataxml[‘return_code‘] == ‘SUCCESS‘){
   //success
}
技術分享圖片

第三部分 微信掃碼支付

微信掃碼支付一般應用的場景是PC端電腦支付。微信掃碼支付可分為兩種模式,根據支付場景選擇相應模式。一般情況下的PC端掃碼支付選擇的是模式二,需要註意的是模式二無回調函數。

【模式一】商戶後臺系統根據微信支付規則鏈接生成二維碼,鏈接中帶固定參數productid(可定義為產品標識或訂單號)。用戶掃碼後,微信支付系統將productid和用戶唯一標識(openid)回調商戶後臺系統(需要設置支付回調URL),商戶後臺系統根據productid生成支付交易,最後微信支付系統發起用戶支付流程。

【模式二】商戶後臺系統調用微信支付【統一下單API】生成預付交易,將接口返回的鏈接生成二維碼,用戶掃碼後輸入密碼完成支付交易。註意:該模式的預付單有效期為2小時,過期後無法支付。

微信掃碼支付最友好的解決方案就是支付完成之後通過JS設置監聽函數,通過該函數完成跳轉。可參考的代碼如下:

技術分享圖片
<script type="text/javascript">
    $(function () {
        sendPost(); //調用監聽事件
    });
    //監聽訂單支付狀態
    function sendPost() {
        //發送AJAX請求
        $.ajax({
            url: "listen.aspx",
            type: "POST",
            timeout: 30000,
            data: { "order_no": "<%=order_no %>" },
            dataType: "json",
            success: function (data, type) {
                if (data.status == 1) {
                    $("#tipshow").show();
                    setTimeout(function () {
                        location.href = data.url; //支付成功後跳轉
                    }, 1000);
                } else {
                    time();
                }
            }
        });
    }
    function time() {
        setTimeout(function () {
            sendPost();
        }, 5000);
    }
</script>
技術分享圖片

第四部分 微信小程序支付

微信小程序支付是在小程序環境中使用的微信支付方式。

技術分享圖片

相對於上述幾個支付方式,微信小程序支付則顯得更簡單一些,不涉及到異步通知。在微信小程序中通過官方提供的API wx.requestPayment(OBJECT)發起微信支付,示例代碼如下:

技術分享圖片
wx.requestPayment({
   ‘timeStamp‘: ‘‘,
   ‘nonceStr‘: ‘‘,
   ‘package‘: ‘‘,
   ‘signType‘: ‘MD5‘,
   ‘paySign‘: ‘‘,
   ‘success‘:function(res){
   },
   ‘fail‘:function(res){
   }
})
技術分享圖片

通過上面我們可以看到,小程序支付的需要timeStamp、nonceStr、package、signType、paySign這五個參數。然後通過回調函數success或者是fail處理業務邏輯。

後臺處理程序的第一步還是統一下單,通過統一下單拿到prepay_id,然後獲取相關參數,通過接口(Ajax)傳到小程序端發起微信支付。因為統一下單需要用到用戶的OpenId,所以在發起統一下單之前要通過小程序的API wx.login(OBJECT)調用接口獲取登錄憑證(code)進而換取用戶登錄態信息,包括用戶的唯一標識(openid) 及本次登錄的 會話密鑰(session_key)等。用戶數據的加解密通訊需要依賴會話密鑰完成。主要是為了拿到OpenId!

涉及到是一個.NET項目,大致的.NET後臺代碼如下:

技術分享圖片
 1 /// <summary>
 2 /// 獲取支付的參數
 3 /// </summary>
 4 private void get_pay_params(HttpContext context)
 5 {
 6     string openId = BJRequest.GetFormString("openid");
 7     int user_id = BJRequest.GetFormIntValue("user_id", 0);
 8 
 9     string appId = MiniPayConfig.APPID;
10     string timeStamp = MiniPay.GenerateTimeStamp();
11     string nonceStr = MiniPay.GenerateNonceStr();
12     string signType = "MD5";
13     string outTradeNo=MiniPay.GenerateOutTradeNo();
14 
15     //獲取統一下單結果,主要是為了拿到prepay_id
16     MiniPay mnpay=new MiniPay();
17     MiniPayData unifiedOrderResult = mnpay.GetUnifiedOrderResult(openId,outTradeNo);
18 
19 
20     //小程序支付需要的參數
21     MiniPayData data = new MiniPayData();
22     data.SetValue("appId", appId);
23     data.SetValue("timeStamp", timeStamp);
24     data.SetValue("nonceStr", nonceStr);
25     data.SetValue("package", "prepay_id=" + unifiedOrderResult.GetValue("prepay_id"));
26     data.SetValue("signType", "MD5");
27     data.SetValue("paySign", data.MakeSign());
28 
29 
30     StringBuilder strTxt = new StringBuilder();
31 
32     strTxt.Append("{");
33     strTxt.Append("\"timeStamp\":\"" + timeStamp + "\"");
34     strTxt.Append(",\"nonceStr\":\"" + nonceStr + "\"");
35     strTxt.Append(",\"package\":\"" + data.GetValue("package") + "\"");
36     strTxt.Append(",\"signType\":\"" + signType + "\"");
37     strTxt.Append(",\"paySign\":\"" + data.GetValue("paySign") + "\"");
38     strTxt.Append("}");
39 
40     context.Response.Write(strTxt.ToString());
41 }
技術分享圖片

微信小程序端拿到參數之後,發起微信支付請求,小程序端代碼如下:

技術分享圖片
 1  //調起微信支付
 2   wxpay: function(){
 3       var that=this;
 4       wx.request({
 5         url: ‘https://888.com/api/order.ashx?action=get_pay_params‘,
 6         method: ‘post‘, 
 7         data: {
 8           openid: that.data.openId,
 9           user_id: that.data.userId
10         },
11         header: {
12           ‘Content-Type‘:  ‘application/x-www-form-urlencoded‘
13         },
14         success: function(res){
15           //if(res.data.status==1){
16             var order=res.data;
17             wx.requestPayment({
18               timeStamp: order.timeStamp,
19               nonceStr: order.nonceStr,
20               package: order.package,
21               signType: ‘MD5‘,
22               paySign: order.paySign,
23               success: function(res){
24                 
25                 //支付成功,處理相應的訂單
26                 wx.request({
27                   url: ‘https://8888.com/api/order.ashx?action=order_edit_pay‘,
28                   method: ‘post‘,
29                   data: {
30                     order_id: that.data.returnOrderId
31                   },
32                   header: {
33                     ‘Content-Type‘: ‘application/x-www-form-urlencoded‘
34                   },
35                   success: function (res) {
36                     var data = res.data;
37                     if (data.status == 1) {
38                       console.log("支付成功,處理訂單:" + that.data.returnOrderId);
39                       wx.showToast({
40                         title: "訂單支付並處理成功!",
41                         duration: 1000,
42                       });
43                       setTimeout(function () {
44                         wx.navigateTo({
45                           url: ‘../user/dingdan?currentTab=2&otype=deliver‘,
46                         });
47                       }, 1500);
48                     } else {
49                       wx.showToast({
50                         title: "訂單處理失敗!",
51                         duration: 2500
52                       });
53                     }
54                   },
55                   fail: function (e) {
56                     wx.showToast({
57                       title: ‘處理訂單網絡異常!‘,
58                       duration: 2000
59                     });
60                   }
61                 });
62               },
63               fail: function(res) {
64                 wx.showToast({
65                   title:‘支付失敗‘,
66                   duration:1000
67                 });
68                 wx.navigateTo({
69                   url: ‘../user/dingdan?currentTab=0&otype=all‘,
70                 });
71               }
72             })
73         },
74         fail: function() {
75           // fail
76           wx.showToast({
77             title: ‘支付時網絡異常!‘,
78             duration: 2000
79           });
80         }
81       })
82   }
技術分享圖片

第五部分 微信APP支付

微信APP支付是在APP應用中使用的微信支付方式。

最後,總結一下上述幾種支付方式需要註意的點。

1. 所有的支付參數都需要到微信支付商戶平臺(pay.weixin.qq.com)配置參數。

2. 微信公眾號支付、微信掃碼支付需要在微信公眾號裏面申請開通;APP支付需要在微信開放平臺申請開通(open.weixin.qq.com);小程序支付需要在小程序平臺申請開通。

3. 僅有公眾號支付和掃碼支付需配置支付域名,APP支付、刷卡支付無需配置域名。下圖就是在微信支付商戶平臺配置授權域名的界面。

技術分享圖片

4. 所有使用JS API方式發起支付請求的鏈接地址,都必須在當前頁面所配置的支付授權目錄之下。下單前需要調用【網頁授權獲取用戶信息】接口獲取到用戶的Openid。

5. 當公眾平臺接到掃碼支付請求時,會回調當前頁面所配置的支付回調鏈接傳遞訂單信息。

***微信公眾號支付+微信H5支付+微信掃碼支付+小程序支付+APP微信支付解決方案總結