隐藏

C#微信开发之旅(九):JSAPI支付(V3)

发布:2021/11/12 15:51:51作者:管理员 来源:本站 浏览次数:1680

微信开发遇到最复杂的就是支付了,无论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         }


   复制代码