WxPayData.cs 10 KB

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