WxPayData.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Security.Cryptography;
  4. using System.Text;
  5. using System.Xml;
  6. using Newtonsoft.Json;
  7. namespace WxPayAPI
  8. {
  9. /// <summary>
  10. /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
  11. /// 在调用接口之前先填充各个字段的值,然后进行接口通信,
  12. /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
  13. /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
  14. /// </summary>
  15. public class WxPayData
  16. {
  17. public WxPayData()
  18. {
  19. }
  20. //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
  21. private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
  22. /**
  23. * 设置某个字段的值
  24. * @param key 字段名
  25. * @param value 字段值
  26. */
  27. public void SetValue(string key, object value)
  28. {
  29. m_values[key] = value;
  30. }
  31. /**
  32. * 根据字段名获取某个字段的值
  33. * @param key 字段名
  34. * @return key对应的字段值
  35. */
  36. public object GetValue(string key)
  37. {
  38. object o = null;
  39. m_values.TryGetValue(key, out o);
  40. return o;
  41. }
  42. /**
  43. * 判断某个字段是否已设置
  44. * @param key 字段名
  45. * @return 若字段key已被设置,则返回true,否则返回false
  46. */
  47. public bool IsSet(string key)
  48. {
  49. object o = null;
  50. m_values.TryGetValue(key, out o);
  51. if (null != o)
  52. return true;
  53. else
  54. return false;
  55. }
  56. /**
  57. * @将Dictionary转成xml
  58. * @return 经转换得到的xml串
  59. * @throws WxPayException
  60. **/
  61. public string ToXml()
  62. {
  63. //数据为空时不能转化为xml格式
  64. if (0 == m_values.Count)
  65. {
  66. //Log.Error(this.GetType().ToString(), "WxPayData数据为空!");
  67. throw new WxPayException("WxPayData数据为空!");
  68. }
  69. string xml = "<xml>";
  70. foreach (KeyValuePair<string, object> pair in m_values)
  71. {
  72. //字段值不能为null,会影响后续流程
  73. if (pair.Value == null)
  74. {
  75. //Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
  76. throw new WxPayException("WxPayData内部含有值为null的字段!");
  77. }
  78. if (pair.Value.GetType() == typeof(int))
  79. {
  80. xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
  81. }
  82. else if (pair.Value.GetType() == typeof(string))
  83. {
  84. xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
  85. }
  86. else//除了string和int类型不能含有其他数据类型
  87. {
  88. //Log.Error(this.GetType().ToString(), "WxPayData字段数据类型错误!");
  89. throw new WxPayException("WxPayData字段数据类型错误!");
  90. }
  91. }
  92. xml += "</xml>";
  93. return xml;
  94. }
  95. /**
  96. * @将xml转为WxPayData对象并返回对象内部的数据
  97. * @param string 待转换的xml串
  98. * @return 经转换得到的Dictionary
  99. * @throws WxPayException
  100. */
  101. public SortedDictionary<string, object> FromXml(string xml)
  102. {
  103. if (string.IsNullOrEmpty(xml))
  104. {
  105. //Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
  106. throw new WxPayException("将空的xml串转换为WxPayData不合法!");
  107. }
  108. XmlDocument xmlDoc = new XmlDocument();
  109. xmlDoc.LoadXml(xml);
  110. XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
  111. XmlNodeList nodes = xmlNode.ChildNodes;
  112. foreach (XmlNode xn in nodes)
  113. {
  114. XmlElement xe = (XmlElement)xn;
  115. m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
  116. }
  117. try
  118. {
  119. //2015-06-29 错误是没有签名
  120. if (m_values["return_code"] != "SUCCESS")
  121. {
  122. return m_values;
  123. }
  124. CheckSign();//验证签名,不通过会抛异常
  125. }
  126. catch (WxPayException ex)
  127. {
  128. throw new WxPayException(ex.Message);
  129. }
  130. return m_values;
  131. }
  132. /**
  133. * @Dictionary格式转化成url参数格式
  134. * @ return url格式串, 该串不包含sign字段值
  135. */
  136. public string ToUrl()
  137. {
  138. string buff = "";
  139. foreach (KeyValuePair<string, object> pair in m_values)
  140. {
  141. if (pair.Value == null)
  142. {
  143. //Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
  144. throw new WxPayException("WxPayData内部含有值为null的字段!");
  145. }
  146. if (pair.Key != "sign" && pair.Value.ToString() != "")
  147. {
  148. buff += pair.Key + "=" + pair.Value + "&";
  149. }
  150. }
  151. buff = buff.Trim('&');
  152. return buff;
  153. }
  154. /**
  155. * @Dictionary格式化成Json
  156. * @return json串数据
  157. */
  158. public string ToJson()
  159. {
  160. string jsonStr = JsonConvert.SerializeObject(m_values);
  161. return jsonStr;
  162. }
  163. /**
  164. * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
  165. */
  166. public string ToPrintStr()
  167. {
  168. string str = "";
  169. foreach (KeyValuePair<string, object> pair in m_values)
  170. {
  171. if (pair.Value == null)
  172. {
  173. //Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
  174. throw new WxPayException("WxPayData内部含有值为null的字段!");
  175. }
  176. str += string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString());
  177. }
  178. //Log.Debug(this.GetType().ToString(), "Print in Web Page : " + str);
  179. return str;
  180. }
  181. /**
  182. * @生成签名,详见签名生成算法
  183. * @return 签名, sign字段不参加签名
  184. */
  185. public string MakeSign()
  186. {
  187. //转url格式
  188. string str = ToUrl();
  189. //在string后加入API KEY
  190. str += "&key=" + WxPayConfig.KEY;
  191. //Log.Info("thiskey", str);
  192. //MD5加密
  193. var md5 = MD5.Create();
  194. var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
  195. var sb = new StringBuilder();
  196. foreach (byte b in bs)
  197. {
  198. sb.Append(b.ToString("x2"));
  199. }
  200. //所有字符转为大写
  201. return sb.ToString().ToUpper();
  202. }
  203. /**
  204. *
  205. * 检测签名是否正确
  206. * 正确返回true,错误抛异常
  207. */
  208. public bool CheckSign()
  209. {
  210. //如果没有设置签名,则跳过检测
  211. if (!IsSet("sign"))
  212. {
  213. //Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
  214. throw new WxPayException("WxPayData签名存在但不合法!");
  215. }
  216. //如果设置了签名但是签名为空,则抛异常
  217. else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
  218. {
  219. //Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
  220. throw new WxPayException("WxPayData签名存在但不合法!");
  221. }
  222. //获取接收到的签名
  223. string return_sign = GetValue("sign").ToString();
  224. //在本地计算新的签名
  225. string cal_sign = MakeSign();
  226. //Log.Info("sign:", return_sign);
  227. //Log.Info("cal_sign:", cal_sign);
  228. if (cal_sign == return_sign)
  229. {
  230. return true;
  231. }
  232. //Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");
  233. throw new WxPayException("WxPayData签名验证错误!");
  234. }
  235. /**
  236. * @获取Dictionary
  237. */
  238. public SortedDictionary<string, object> GetValues()
  239. {
  240. return m_values;
  241. }
  242. }
  243. }