zoukankan      html  css  js  c++  java
  • C#微信开发之旅(九):JSAPI支付(V3)

    微信开发遇到最复杂的就是支付了,无论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         }
    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中,实现方式与前几篇中相似,这里就不上代码了。

    二、订单查询

    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方法:

     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

    三、通知

    微信支付通知以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         }
    View Code

    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     }
    View Code

    四、退款

    退款需要用到证书,配置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         }
    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
  • 相关阅读:
    助教学期总结
    助教学习总结
    第十二周助教总结
    第十一周助教总结
    第十周助教总结
    第九周助教总结
    第八周助教总结
    第八周作业——基础
    19秋第三周助教总结
    助教学习总结
  • 原文地址:https://www.cnblogs.com/hetring/p/4057447.html
Copyright © 2011-2022 走看看