C#微信開發之旅(九):JSAPI支付(V3)

微信開發遇到最複雜的就是支付了,不管V2仍是V3。這篇文章將給出全套的V3版本JSAPI支付代碼,包括預支付->支付->訂單查詢->通知->退款,其中前三步已經上線應用,退款只是簡單測試了一下,你們要用的話須要謹慎。。。node

1、預支付&支付

實際就是講訂單信息交給微信端,返回給咱們一個預支付id(與V2app支付類似),支付時將預支付id交給微信處理。注意:預支付id 需存儲,每一個out_trade_no(咱們本身的訂單號)只能對應一個預支付id。代碼奉上:(mvc demo 最後會一併發出)api

 1         public ActionResult Pay()
 2         {
 3             string code = "";//網頁受權得到的code
 4             string orderNo = ""; //文檔中的out_trade_no
 5             string description = ""; //商品描述
 6             string totalFee = "1";//訂單金額(單位:分)
 7             string createIp = "127.0.0.1";
 8             string notifyUrl = ""; //通知url
 9             string openId = WeiXinHelper.GetUserOpenId(code);//經過網頁受權code獲取用戶openid(或者以前有存儲用戶的openid 也能夠直接拿來用)
10 
11             //prepayid 只有第一次支付時生成,若是須要再次支付,必須拿以前生成的prepayid。
12             //也就是說一個orderNo 只能對應一個prepayid
13             string prepayid = string.Empty;
14 
15             #region 以前生成過 prepayid,此處可略過
16 
17             //建立Model
18             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
19 
20             //預支付
21             UnifiedPrePayMessage result = WeiXinHelper.UnifiedPrePay(model.CreatePrePayPackage(description, orderNo, totalFee, createIp, notifyUrl, openId));
22 
23             if (result == null
24                     || !result.ReturnSuccess
25                     || !result.ResultSuccess
26                     || string.IsNullOrEmpty(result.Prepay_Id))
27             {
28                 throw new Exception("獲取PrepayId 失敗");
29             }
30 
31             //預支付訂單
32             prepayid = result.Prepay_Id;
33 
34             #endregion
35             
36             //JSAPI 支付參數的Model
37             PayModel payModel = new PayModel()
38             {
39                 AppId = model.AppId,
40                 Package = string.Format("prepay_id={0}", prepayid),
41                 Timestamp = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000).ToString(),
42                 Noncestr = CommonUtil.CreateNoncestr(),
43             };
44 
45             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
46             nativeObj.Add("appId", payModel.AppId);
47             nativeObj.Add("package", payModel.Package);
48             nativeObj.Add("timeStamp", payModel.Timestamp);
49             nativeObj.Add("nonceStr", payModel.Noncestr);
50             nativeObj.Add("signType", payModel.SignType);
51             payModel.PaySign = model.GetCftPackage(nativeObj); //生成JSAPI 支付簽名
52 
53 
54             return View(payModel);
55         }
View Code

UnifiedWxPayModel 爲V3統一支付幫助類,包括V3相關接口參數生成及簽名的實現:服務器

這裏用到的 生成預支付請求參數Xml:微信

 1         #region 生成 預支付 請求參數(XML)
 2         /// <summary>
 3         /// 生成 預支付 請求參數(XML)
 4         /// </summary>
 5         /// <param name="description"></param>
 6         /// <param name="tradeNo"></param>
 7         /// <param name="totalFee"></param>
 8         /// <param name="createIp"></param>
 9         /// <param name="notifyUrl"></param>
10         /// <param name="openid"></param>
11         /// <returns></returns>
12         public string CreatePrePayPackage(string description, string tradeNo, string totalFee, string createIp, string notifyUrl, string openid)
13         {
14             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
15 
16             nativeObj.Add("appid", AppId);
17             nativeObj.Add("mch_id", PartnerId);
18             nativeObj.Add("nonce_str", CommonUtil.CreateNoncestr());
19             nativeObj.Add("body", description);
20             nativeObj.Add("out_trade_no", tradeNo);
21             nativeObj.Add("total_fee", totalFee); //todo:寫死爲1
22             nativeObj.Add("spbill_create_ip", createIp);
23             nativeObj.Add("notify_url", notifyUrl);
24             nativeObj.Add("trade_type", "JSAPI");
25             nativeObj.Add("openid", openid);
26             nativeObj.Add("sign", GetCftPackage(nativeObj));
27 
28             return DictionaryToXmlString(nativeObj);
29         }
30 
31         #endregion
View Code

預支付請求在WeiXinHelper中,實現方式與前幾篇中類似,這裏就不上代碼了。微信開發

2、訂單查詢

JSAPI返回支付成功,咱們須要到後臺查詢下訂單狀態以肯定支付是否成功,若是後臺未接到通知,則要到微信服務器查詢訂單狀態;最後才能展現給用戶支付的結果:併發

 1         /// <summary>
 2         /// 到微信服務器查詢 訂單支付的結果 (jsapi支付返回ok,咱們要判斷下服務器支付狀態,若是沒有支付成功,到微信服務器查詢)
 3         /// </summary>
 4         /// <param name="orderNo"></param>
 5         public bool QueryOrder(string orderNo)
 6         {
 7             //這裏應先判斷服務器 訂單支付狀態,若是接到通知,並已經支付成功,就不用 執行下面的查詢了
 8             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
 9             UnifiedOrderQueryMessage message = WeiXinHelper.UnifiedOrderQuery(model.CreateOrderQueryXml(orderNo));
10             //此處主動查詢的結果,只作查詢用(不能做爲支付成功的依據)
11             return message.Success;
12         }
View Code

生成訂單查詢Xml方法:mvc

 1         #region 建立訂單查詢 XML
 2         /// <summary>
 3         /// 建立訂單查詢 XML
 4         /// </summary>
 5         /// <param name="orderNo"></param>
 6         /// <returns></returns>
 7         public string CreateOrderQueryXml(string orderNo)
 8         {
 9             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
10 
11             nativeObj.Add("appid", AppId);
12             nativeObj.Add("mch_id", PartnerId);
13             nativeObj.Add("out_trade_no", orderNo);
14             nativeObj.Add("nonce_str", CommonUtil.CreateNoncestr());
15             nativeObj.Add("sign", GetCftPackage(nativeObj));
16 
17             return DictionaryToXmlString(nativeObj);
18         }
19         #endregion
View Code

3、通知

微信支付通知以Post Xml方式:app

 1         /// <summary>
 2         /// 微信支付通知(貌似比較臃腫,待優化)
 3         /// </summary>
 4         /// <returns></returns>
 5         public void Notify()
 6         {
 7             ReturnMessage returnMsg = new ReturnMessage() { Return_Code = "SUCCESS", Return_Msg = "" };
 8             string xmlString = GetXmlString(Request);
 9             NotifyMessage message = null;
10             try
11             {
12                 //此處應記錄日誌
13                 message = HttpClientHelper.XmlDeserialize<NotifyMessage>(xmlString);
14 
15                 #region 驗證簽名並處理通知
16                 XmlDocument doc = new XmlDocument();
17                 doc.LoadXml(xmlString);
18 
19                 Dictionary<string, string> dic = new Dictionary<string, string>();
20                 string sign = string.Empty;
21                 foreach (XmlNode node in doc.FirstChild.ChildNodes)
22                 {
23                     if (node.Name.ToLower() != "sign")
24                         dic.Add(node.Name, node.InnerText);
25                     else
26                         sign = node.InnerText;
27                 }
28 
29                 UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
30                 if (model.ValidateMD5Signature(dic, sign))
31                 {
32                     //處理通知
33                 }
34                 else
35                 {
36                     throw new Exception("簽名未經過!");
37                 }
38 
39                 #endregion
40 
41             }
42             catch (Exception ex)
43             {
44                 //此處記錄異常日誌
45                 returnMsg.Return_Code = "FAIL";
46                 returnMsg.Return_Msg = ex.Message;
47             }
48             Response.Write(returnMsg.ToXmlString());
49             Response.End();
50         }
51 
52         /// <summary>
53         /// 獲取Post Xml數據
54         /// </summary>
55         /// <param name="request"></param>
56         /// <returns></returns>
57         private string GetXmlString(HttpRequestBase request)
58         {
59             using (System.IO.Stream stream = request.InputStream)
60             {
61                 Byte[] postBytes = new Byte[stream.Length];
62                 stream.Read(postBytes, 0, (Int32)stream.Length);
63                 return System.Text.Encoding.UTF8.GetString(postBytes);
64             }
65         }
View Code

ReturnMessage是調用V3接口返回消息基類,也包含了給微信返回消息的方法:ide

 1     /// <summary>
 2     /// 消息基類
 3     /// </summary>
 4     public class ReturnMessage
 5     {
 6         [XmlElement("return_code")]
 7         public string Return_Code { get; set; }
 8 
 9         [XmlElement("return_msg")]
10         public string Return_Msg { get; set; }
11 
12         public string ToXmlString()
13         {
14             return string.Format(@"<xml><return_code><![CDATA[{0}]]></return_code>
15                     <return_msg><![CDATA[{1}]]></return_msg></xml>", Return_Code, Return_Msg);
16         }
17     }
View Code

4、退款

退款須要用到證書,配置WeiXinConst內證書相關常量再使用:post

 1         /// <summary>
 2         /// 訂單退款
 3         /// </summary>
 4         /// <param name="transaction_Id">微信交易單號</param>
 5         /// <param name="orderNo">咱們本身的單號</param>
 6         /// <param name="totalFee">訂單金額(分)</param>
 7         /// <param name="refundNo">退款單號(咱們本身定義)</param>
 8         /// <param name="refundFee">退款金額(分)</param>
 9         /// <returns></returns>
10         public bool UnifiedOrderRefund(string transaction_Id,string orderNo,string totalFee, string refundNo,string refundFee)
11         {
12             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
13             string postData = model.CreateOrderRefundXml(orderNo, transaction_Id, totalFee, refundNo, refundFee);
14             //退款須要用到證書, 要配置WeiXineConst CertPath 和 CertPwd
15             return WeiXinHelper.Refund(postData, WeiXinConst.CertPath, WeiXinConst.CertPwd);
16         }
View Code

建立訂單退款Xml:

 1         #region 建立訂單退款 XML
 2         /// <summary>
 3         /// 建立訂單退款 XML
 4         /// </summary>
 5         /// <param name="orderNo">商戶訂單號</param>
 6         /// <param name="transactionId">微信訂單號</param>
 7         /// <param name="totalFee">總金額</param>
 8         /// <param name="refundNo">退款訂單號</param>
 9         /// <param name="refundFee">退款金額</param>
10         /// <returns></returns>
11         public string CreateOrderRefundXml(string orderNo, string transactionId, string totalFee, string refundNo, string refundFee)
12         {
13             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
14 
15             nativeObj.Add("appid", AppId);
16             nativeObj.Add("mch_id", WeiXinConst.PartnerId);
17             nativeObj.Add("nonce_str", CommonUtil.CreateNoncestr());
18             if (string.IsNullOrEmpty(transactionId))
19             {
20                 if (string.IsNullOrEmpty(orderNo))
21                     throw new Exception("缺乏訂單號!");
22                 nativeObj.Add("out_trade_no", orderNo);
23             }
24             else
25             {
26                 nativeObj.Add("transaction_id", transactionId);
27             }
28 
29             nativeObj.Add("out_refund_no", refundNo);
30             nativeObj.Add("total_fee", totalFee);
31             nativeObj.Add("refund_fee", refundFee);
32             nativeObj.Add("op_user_id", PartnerId); //todo:配置
33 
34             nativeObj.Add("sign", GetCftPackage(nativeObj));
35 
36             return DictionaryToXmlString(nativeObj);
37         }
38 
39         #endregion
View Code

WeiXinHelper中V3退款方法:

 1         #region V3 申請退款
 2 
 3         /// <summary>
 4         /// 申請退款(V3接口)
 5         /// </summary>
 6         /// <param name="postData">請求參數</param>
 7         /// <param name="certPath">證書路徑</param>
 8         /// <param name="certPwd">證書密碼</param>
 9         public static bool Refund(string postData, string certPath, string certPwd)
10         {
11             string url = WeiXinConst.WeiXin_Pay_UnifiedOrderRefundUrl;
12             RefundMessage message = RefundHelper.PostXmlResponse<RefundMessage>(url, postData, certPath, certPwd);
13             return message.Success;
14         }
15 
16         #endregion
View Code

V3退款幫助類:

 1     /// <summary>
 2     /// V3退款幫助類
 3     /// </summary>
 4     public class RefundHelper
 5     {
 6         /// <summary>
 7         /// 證書驗證的 post請求
 8         /// </summary>
 9         /// <typeparam name="T"></typeparam>
10         /// <param name="url">請求Url</param>
11         /// <param name="postData">post數據</param>
12         /// <param name="certPath">證書路徑</param>
13         /// <param name="certPwd">證書密碼</param>
14         /// <returns></returns>
15         public static T PostXmlResponse<T>(string url, string postData, string certPath, string certPwd) where T : class
16         {
17             if (url.StartsWith("https"))
18                 System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
19 
20             HttpWebRequest hp = (HttpWebRequest)WebRequest.Create(url);
21 
22             ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
23 
24             hp.ClientCertificates.Add(new X509Certificate2(certPath, certPwd));
25 
26             var encoding = System.Text.Encoding.UTF8;
27             byte[] data = encoding.GetBytes(postData);
28 
29             hp.Method = "POST";
30 
31             hp.ContentType = "application/x-www-form-urlencoded";
32 
33             hp.ContentLength = data.Length;
34 
35             using (Stream ws = hp.GetRequestStream())
36             {
37                 // 發送數據
38                 ws.Write(data, 0, data.Length);
39                 ws.Close();
40 
41                 using (HttpWebResponse wr = (HttpWebResponse)hp.GetResponse())
42                 {
43                     using (StreamReader sr = new StreamReader(wr.GetResponseStream(), encoding))
44                     {
45                         return HttpClientHelper.XmlDeserialize<T>(sr.ReadToEnd());
46                     }
47                 }
48             }
49         }
50 
51         //驗證服務器證書
52         private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
53         {
54             return true;
55         }
56     }
View Code

私有的方法:

  1. Dictionary<string,string>轉爲XmlDocument 
     1         /// <summary>
     2         /// dictionary轉爲xml 字符串
     3         /// </summary>
     4         /// <param name="dic"></param>
     5         /// <returns></returns>
     6         private static string DictionaryToXmlString(Dictionary<string, string> dic)
     7         {
     8             StringBuilder xmlString = new StringBuilder();
     9             xmlString.Append("<xml>");
    10             foreach (string key in dic.Keys)
    11             {
    12                 xmlString.Append(string.Format("<{0}><![CDATA[{1}]]></{0}>", key, dic[key]));
    13             }
    14             xmlString.Append("</xml>");
    15             return xmlString.ToString();
    16         }
    View Code
  2. XmlDocument轉爲Dictionary<string,string> 
     1         /// <summary>
     2         /// xml字符串 轉換爲  dictionary
     3         /// </summary>
     4         /// <param name="document"></param>
     5         /// <returns></returns>
     6         public static Dictionary<string, string> XmlToDictionary(string xmlString)
     7         {
     8             System.Xml.XmlDocument document = new System.Xml.XmlDocument();
     9             document.LoadXml(xmlString);
    10 
    11             Dictionary<string, string> dic = new Dictionary<string, string>();
    12 
    13             var nodes = document.FirstChild.ChildNodes;
    14 
    15             foreach (System.Xml.XmlNode item in nodes)
    16             {
    17                 dic.Add(item.Name, item.InnerText);
    18             }
    19             return dic;
    20         }
    View Code
相關文章
相關標籤/搜索