首先从官方开放平台下载服务端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,因此只要稍微调整几个方法即可。完整代码如下:
- //回调方法
- public string Wx_Notify_url()
- {
- string logid = Guid.NewGuid().ToString();
- const string key="......";//(获取商户api秘钥 )//////重点是这里这里这里,两个回调这里的key对应的是商户里头设置的秘钥 这里是常量常量常量
- //对返回的支付结果通知的内容做签名验证
- WxPayAPI.WxPayData notifyData = GetNotifyData(key);
- //检查支付结果中transaction_id是否存在
- if (!notifyData.IsSet("transaction_id"))
- {
- //若transaction_id不存在,则立即返回结果给微信支付后台
- WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
- res.SetValue("return_code", "FAIL");
- res.SetValue("return_msg", "支付结果中微信订单号不存在");
- //Log.Error(this.GetType().ToString(), "The Pay result is error : " + res.ToXml());
- return res.ToXml();
- }
- string transaction_id = notifyData.GetValue("transaction_id").ToString();
- //先写入记事本对账
- if (transaction_id != "") {
- //写入记事本
- DbHelperSQL.ExecuteSql("insert into [log] values('" + logid + "', 'transaction_id: " + transaction_id + ", 订单号:" + notifyData.GetValue("out_trade_no").ToString() + "', getdate(), 'wx')");
- }
- //支付结果中查询订单,判断订单真实性 判断条件!QueryOrder(transaction_id) 不判断transaction_id == ""
- if (!QueryOrder(transaction_id, notifyData, key))
- {
- //若订单查询失败,则立即返回结果给微信支付后台
- WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
- res.SetValue("return_code", "FAIL");
- res.SetValue("return_msg", "订单查询失败");
- return res.ToXml();
- }
- //查询订单成功
- else{
- //业务逻辑处理start...
- //////////////////////////////////////////////////////////////
- //更改订单状态
- if(.....){
- //插入币值记录
- }
- /////////////////////////////////////////////////////////////
- //业务逻辑处理end...
- WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
- res.SetValue("return_code", "SUCCESS");
- res.SetValue("return_msg", "OK");
- return res.ToXml();
- }
- }
- /// <summary>
- /// 接收从微信支付后台发送过来的数据并验证签名
- /// </summary>
- /// <returns>微信支付后台返回的数据</returns>
- private WxPayAPI.WxPayData GetNotifyData(string KEY)
- {
- //接收从微信后台POST过来的数据
- System.IO.Stream s = Request.InputStream;
- int count = 0;
- byte[] buffer = new byte[1024];
- StringBuilder builder = new StringBuilder();
- while ((count = s.Read(buffer, 0, 1024)) > 0)
- {
- builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
- }
- s.Flush();
- s.Close();
- s.Dispose();
- //Log.Info(this.GetType().ToString(), "Receive data from WeChat : " + builder.ToString());
- //转换数据格式并验证签名
- WxPayAPI.WxPayData data = new WxPayAPI.WxPayData();
- try
- {
- data.FromXml(builder.ToString(), KEY);
- }
- catch (WxPayAPI.WxPayException ex)
- {
- //若签名错误,则立即返回结果给微信支付后台
- WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
- res.SetValue("return_code", "FAIL");
- res.SetValue("return_msg", ex.Message);
- //Log.Error(this.GetType().ToString(), "Sign check error : " + res.ToXml());
- Response.Write(res.ToXml());
- }
- //Log.Info(this.GetType().ToString(), "Check sign success");
- return data;
- }
- //查询订单 这里我重写了下,把远程返回的data和加密key直接传入
- public bool QueryOrder(string transaction_id, WxPayData inputObj, string key)
- {
- WxPayAPI.WxPayData req = new WxPayAPI.WxPayData();
- //增加对req进行赋值//////////////////////////////////////
- req.SetValue("appid", inputObj.GetValue("appid"));
- req.SetValue("mch_id", inputObj.GetValue("mch_id"));
- req.SetValue("key", key);
- ////////////////////////////////////////////////////////
- req.SetValue("transaction_id", transaction_id);
- WxPayAPI.WxPayData res = WxPayAPI.WxPayApi.OrderQuery(req);
- if (res.GetValue("return_code").ToString() == "FAIL")
- return false;
- if (res.GetValue("return_code").ToString() == "SUCCESS" &&
- res.GetValue("result_code").ToString() == "SUCCESS")
- {
- return true;
- }
- else
- {
- return false;
- }
- }
然后,在QueryOrder()方法里头进行一些列sign验证,只要把这几个方法在重新调整下,即可!修改WxPayApi文件:
- using System;
- using System.Collections.Generic;
- using System.Web;
- using System.Net;
- using System.IO;
- using System.Text;
- namespace WxPayAPI
- {
- public class WxPayApi
- {
- /**
- *
- * 查询订单
- * @param WxPayData inputObj 提交给查询订单API的参数
- * @param int timeOut 超时时间
- * @throws WxPayException
- * @return 成功时返回订单查询结果,其他抛异常
- */
- public static WxPayData OrderQuery(WxPayData inputObj, int timeOut = 6)
- {
- string url = "https://api.mch.weixin.qq.com/pay/orderquery";
- //检测必填参数
- if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
- {
- throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
- }
- //1、我这里定义了临时WxPayData对象,因为inputObj把key也装进去了,而这里签名并不需要key,不然会出错,而且这个key是要到处使用的,
- //2、另外的解决方法就是string key = inputObj.GetValue("KEY").ToString(); 然后移除key值,不过微信 WxPayData对象并没有封装这个方法,Directory字典有内部方法可以使用【m_values.Remove("key");//这里就可以直接根据他们文档来写个方法了】,偷懒了下,我选择方案1
- WxPayData tmp = new WxPayData();
- tmp.SetValue("transaction_id", inputObj.GetValue("transaction_id"));
- tmp.SetValue("appid", inputObj.GetValue("appid"));//公众账号ID
- tmp.SetValue("mch_id", inputObj.GetValue("mch_id"));//商户号
- tmp.SetValue("nonce_str", WxPayApi.GenerateNonceStr());//随机字符串
- tmp.SetValue("sign", tmp.MakeSign(inputObj.GetValue("key").ToString()));//签名
- string xml = tmp.ToXml();
- var start = DateTime.Now;
- Log.Debug("WxPayApi", "OrderQuery request : " + xml);
- string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口提交数据
- Log.Debug("WxPayApi", "OrderQuery response : " + response);
- var end = DateTime.Now;
- int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
- //将xml格式的数据转化为对象以返回
- WxPayData result = new WxPayData();
- result.FromXml(response, inputObj.GetValue("key").ToString());//这里增加传递秘钥key
- ReportCostTime(url, timeCost, result, inputObj.GetValue("key").ToString());//测速上报 //不知道这个注销有没有影响,这个倒是没测
- return result;
- }
- /**
- *
- * 测速上报
- * @param string interface_url 接口URL
- * @param int timeCost 接口耗时
- * @param WxPayData inputObj参数数组
- */
- private static void ReportCostTime(string interface_url, int timeCost, WxPayData inputObj, string key)
- {
- //如果不需要进行上报
- if(WxPayConfig3.REPORT_LEVENL == 0)
- {
- return;
- }
- //如果仅失败上报
- if(WxPayConfig3.REPORT_LEVENL == 1 && inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
- inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
- {
- return;
- }
- //上报逻辑
- WxPayData3 data = new WxPayData3();
- data.SetValue("interface_url",interface_url);
- data.SetValue("execute_time_",timeCost);
- //返回状态码
- if(inputObj.IsSet("return_code"))
- {
- data.SetValue("return_code",inputObj.GetValue("return_code"));
- }
- //返回信息
- if(inputObj.IsSet("return_msg"))
- {
- data.SetValue("return_msg",inputObj.GetValue("return_msg"));
- }
- //业务结果
- if(inputObj.IsSet("result_code"))
- {
- data.SetValue("result_code",inputObj.GetValue("result_code"));
- }
- //错误代码
- if(inputObj.IsSet("err_code"))
- {
- data.SetValue("err_code",inputObj.GetValue("err_code"));
- }
- //错误代码描述
- if(inputObj.IsSet("err_code_des"))
- {
- data.SetValue("err_code_des",inputObj.GetValue("err_code_des"));
- }
- //商户订单号
- if(inputObj.IsSet("out_trade_no"))
- {
- data.SetValue("out_trade_no",inputObj.GetValue("out_trade_no"));
- }
- //设备号
- if(inputObj.IsSet("device_info"))
- {
- data.SetValue("device_info",inputObj.GetValue("device_info"));
- }
- try
- {
- Report(data, key);///增加key值
- }
- catch (WxPayException ex)
- {
- //不做任何处理
- }
- }
- /**
- *
- * 测速上报接口实现
- * @param WxPayData inputObj 提交给测速上报接口的参数
- * @param int timeOut 测速上报接口超时时间
- * @throws WxPayException
- * @return 成功时返回测速上报接口返回的结果,其他抛异常
- */
- public static WxPayData Report(WxPayData inputObj, string key, int timeOut = 1)
- {
- string url = "https://api.mch.weixin.qq.com/payitil/report";
- //检测必填参数
- if(!inputObj.IsSet("interface_url"))
- {
- throw new WxPayException("接口URL,缺少必填参数interface_url!");
- }
- if(!inputObj.IsSet("return_code"))
- {
- throw new WxPayException("返回状态码,缺少必填参数return_code!");
- }
- if(!inputObj.IsSet("result_code"))
- {
- throw new WxPayException("业务结果,缺少必填参数result_code!");
- }
- if(!inputObj.IsSet("user_ip"))
- {
- throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
- }
- if(!inputObj.IsSet("execute_time_"))
- {
- throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
- }
- inputObj.SetValue("appid",WxPayConfig3.APPID);//公众账号ID
- inputObj.SetValue("mch_id",WxPayConfig3.MCHID);//商户号
- inputObj.SetValue("user_ip",WxPayConfig3.IP);//终端ip
- inputObj.SetValue("time",DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间
- inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串
- inputObj.SetValue("sign", inputObj.MakeSign(key));//签名
- string xml = inputObj.ToXml();
- Log.Info("WxPayApi", "Report request : " + xml);
- string response = HttpService.Post(xml, url, false, timeOut);
- Log.Info("WxPayApi", "Report response : " + response);
- WxPayData result = new WxPayData();
- result.FromXml(response, key);///增加key值
- return result;
- }
- }
- }
然后,修改Data.cs文件:
- /**
- * @将xml转为WxPayData对象并返回对象内部的数据
- * @param string 待转换的xml串
- * @return 经转换得到的Dictionary
- * @throws WxPayException
- */
- public SortedDictionary<string, object> FromXml(string xml, string key/*传递秘钥*/)
- {
- if (string.IsNullOrEmpty(xml))
- {
- Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
- throw new WxPayException("将空的xml串转换为WxPayData不合法!");
- }
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.LoadXml(xml);
- XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
- XmlNodeList nodes = xmlNode.ChildNodes;
- foreach (XmlNode xn in nodes)
- {
- XmlElement xe = (XmlElement)xn;
- m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
- }
- try
- {
- //2015-06-29 错误是没有签名
- if (m_values["return_code"].ToString() != "SUCCESS")
- {
- return m_values;
- }
- CheckSign(key);//验证签名,不通过会抛异常 //在把秘钥传入验证签名的方法里头
- }
- catch (WxPayException ex)
- {
- throw new WxPayException(ex.Message);
- }
- return m_values;
- }
- /**
- *
- * 检测签名是否正确
- * 正确返回true,错误抛异常
- */
- public bool CheckSign(string key)
- {
- //如果没有设置签名,则跳过检测
- if (!IsSet("sign"))
- {
- Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
- throw new WxPayException("WxPayData签名存在但不合法!");
- }
- //如果设置了签名但是签名为空,则抛异常
- else if(GetValue("sign") == null || GetValue("sign").ToString() == "")
- {
- Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
- throw new WxPayException("WxPayData签名存在但不合法!");
- }
- //获取接收到的签名
- string return_sign = GetValue("sign").ToString();
- //在本地计算新的签名
- string cal_sign = MakeSign(key);//生成sign直接使用传入的key值,而不是config.cs配置好的默认的key值
- if (cal_sign == return_sign)
- {
- return true;
- }
- Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");
- throw new WxPayException("WxPayData签名验证错误!");
- }
- /**
- * @生成签名,详见签名生成算法
- * @return 签名, sign字段不参加签名
- */
- public string MakeSign(string key)
- {
- //转url格式
- string str = ToUrl();
- //在string后加入API KEY
- //str += "&key=" + WxPayConfig.KEY;//不适用写好的方法
- str += "&key=" + key;
- //MD5加密
- var md5 = MD5.Create();
- var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
- var sb = new StringBuilder();
- foreach (byte b in bs)
- {
- sb.Append(b.ToString("x2"));
- }
- //所有字符转为大写
- return sb.ToString().ToUpper();
- }
最后,补充下上面OrderQuery()说的方案二删除key的方法,已经测试过,有效!!
- //这个是WxPayApi.cs的调整
- string key = inputObj.GetValue("KEY").ToString();
- inputObj.RemoveTkey("KEY");
- //Data.cs文件增加方法RemoveTkey
- /**
- * 根据字段名删除某个字段的值
- * @param key 字段名
- * @return key对应的字段值
- */
- public bool RemoveTkey(string Tkey)
- {
- bool e = m_values.Remove(Tkey);
- return e;
- }
经测试,返回参数如下: