Data.cs 8.8 KB

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