微信开发遇到最复杂的就是支付了,无论V2还是V3。这篇文章将给出全套的V3版本JSAPI支付代码,包括预支付->支付->订单查询->通知->退款,其中前三步已经上线应用,退款只是简单测试了一下,大家要用的话需要谨慎。。。
一、预支付&支付
实际就是讲订单信息交给微信端,返回给我们一个预支付id(与V2app支付相似),支付时将预支付id交给微信处理。注意:预支付id 需存储,每个out_trade_no(我们自己的订单号)只能对应一个预支付id。代码奉上:(mvc demo 最后会一并发出)
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 }
三、通知
微信支付通知以Post Xml方式:
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 }
四、退款
退款需要用到证书,配置WeiXinConst内证书相关常量再使用:
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 }
私有的方法:
- 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 }
- 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 }