坑爹的微信支付v3,其實沒有那么坑


研究微信開發一年多了,每個新接口,都會第一時間進行研究。微信支付開放很久,一直沒機會接觸到支付接口,等了好久終於從朋友那兒搞到了接口,從此開始了我兩天多的支付接口的研究。

 

拿到這個接口文檔的第一個想法就是這也沒什么難的嘛, 和支付寶、財付通、網銀在線等一些傳統接口的思路邏輯都是一樣的,覺得差不多最多一個下午就可以搞定,結果第一步調用統一支付接口就給來了個下馬威,不管怎么改,就一直返回簽名錯誤。第一次遇到簽名錯誤,首先想到的是應該是沒有正確理解簽名的生成規則,又從頭看了幾次簽名的生成規則,每次都是的理解都是一樣的,試了改幾次還是不行。 這一次已經開始懷疑騰訊的文檔寫的有問題,一邊找其他資料一邊在心里罵騰訊寫文檔的作者。在園子里看到了到處都是坑的微信支付V3后,更加確認是微信的文檔的問題。現在想想當時的想法太幼稚了,大部分自信心爆棚的人,在遇到解決不了的問題時總是會懷疑是不是別人給的東西不對,而不會從自身找問題,一句話總結就是一到便秘就怪地球沒引力。(各位看官請勿對號入座,純屬個人見解,勿噴)。

現在說正題。。

從開始遇到錯誤到最后解決簽名的問題,總結的問題就是我在生成簽名的時候把參數進行了編碼,而官方給的開發文檔並沒有說要做url編碼,另外一個就是我進入了一個死胡同,總覺得自己的理解與實現過程沒有問題,但最后當我把之前寫的代碼完全放棄,推倒重做后,問題終於解決。興奮之極。下面從頭說下我的理解與解決方法。

官方文檔中接口調用規則:

�  認證方式:HTTPS 認證,退款和沖正接口調用需要商戶證書(證書在審核郵件附件

中)

�  請求采用 POST 方式

�  提交和返回結果采用 XML 格式

�  字符集默認使用 UTF-8,請勿使用其它字符集

�  商戶與微信之間的交互(特別是 Native 回調和支付通知回調),都需要驗證簽名

�  處理返回時先判斷協議返回錯誤碼,再判斷業務返回錯誤碼,最后判斷交易狀態

 

下面是官方的簽名生成方法

a.對所有傳入參數按照字段名的 ASCII 碼從小到大排序(字典序)后,使用 URL 鍵值對的格式(即 key1=value1&key2=value2…)拼接成字符串 string1,注意:值為空的參數不參與簽名

b. 在 string1 最 后 拼 接 上 key=Key( 商 戶 支 付 密 鑰 ) 得 到 stringSignTemp 字 符 串 , 並 對 stringSignTemp 進行 md5 運算,再將得到的字符串所有字符 轉換為大寫,得到 sign 值

下面是我所理解的簽名生成規則:

1,所有的參數都是小寫的

2,參數的值不需要做任何處理,包括url編碼

3,確保必須的參數不能為空,且是正確無誤的。

下面是示范過程:

要傳入的參數分別為:appid,mch_id,nonce_str,body,attach,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,openid(jsapi必須) product_id(native必須)

首先將鍵值對存入 Dictionary<string,string>中,其次根據key值升序排序,代碼如下:    var dictemp = dic.OrderBy(d => d.Key);

然后將鍵值對轉換成url形式后,在末尾鏈接上key值,例如:appid=****&attach=****…………&key=******,最后進行md5加密並將加密后的字符串轉換成大寫。這里需要特別注意的是,md5加密是需要將字符集轉換成utf-8,否則中文商品描述會出現亂碼。

 1  public static string MD5(string pwd)
 2         {
 3             MD5 md5 = new MD5CryptoServiceProvider();
 4             byte[] data = System.Text.Encoding.UTF8.GetBytes(pwd);
 5             byte[] md5data = md5.ComputeHash(data);
 6             md5.Clear();
 7             string str = "";
 8             for (int i = 0; i < md5data.Length; i++)
 9             {
10                 str += md5data[i].ToString("x").PadLeft(2, '0');
11             }
12             return str;
13         }
md5加密

 

生成簽名后將sign=簽名  鍵值對添加到生成簽名時生成的dictemp中,然后將dictemp轉換成xml,post到https://api.mch.weixin.qq.com/pay/unifiedorder,返回值也是xml,最后對xml進行解析,為了保證安全性,需將解析后的鍵值對進行簽名校驗。

1 <xml><appid><![CDATA[******]]></appid><mch_id><![CDATA[******]]></mch_id><nonce_str><![CDATA[13120e01b82b48cfbebd4c9df66f0e47]]></nonce_str><body><![CDATA[神六]]></body><out_trade_no><![CDATA[ggggg673526]]></out_trade_no><total_fee><![CDATA[1000000]]></total_fee><spbill_create_ip><![CDATA[59.174.203.41]]></spbill_create_ip><notify_url><![CDATA[http://wxpay.ttyouni.net/aspx/order/notify.aspx]]></notify_url><trade_type><![CDATA[JSAPI]]></trade_type><openid><![CDATA[ozJkDj6yXuUsxIgS4xiJbtZMv2XQ]]></openid><sign><![CDATA[7CBA5A6BFF210BDA8C1AA33E9D803711]]></sign></xml>
正確的xml

校驗簽名無誤后,下一步就是取出預支付id prepay_id,然后調用微信支付js,注意:調用微信支付js之前也需要將所有參與調用的參數進行簽名,且這里的參與簽名的參數需要驗證遵守大小寫(騰訊有的時候真的很腦殘,一會全小寫,一會有大寫有小寫)。生成簽名后就可以調用微信支付js了,代碼如下:

 1 var WxPay= {
 2     Pay: function (appId, timeStamp, nonceStr, package, signType, paySign,callback) {
 3         WeixinJSBridge.invoke('getBrandWCPayRequest', {
 4             "appId": appId,    //公眾號名稱,由商戶傳入
 5             "timeStamp":timeStamp,    //時間戳,自 1970 年以來的秒數
 6             "nonceStr": nonceStr, //隨機串
 7             "package": package,
 8             "signType": signType,    //微信簽名方式
 9             "paySign": paySign //微信簽名
10         }, function (res) {
11             if (res.err_msg == "get_brand_wcpay_request:ok") {
12                 callback();
13             }
14             // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg 將在用戶支付成功后返回 ok,但並不保證它絕對可靠。
15         });
16     }
17 }
微信支付js

為了方便調用,我將微信支付js寫到了一個單獨的js文件,然后在頁面中載入,生成簽名用ajax調用。調用代碼如下:

 1 <script>
 2         $(function () {
 3             $("#submit").click(function () {
 4                 $.get("WxPay.ashx?action=jspayparam", {
 5                     body: $("#body").val(),
 6                     total_fee: $("#price").val(),
 7                     out_trade_no: $("#order").val(),
 8                     trade_type: "JSAPI",
 9                     msgid:"<%=openid%>"
10                 }, function (data) {
11                     WxPay.Pay(data.appId, data.timeStamp, data.nonceStr, data.package, data.signType, data.paySign, function () {
12                         alert("支付成功");
13                     });
14                 }, "json");
15 
16             });
17         })
18     </script>
支付js調用

這里我只傳入了一些和商品相關的參數,其他和商品無法的參數寫到了后台代碼中。后台收到請求后,將appid,mch_id等參數拼接成鍵值對進行進一步的處理,然后將處理后的結果返回給前台。

 

 1         void GetJsPayParam(HttpContext context)
 2         {
 3             JsEntities jsEntities = new JsEntities()
 4             {
 5                 appId = appid,
 6                 nonceStr = WxPayHelper.Utils.GetRandom(),
 7                 package = string.Format("prepay_id={0}", GetPrepayId(context)),
 8                 signType = "MD5",
 9                 timeStamp = WxPayHelper.Utils.ConvertDateTimeInt(DateTime.Now).ToString()
10             };
11             string url, sign;
12             WxPayHelper.Utils.GetUnifyUrlXml<JsEntities>(jsEntities, key, out url, out sign);
13             jsEntities.paySign = sign;
14             context.Response.Write(JsonConvert.SerializeObject(jsEntities));
15         }
獲取js支付參數

下面是生成鍵值對的方法,由於請求支付的過程中,到處需要生成簽名,所以我將各個請求參數都寫成了一個個類,然后使用泛型類和反射動態生成字典鍵值對,請求url和xml。代碼如下:

 1   public static string GetUnifyUrlXml<T>(T t,string key,out string url,out string _sign)
 2         {
 3             Type type = typeof (T);
 4             Dictionary<string,string> dic = new Dictionary<string, string>();
 5             PropertyInfo[] pis = type.GetProperties();
 6             #region 組合url參數到字典里
 7             foreach (PropertyInfo pi in pis)
 8             {
 9                 object val = pi.GetValue(t, null);
10                 if (val != null)
11                 {
12                     dic.Add(pi.Name, val.ToString());
13                 }
14             }
15             #endregion
16             //字典排序
17             var dictemp = dic.OrderBy(d => d.Key);
18             #region 生成url字符串
19             StringBuilder str = new StringBuilder();
20             foreach (var item in dictemp)
21             {
22                 str.AppendFormat("{0}={1}&", item.Key, item.Value);
23             }
24             #endregion
25             var ourl= str.ToString().Trim('&');
26             //加上key
27             string tempsign = ourl + "&key="+key;
28             //md5加密后,轉換成大寫
29             string sign = MD5(tempsign).ToUpper();
30             //將簽名添加到字典中
31             dic.Add("sign", sign);
32             _sign = sign;
33             url = str.AppendFormat("sign={0}",sign).ToString();
34             //生成請求的內容,並返回
35             return parseXML(dic);
36         }
生成鍵值對,url,xml

  到這里應該就可以滿足jsapi的需求了, 后期會將native和其他接口分享給大家。

 

 

 

 

如果你覺得本文對你有幫助,請大方的掃下面的二維碼懸賞一下吧。

新建了個微信支付及微信開發的QQ群,歡迎大家加入一起交流微信開發技術。C#微信開發交流

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM