c#微信回调中核实订单的签名验证

 首先从官方开放平台下载服务端c#开发sdk文件。然后找到里头一个 “支付结果通知回调处理类” ResultNotify.cs文件。主要处理流程都在下面这个方法里头。从这个方法里头衍生出其他的方法来。


      本来只有一个应用的话,也是不必要在来发文章讨论的,直接跟着sdk写好的方法走就成,但是我们项目最终是要有两个app的,也因此申请了两个微信移动应用,充值分别充入对应的商户里头。官方文档表明了 “通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”” 因此这导致回调都是分开的。一开始我想要直接使用一个配置文件【config.cs】,但是验证一直通不过。最后没办法,只得分开了两份代码,都具备各自的配置文件和其他方法。


    后来实在想不通,既然他的config.cs可以通过WxPayData进行重新赋值,没道理需要分开写。因此为了测试,我把以前充值的单号找出来,重新写了两个查询订单的方法,反复写测试返回参数比对查看。最终才发现问题的关键。


      1、首先不得不说下官方封装的HttpService.Post(xml, url, false, timeOut)//调用HTTP通信接口提交数据

                        图片1 config.cs

                           图片2 HttpService.cs

你说你这里晓得传递一个证书验证启用开关,怎么代理服务器的设置,就不晓得也整个开关,让我们自由选择呢。

结果我每次运行这个方法就提示超时。刚开始也没明白怎么整,直接找到config.cs的PROXY_URL端口配置为自己项目的服务器端口【/*默认IP和端口号分别为0.0.0.0和0,此时不开启代理(如有需要才设置)*/官方文档里头的注释,关键你这里写不开启,可是怎么到了方法里头默认的竟然是开启的,而且一点说明都木有!!】还是不行,都要疯了,大量查阅资料,终于找到有网友提出的解决方案,直接把这几行代码注释掉。


      2、是在验证签名过程,这还在自己本身,文档没有阅读仔细,

一开始以为签名都只需要【appid、mch_id、nonce_str、transaction_id+key】的,然后我在MakeSign()方法里头直接封装str参数串,在运行,返回的还是签名错误。

没办法只得在重写下几个用到的方法,然后到处打印出字符串,最后才发现在我们从远程获取到正确的数据后,是整个对象里头的参数都又进行了签名的。【appid、bank_type、cash_fee、fee_type......transaction_id+key】,然后又回头去看官网,文档里头提示 “微信接口可能增加字段,验证签名时必须支持增加的扩展字段”。果然文档提示的都要小心要注意!!


      最后,我想到的方案是修改MakeSign(string key)增加个key秘钥的传递。因为远程返回的xml里头都有appid和mch_id,因此只要稍微调整几个方法即可。完整代码如下:

[csharp]  view plain  copy
  1. //回调方法   
  2. public string Wx_Notify_url()  
  3. {  
  4.     string logid = Guid.NewGuid().ToString();  
  5.     const string key="......";//(获取商户api秘钥 )//////重点是这里这里这里,两个回调这里的key对应的是商户里头设置的秘钥 这里是常量常量常量  
  6.       
  7.     //对返回的支付结果通知的内容做签名验证  
  8.     WxPayAPI.WxPayData notifyData = GetNotifyData(key);  
  9.     //检查支付结果中transaction_id是否存在  
  10.     if (!notifyData.IsSet("transaction_id"))  
  11.     {  
  12.         //若transaction_id不存在,则立即返回结果给微信支付后台  
  13.         WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();  
  14.         res.SetValue("return_code""FAIL");  
  15.         res.SetValue("return_msg""支付结果中微信订单号不存在");  
  16.         //Log.Error(this.GetType().ToString(), "The Pay result is error : " + res.ToXml());                  
  17.         return res.ToXml();  
  18.     }  
  19.   
  20.     string transaction_id = notifyData.GetValue("transaction_id").ToString();  
  21.     //先写入记事本对账  
  22.     if (transaction_id != "") {  
  23.         //写入记事本  
  24.         DbHelperSQL.ExecuteSql("insert into [log] values('" + logid + "', 'transaction_id: " + transaction_id + ", 订单号:" + notifyData.GetValue("out_trade_no").ToString() + "', getdate(), 'wx')");  
  25.     }  
  26.   
  27.     //支付结果中查询订单,判断订单真实性   判断条件!QueryOrder(transaction_id)    不判断transaction_id == ""              
  28.     if (!QueryOrder(transaction_id, notifyData, key))  
  29.     {  
  30.         //若订单查询失败,则立即返回结果给微信支付后台  
  31.         WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();  
  32.         res.SetValue("return_code""FAIL");  
  33.         res.SetValue("return_msg""订单查询失败");  
  34.         return res.ToXml();  
  35.     }  
  36.     //查询订单成功  
  37.     else{  
  38.         //业务逻辑处理start...  
  39.         //////////////////////////////////////////////////////////////  
  40.         //更改订单状态  
  41.         if(.....){  
  42.             //插入币值记录                
  43.   
  44.         }  
  45.         /////////////////////////////////////////////////////////////                  
  46.         //业务逻辑处理end...  
  47.         WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();  
  48.         res.SetValue("return_code""SUCCESS");  
  49.         res.SetValue("return_msg""OK");  
  50.         return res.ToXml();  
  51.     }  
  52. }  
  53. /// <summary>  
  54. /// 接收从微信支付后台发送过来的数据并验证签名  
  55. /// </summary>  
  56. /// <returns>微信支付后台返回的数据</returns>  
  57. private WxPayAPI.WxPayData GetNotifyData(string KEY)  
  58. {  
  59.     //接收从微信后台POST过来的数据  
  60.     System.IO.Stream s = Request.InputStream;  
  61.     int count = 0;  
  62.     byte[] buffer = new byte[1024];  
  63.     StringBuilder builder = new StringBuilder();  
  64.     while ((count = s.Read(buffer, 0, 1024)) > 0)  
  65.     {  
  66.         builder.Append(Encoding.UTF8.GetString(buffer, 0, count));  
  67.     }  
  68.     s.Flush();  
  69.     s.Close();  
  70.     s.Dispose();  
  71.   
  72.     //Log.Info(this.GetType().ToString(), "Receive data from WeChat : " + builder.ToString());  
  73.     //转换数据格式并验证签名  
  74.     WxPayAPI.WxPayData data = new WxPayAPI.WxPayData();  
  75.     try  
  76.     {  
  77.         data.FromXml(builder.ToString(), KEY);  
  78.     }  
  79.     catch (WxPayAPI.WxPayException ex)  
  80.     {  
  81.         //若签名错误,则立即返回结果给微信支付后台  
  82.         WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();  
  83.         res.SetValue("return_code""FAIL");  
  84.         res.SetValue("return_msg", ex.Message);  
  85.         //Log.Error(this.GetType().ToString(), "Sign check error : " + res.ToXml());  
  86.         Response.Write(res.ToXml());  
  87.     }  
  88.   
  89.     //Log.Info(this.GetType().ToString(), "Check sign success");  
  90.     return data;  
  91. }  
  92. //查询订单  这里我重写了下,把远程返回的data和加密key直接传入  
  93. public bool QueryOrder(string transaction_id, WxPayData inputObj, string key)  
  94. {  
  95.     WxPayAPI.WxPayData req = new WxPayAPI.WxPayData();  
  96.     //增加对req进行赋值//////////////////////////////////////              
  97.     req.SetValue("appid", inputObj.GetValue("appid"));             
  98.   
  99.     req.SetValue("mch_id", inputObj.GetValue("mch_id"));  
  100.   
  101.     req.SetValue("key", key);  
  102.     ////////////////////////////////////////////////////////  
  103.     req.SetValue("transaction_id", transaction_id);  
  104.     WxPayAPI.WxPayData res = WxPayAPI.WxPayApi.OrderQuery(req);  
  105.     if (res.GetValue("return_code").ToString() == "FAIL")  
  106.         return false;  
  107.     if (res.GetValue("return_code").ToString() == "SUCCESS" &&  
  108.         res.GetValue("result_code").ToString() == "SUCCESS")  
  109.     {  
  110.         return true;  
  111.     }  
  112.     else  
  113.     {  
  114.         return false;  
  115.     }  
  116. }  


     然后,在QueryOrder()方法里头进行一些列sign验证,只要把这几个方法在重新调整下,即可!修改WxPayApi文件:

[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Web;  
  4. using System.Net;  
  5. using System.IO;  
  6. using System.Text;  
  7.   
  8. namespace WxPayAPI  
  9. {  
  10.     public class WxPayApi  
  11.     {                          
  12.         /** 
  13.         *     
  14.         * 查询订单 
  15.         * @param WxPayData inputObj 提交给查询订单API的参数 
  16.         * @param int timeOut 超时时间 
  17.         * @throws WxPayException 
  18.         * @return 成功时返回订单查询结果,其他抛异常 
  19.         */  
  20.         public static WxPayData OrderQuery(WxPayData inputObj, int timeOut = 6)  
  21.         {  
  22.             string url = "https://api.mch.weixin.qq.com/pay/orderquery";  
  23.             //检测必填参数  
  24.             if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))  
  25.             {  
  26.                 throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");  
  27.             }  
  28.             //1、我这里定义了临时WxPayData对象,因为inputObj把key也装进去了,而这里签名并不需要key,不然会出错,而且这个key是要到处使用的,    
  29.             //2、另外的解决方法就是string key = inputObj.GetValue("KEY").ToString();  然后移除key值,不过微信 WxPayData对象并没有封装这个方法,Directory字典有内部方法可以使用【m_values.Remove("key");//这里就可以直接根据他们文档来写个方法了】,偷懒了下,我选择方案1  
  30.             WxPayData tmp = new WxPayData();  
  31.             tmp.SetValue("transaction_id", inputObj.GetValue("transaction_id"));  
  32.             tmp.SetValue("appid", inputObj.GetValue("appid"));//公众账号ID  
  33.             tmp.SetValue("mch_id", inputObj.GetValue("mch_id"));//商户号  
  34.             tmp.SetValue("nonce_str", WxPayApi.GenerateNonceStr());//随机字符串  
  35.             tmp.SetValue("sign", tmp.MakeSign(inputObj.GetValue("key").ToString()));//签名  
  36.   
  37.             string xml = tmp.ToXml();  
  38.               
  39.             var start = DateTime.Now;  
  40.   
  41.             Log.Debug("WxPayApi""OrderQuery request : " + xml);  
  42.             string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口提交数据  
  43.             Log.Debug("WxPayApi""OrderQuery response : " + response);  
  44.   
  45.             var end = DateTime.Now;  
  46.             int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时  
  47.   
  48.             //将xml格式的数据转化为对象以返回  
  49.             WxPayData result = new WxPayData();  
  50.             result.FromXml(response, inputObj.GetValue("key").ToString());//这里增加传递秘钥key  
  51.   
  52.             ReportCostTime(url, timeCost, result, inputObj.GetValue("key").ToString());//测速上报   //不知道这个注销有没有影响,这个倒是没测  
  53.   
  54.             return result;  
  55.         }  
  56.   
  57.         /** 
  58.         *  
  59.         * 测速上报 
  60.         * @param string interface_url 接口URL 
  61.         * @param int timeCost 接口耗时 
  62.         * @param WxPayData inputObj参数数组 
  63.         */  
  64.         private static void ReportCostTime(string interface_url, int timeCost, WxPayData inputObj, string key)  
  65.         {  
  66.             //如果不需要进行上报  
  67.             if(WxPayConfig3.REPORT_LEVENL == 0)  
  68.             {  
  69.                 return;  
  70.             }   
  71.   
  72.             //如果仅失败上报  
  73.             if(WxPayConfig3.REPORT_LEVENL == 1 && inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&  
  74.              inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")  
  75.             {  
  76.                 return;  
  77.             }  
  78.            
  79.             //上报逻辑  
  80.             WxPayData3 data = new WxPayData3();  
  81.             data.SetValue("interface_url",interface_url);  
  82.             data.SetValue("execute_time_",timeCost);  
  83.             //返回状态码  
  84.             if(inputObj.IsSet("return_code"))  
  85.             {  
  86.                 data.SetValue("return_code",inputObj.GetValue("return_code"));  
  87.             }  
  88.             //返回信息  
  89.             if(inputObj.IsSet("return_msg"))  
  90.             {  
  91.                 data.SetValue("return_msg",inputObj.GetValue("return_msg"));  
  92.             }  
  93.             //业务结果  
  94.             if(inputObj.IsSet("result_code"))  
  95.             {  
  96.                 data.SetValue("result_code",inputObj.GetValue("result_code"));  
  97.             }  
  98.             //错误代码  
  99.             if(inputObj.IsSet("err_code"))  
  100.             {  
  101.                 data.SetValue("err_code",inputObj.GetValue("err_code"));  
  102.             }  
  103.             //错误代码描述  
  104.             if(inputObj.IsSet("err_code_des"))  
  105.             {  
  106.                 data.SetValue("err_code_des",inputObj.GetValue("err_code_des"));  
  107.             }  
  108.             //商户订单号  
  109.             if(inputObj.IsSet("out_trade_no"))  
  110.             {  
  111.                 data.SetValue("out_trade_no",inputObj.GetValue("out_trade_no"));  
  112.             }  
  113.             //设备号  
  114.             if(inputObj.IsSet("device_info"))  
  115.             {  
  116.                 data.SetValue("device_info",inputObj.GetValue("device_info"));  
  117.             }  
  118.           
  119.             try  
  120.             {  
  121.                 Report(data, key);///增加key值  
  122.             }  
  123.             catch (WxPayException ex)  
  124.             {  
  125.                 //不做任何处理  
  126.             }  
  127.         }  
  128.   
  129.   
  130.         /** 
  131.         *  
  132.         * 测速上报接口实现 
  133.         * @param WxPayData inputObj 提交给测速上报接口的参数 
  134.         * @param int timeOut 测速上报接口超时时间 
  135.         * @throws WxPayException 
  136.         * @return 成功时返回测速上报接口返回的结果,其他抛异常 
  137.         */  
  138.         public static WxPayData Report(WxPayData inputObj, string key, int timeOut = 1)  
  139.         {  
  140.             string url = "https://api.mch.weixin.qq.com/payitil/report";  
  141.             //检测必填参数  
  142.             if(!inputObj.IsSet("interface_url"))  
  143.             {  
  144.                 throw new WxPayException("接口URL,缺少必填参数interface_url!");  
  145.             }   
  146.             if(!inputObj.IsSet("return_code"))  
  147.             {  
  148.                 throw new WxPayException("返回状态码,缺少必填参数return_code!");  
  149.             }   
  150.             if(!inputObj.IsSet("result_code"))  
  151.             {  
  152.                 throw new WxPayException("业务结果,缺少必填参数result_code!");  
  153.             }   
  154.             if(!inputObj.IsSet("user_ip"))  
  155.             {  
  156.                 throw new WxPayException("访问接口IP,缺少必填参数user_ip!");  
  157.             }   
  158.             if(!inputObj.IsSet("execute_time_"))  
  159.             {  
  160.                 throw new WxPayException("接口耗时,缺少必填参数execute_time_!");  
  161.             }  
  162.   
  163.             inputObj.SetValue("appid",WxPayConfig3.APPID);//公众账号ID  
  164.             inputObj.SetValue("mch_id",WxPayConfig3.MCHID);//商户号  
  165.             inputObj.SetValue("user_ip",WxPayConfig3.IP);//终端ip  
  166.             inputObj.SetValue("time",DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间     
  167.             inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串  
  168.             inputObj.SetValue("sign", inputObj.MakeSign(key));//签名  
  169.             string xml = inputObj.ToXml();  
  170.   
  171.             Log.Info("WxPayApi""Report request : " + xml);  
  172.   
  173.             string response = HttpService.Post(xml, url, false, timeOut);  
  174.   
  175.             Log.Info("WxPayApi""Report response : " + response);  
  176.   
  177.             WxPayData result = new WxPayData();  
  178.             result.FromXml(response, key);///增加key值  
  179.             return result;  
  180.         }  
  181.   
  182.     }  
  183. }  

    然后,修改Data.cs文件:

[csharp]  view plain  copy
  1. /** 
  2.         * @将xml转为WxPayData对象并返回对象内部的数据 
  3.         * @param string 待转换的xml串 
  4.         * @return 经转换得到的Dictionary 
  5.         * @throws WxPayException 
  6.         */  
  7.         public SortedDictionary<stringobject> FromXml(string xml, string key/*传递秘钥*/)  
  8.         {  
  9.             if (string.IsNullOrEmpty(xml))  
  10.             {  
  11.                 Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");  
  12.                 throw new WxPayException("将空的xml串转换为WxPayData不合法!");  
  13.             }  
  14.   
  15.             XmlDocument xmlDoc = new XmlDocument();  
  16.             xmlDoc.LoadXml(xml);  
  17.             XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>  
  18.             XmlNodeList nodes = xmlNode.ChildNodes;  
  19.             foreach (XmlNode xn in nodes)  
  20.             {  
  21.                 XmlElement xe = (XmlElement)xn;  
  22.                 m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中  
  23.             }  
  24.   
  25.             try  
  26.             {  
  27.                 //2015-06-29 错误是没有签名  
  28.                 if (m_values["return_code"].ToString() != "SUCCESS")  
  29.                 {  
  30.                     return m_values;  
  31.                 }  
  32.                 CheckSign(key);//验证签名,不通过会抛异常  //在把秘钥传入验证签名的方法里头  
  33.             }  
  34.             catch (WxPayException ex)  
  35.             {  
  36.                 throw new WxPayException(ex.Message);  
  37.             }  
  38.   
  39.             return m_values;  
  40.         }  
  41.        /** 
  42.         *  
  43.         * 检测签名是否正确 
  44.         * 正确返回true,错误抛异常 
  45.         */  
  46.         public bool CheckSign(string key)  
  47.         {  
  48.             //如果没有设置签名,则跳过检测  
  49.             if (!IsSet("sign"))  
  50.             {  
  51.                Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");  
  52.                throw new WxPayException("WxPayData签名存在但不合法!");  
  53.             }  
  54.             //如果设置了签名但是签名为空,则抛异常  
  55.             else if(GetValue("sign") == null || GetValue("sign").ToString() == "")  
  56.             {  
  57.                 Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");  
  58.                 throw new WxPayException("WxPayData签名存在但不合法!");  
  59.             }  
  60.   
  61.             //获取接收到的签名  
  62.             string return_sign = GetValue("sign").ToString();  
  63.   
  64.             //在本地计算新的签名  
  65.             string cal_sign = MakeSign(key);//生成sign直接使用传入的key值,而不是config.cs配置好的默认的key值  
  66.   
  67.             if (cal_sign == return_sign)  
  68.             {  
  69.                 return true;  
  70.             }  
  71.   
  72.             Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");  
  73.             throw new WxPayException("WxPayData签名验证错误!");  
  74.         }  
  75.   
  76.         /** 
  77.         * @生成签名,详见签名生成算法 
  78.         * @return 签名, sign字段不参加签名 
  79.         */  
  80.         public string MakeSign(string key)  
  81.         {  
  82.             //转url格式  
  83.             string str = ToUrl();  
  84.             //在string后加入API KEY  
  85.             //str += "&key=" + WxPayConfig.KEY;//不适用写好的方法  
  86.             str += "&key=" + key;  
  87.             //MD5加密  
  88.             var md5 = MD5.Create();  
  89.             var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));  
  90.             var sb = new StringBuilder();  
  91.             foreach (byte b in bs)  
  92.             {  
  93.                 sb.Append(b.ToString("x2"));  
  94.             }  
  95.             //所有字符转为大写  
  96.             return sb.ToString().ToUpper();  
  97.         }  


      最后,补充下上面OrderQuery()说的方案二删除key的方法,已经测试过,有效!!

[csharp]  view plain  copy
  1.             //这个是WxPayApi.cs的调整  
  2.             string key = inputObj.GetValue("KEY").ToString();  
  3.             inputObj.RemoveTkey("KEY");  
  4.   
  5.             //Data.cs文件增加方法RemoveTkey              
  6.             /** 
  7.              * 根据字段名删除某个字段的值 
  8.              * @param key 字段名 
  9.              * @return key对应的字段值 
  10.             */  
  11.             public bool RemoveTkey(string Tkey)  
  12.             {  
  13.                 bool e = m_values.Remove(Tkey);  
  14.                 return e;  
  15.             }  

        经测试,返回参数如下:




猜你喜欢

转载自blog.csdn.net/yelin042/article/details/80708893