微信開發遇到最複雜的就是支付了,不管V2仍是V3。這篇文章將給出全套的V3版本JSAPI支付代碼,包括預支付->支付->訂單查詢->通知->退款,其中前三步已經上線應用,退款只是簡單測試了一下,你們要用的話須要謹慎。。。node
實際就是講訂單信息交給微信端,返回給咱們一個預支付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 }
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
預支付請求在WeiXinHelper中,實現方式與前幾篇中類似,這裏就不上代碼了。微信開發
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 }
生成訂單查詢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
微信支付通知以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 }
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 }
退款須要用到證書,配置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 }
建立訂單退款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
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
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 }
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 }
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 }