发布:2021/11/12 15:51:51作者:管理员 来源:本站 浏览次数:1721
微信开发遇到最复杂的就是支付了,无论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 }
复制代码
生成订单查询Xml方法:
复制代码
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方式:
复制代码
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接口返回消息基类,也包含了给微信返回消息的方法:
复制代码
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内证书相关常量再使用:
复制代码
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 }
复制代码
私有的方法:
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 }
复制代码
© Copyright 2014 - 2024 柏港建站平台 ejk5.com. 渝ICP备16000791号-4