AlipayClient.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. using Essensoft.AspNetCore.Payment.Alipay.Parser;
  2. using Essensoft.AspNetCore.Payment.Alipay.Request;
  3. using Essensoft.AspNetCore.Payment.Alipay.Utility;
  4. using Essensoft.AspNetCore.Payment.Security;
  5. using Microsoft.Extensions.Logging;
  6. using Microsoft.Extensions.Options;
  7. using Newtonsoft.Json;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Security.Cryptography;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. namespace Essensoft.AspNetCore.Payment.Alipay
  14. {
  15. /// <summary>
  16. /// Alipay客户端。
  17. /// </summary>
  18. public class AlipayClient : IAlipayClient
  19. {
  20. private const string APP_ID = "app_id";
  21. private const string FORMAT = "format";
  22. private const string METHOD = "method";
  23. private const string TIMESTAMP = "timestamp";
  24. private const string VERSION = "version";
  25. private const string SIGN_TYPE = "sign_type";
  26. private const string ACCESS_TOKEN = "auth_token";
  27. private const string SIGN = "sign";
  28. private const string TERMINAL_TYPE = "terminal_type";
  29. private const string TERMINAL_INFO = "terminal_info";
  30. private const string PROD_CODE = "prod_code";
  31. private const string NOTIFY_URL = "notify_url";
  32. private const string CHARSET = "charset";
  33. private const string ENCRYPT_TYPE = "encrypt_type";
  34. private const string BIZ_CONTENT = "biz_content";
  35. private const string APP_AUTH_TOKEN = "app_auth_token";
  36. private const string RETURN_URL = "return_url";
  37. private readonly RSAParameters PrivateRSAParameters;
  38. private readonly RSAParameters PublicRSAParameters;
  39. public AlipayOptions Options { get; }
  40. public virtual ILogger Logger { get; set; }
  41. protected internal HttpClientEx Client { get; set; }
  42. #region AlipayClient Constructors
  43. public AlipayClient(
  44. IOptions<AlipayOptions> optionsAccessor,
  45. ILogger<AlipayClient> logger)
  46. {
  47. Options = optionsAccessor.Value;
  48. Logger = logger;
  49. Client = new HttpClientEx();
  50. if (string.IsNullOrEmpty(Options.AppId))
  51. {
  52. throw new ArgumentNullException(nameof(Options.AppId));
  53. }
  54. if (string.IsNullOrEmpty(Options.RsaPrivateKey))
  55. {
  56. throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
  57. }
  58. if (string.IsNullOrEmpty(Options.RsaPublicKey))
  59. {
  60. throw new ArgumentNullException(nameof(Options.RsaPublicKey));
  61. }
  62. PrivateRSAParameters = RSAUtilities.GetRSAParametersFormPrivateKey(Options.RsaPrivateKey);
  63. PublicRSAParameters = RSAUtilities.GetRSAParametersFormPublicKey(Options.RsaPublicKey);
  64. }
  65. public AlipayClient(IOptions<AlipayOptions> optionsAccessor)
  66. : this(optionsAccessor, null)
  67. { }
  68. #endregion
  69. #region IAlipayClient Members
  70. public void SetTimeout(int timeout)
  71. {
  72. Client.Timeout = new TimeSpan(0, 0, 0, timeout);
  73. }
  74. #endregion
  75. #region IAlipayClient Members
  76. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
  77. {
  78. return await ExecuteAsync<T>(request, null);
  79. }
  80. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string accessToken) where T : AlipayResponse
  81. {
  82. return await ExecuteAsync<T>(request, accessToken, null);
  83. }
  84. #endregion
  85. #region IAlipayClient Members
  86. public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
  87. {
  88. return await PageExecuteAsync<T>(request, null, "POST");
  89. }
  90. #endregion
  91. #region IAlipayClient Members
  92. public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string accessToken, string reqMethod) where T : AlipayResponse
  93. {
  94. string apiVersion = null;
  95. if (!string.IsNullOrEmpty(request.GetApiVersion()))
  96. {
  97. apiVersion = request.GetApiVersion();
  98. }
  99. else
  100. {
  101. apiVersion = Options.Version;
  102. }
  103. var txtParams = new AlipayDictionary(request.GetParameters())
  104. {
  105. // 序列化BizModel
  106. { BIZ_CONTENT, Serialize(request.GetBizModel()) },
  107. // 添加协议级请求参数
  108. { METHOD, request.GetApiName() },
  109. { VERSION, apiVersion },
  110. { APP_ID, Options.AppId },
  111. { FORMAT, Options.Format },
  112. { TIMESTAMP, DateTime.Now },
  113. { ACCESS_TOKEN, accessToken },
  114. { SIGN_TYPE, Options.SignType },
  115. { TERMINAL_TYPE, request.GetTerminalType() },
  116. { TERMINAL_INFO, request.GetTerminalInfo() },
  117. { PROD_CODE, request.GetProdCode() },
  118. { NOTIFY_URL, request.GetNotifyUrl() },
  119. { CHARSET, Options.Charset },
  120. { RETURN_URL, request.GetReturnUrl() }
  121. };
  122. // 添加签名参数
  123. var signContent = AlipaySignature.GetSignContent(txtParams);
  124. txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType));
  125. // 是否需要上传文件
  126. var body = string.Empty;
  127. if (request is IAlipayUploadRequest<T> uRequest)
  128. {
  129. var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
  130. body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams);
  131. }
  132. else
  133. {
  134. if (reqMethod.ToUpper() == "GET")
  135. {
  136. //拼接get请求的url
  137. var tmpUrl = Options.ServerUrl;
  138. if (txtParams != null && txtParams.Count > 0)
  139. {
  140. if (tmpUrl.Contains("?"))
  141. {
  142. tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(txtParams);
  143. }
  144. else
  145. {
  146. tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(txtParams);
  147. }
  148. }
  149. body = tmpUrl;
  150. Logger?.LogTrace(0, "Request Url:{body}", body);
  151. }
  152. else
  153. {
  154. //输出post表单
  155. body = BuildHtmlRequest(txtParams, reqMethod);
  156. Logger?.LogTrace(0, "Request Html:{body}", body);
  157. }
  158. }
  159. T rsp = null;
  160. IAlipayParser<T> parser = null;
  161. if ("xml".Equals(Options.Format))
  162. {
  163. parser = new AlipayXmlParser<T>();
  164. rsp = parser.Parse(body);
  165. }
  166. else
  167. {
  168. parser = new AlipayJsonParser<T>();
  169. rsp = parser.Parse(body);
  170. }
  171. return rsp;
  172. }
  173. #endregion
  174. #region IAlipayClient Members
  175. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken) where T : AlipayResponse
  176. {
  177. var apiVersion = string.Empty;
  178. if (!string.IsNullOrEmpty(request.GetApiVersion()))
  179. {
  180. apiVersion = request.GetApiVersion();
  181. }
  182. else
  183. {
  184. apiVersion = Options.Version;
  185. }
  186. // 添加协议级请求参数
  187. var txtParams = new AlipayDictionary(request.GetParameters())
  188. {
  189. // 序列化BizModel
  190. { BIZ_CONTENT, Serialize(request.GetBizModel()) },
  191. // 添加协议级请求参数
  192. { METHOD, request.GetApiName() },
  193. { VERSION, apiVersion },
  194. { APP_ID, Options.AppId },
  195. { FORMAT, Options.Format },
  196. { TIMESTAMP, DateTime.Now },
  197. { ACCESS_TOKEN, accessToken },
  198. { SIGN_TYPE, Options.SignType },
  199. { TERMINAL_TYPE, request.GetTerminalType() },
  200. { TERMINAL_INFO, request.GetTerminalInfo() },
  201. { PROD_CODE, request.GetProdCode() },
  202. { CHARSET, Options.Charset }
  203. };
  204. if (!string.IsNullOrEmpty(request.GetNotifyUrl()))
  205. {
  206. txtParams.Add(NOTIFY_URL, request.GetNotifyUrl());
  207. }
  208. if (!string.IsNullOrEmpty(appAuthToken))
  209. {
  210. txtParams.Add(APP_AUTH_TOKEN, appAuthToken);
  211. }
  212. if (request.GetNeedEncrypt())
  213. {
  214. if (string.IsNullOrEmpty(txtParams[BIZ_CONTENT]))
  215. {
  216. throw new Exception("api request Fail ! The reason: encrypt request is not supported!");
  217. }
  218. if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType))
  219. {
  220. throw new Exception("encryptType or encryptKey must not null!");
  221. }
  222. if (!"AES".Equals(Options.EncyptType))
  223. {
  224. throw new Exception("api only support Aes!");
  225. }
  226. var encryptContent = AES.Encrypt(txtParams[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
  227. txtParams.Remove(BIZ_CONTENT);
  228. txtParams.Add(BIZ_CONTENT, encryptContent);
  229. txtParams.Add(ENCRYPT_TYPE, Options.EncyptType);
  230. }
  231. // 添加签名参数
  232. var signContent = AlipaySignature.GetSignContent(txtParams);
  233. txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType));
  234. var query = HttpClientEx.BuildQuery(txtParams);
  235. Logger?.LogTrace(0, "Request:{query}", query);
  236. // 是否需要上传文件
  237. var body = string.Empty;
  238. if (request is IAlipayUploadRequest<T> uRequest)
  239. {
  240. var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
  241. body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams);
  242. }
  243. else
  244. {
  245. body = await Client.DoPostAsync(Options.ServerUrl, query);
  246. }
  247. Logger?.LogTrace(1, "Response:{body}", body);
  248. T rsp = null;
  249. IAlipayParser<T> parser = null;
  250. if ("xml".Equals(Options.Format))
  251. {
  252. parser = new AlipayXmlParser<T>();
  253. rsp = parser.Parse(body);
  254. }
  255. else
  256. {
  257. parser = new AlipayJsonParser<T>();
  258. rsp = parser.Parse(body);
  259. }
  260. var item = ParseRespItem(request, body, parser, Options.EncyptKey, Options.EncyptType);
  261. rsp = parser.Parse(item.realContent);
  262. CheckResponseSign(request, item.respContent, rsp.IsError, parser, PublicRSAParameters, Options.SignType);
  263. return rsp;
  264. }
  265. private ResponseParseItem ParseRespItem<T>(IAlipayRequest<T> request, string respBody, IAlipayParser<T> parser, string encryptKey, string encryptType) where T : AlipayResponse
  266. {
  267. string realContent = null;
  268. if (request.GetNeedEncrypt())
  269. {
  270. realContent = parser.EncryptSourceData(request, respBody, encryptType, encryptKey);
  271. }
  272. else
  273. {
  274. realContent = respBody;
  275. }
  276. var item = new ResponseParseItem()
  277. {
  278. realContent = realContent,
  279. respContent = respBody
  280. };
  281. return item;
  282. }
  283. private void CheckResponseSign<T>(IAlipayRequest<T> request, string responseBody, bool isError, IAlipayParser<T> parser, RSAParameters parameters, string signType) where T : AlipayResponse
  284. {
  285. var signItem = parser.GetSignItem(request, responseBody);
  286. if (signItem == null)
  287. {
  288. throw new Exception("sign check fail: Body is Empty!");
  289. }
  290. if (!isError || (isError && !string.IsNullOrEmpty(signItem.Sign)))
  291. {
  292. var rsaCheckContent = AlipaySignature.RSACheckContent(signItem.SignSourceDate, signItem.Sign, parameters, signType);
  293. if (!rsaCheckContent)
  294. {
  295. if (!string.IsNullOrEmpty(signItem.SignSourceDate) && signItem.SignSourceDate.Contains("\\/"))
  296. {
  297. var srouceData = signItem.SignSourceDate.Replace("\\/", "/");
  298. var jsonCheck = AlipaySignature.RSACheckContent(srouceData, signItem.Sign, parameters, signType);
  299. if (!jsonCheck)
  300. {
  301. throw new Exception("sign check fail: check Sign and Data Fail JSON also");
  302. }
  303. }
  304. else
  305. {
  306. throw new Exception("sign check fail: check Sign and Data Fail!");
  307. }
  308. }
  309. }
  310. }
  311. #endregion
  312. #region IAlipayClient Members
  313. public string BuildHtmlRequest(IDictionary<string, string> sParaTemp, string strMethod)
  314. {
  315. //待请求参数数组
  316. var dicPara = new Dictionary<string, string>(sParaTemp);
  317. var sbHtml = new StringBuilder();
  318. sbHtml.Append("<form id='submit' name='submit' action='" + Options.ServerUrl + "?charset=" + Options.Charset +
  319. "' method='" + strMethod + "' style='display:none;'>");
  320. foreach (var temp in dicPara)
  321. {
  322. sbHtml.Append("<input name='" + temp.Key + "' value='" + temp.Value + "'/>");
  323. }
  324. sbHtml.Append("<input type='submit' style='display:none;'></form>");
  325. //表单实现自动提交
  326. sbHtml.Append("<script>document.forms['submit'].submit();</script>");
  327. return sbHtml.ToString();
  328. }
  329. #endregion
  330. #region SDK Execute
  331. public Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
  332. {
  333. // 构造请求参数
  334. var requestParams = BuildRequestParams(request, null, null);
  335. // 字典排序
  336. var sortedParams = new SortedDictionary<string, string>(requestParams);
  337. var sortedAlipayDic = new AlipayDictionary(sortedParams);
  338. // 参数签名
  339. var signContent = AlipaySignature.GetSignContent(sortedAlipayDic);
  340. var signResult = AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType);
  341. // 添加签名结果参数
  342. sortedAlipayDic.Add(SIGN, signResult);
  343. // 参数拼接
  344. var signedResult = HttpClientEx.BuildQuery(sortedAlipayDic);
  345. // 构造结果
  346. var rsp = Activator.CreateInstance<T>();
  347. rsp.Body = signedResult;
  348. return Task.FromResult(rsp);
  349. }
  350. #endregion
  351. #region Common Method
  352. private AlipayDictionary BuildRequestParams<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken) where T : AlipayResponse
  353. {
  354. // 默认参数
  355. var result = new AlipayDictionary(request.GetParameters())
  356. {
  357. // 序列化BizModel
  358. { BIZ_CONTENT, Serialize(request.GetBizModel()) },
  359. // 添加协议级请求参数,为空的参数后面会自动过滤,这里不做处理。
  360. { METHOD, request.GetApiName() },
  361. { VERSION, string.IsNullOrEmpty(request.GetApiVersion()) ? Options.Version : request.GetApiVersion() },
  362. { APP_ID, Options.AppId },
  363. { FORMAT, Options.Format },
  364. { TIMESTAMP, DateTime.Now },
  365. { ACCESS_TOKEN, accessToken },
  366. { SIGN_TYPE, Options.SignType },
  367. { TERMINAL_TYPE, request.GetTerminalType() },
  368. { TERMINAL_INFO, request.GetTerminalInfo() },
  369. { PROD_CODE, request.GetProdCode() },
  370. { NOTIFY_URL, request.GetNotifyUrl() },
  371. { CHARSET, Options.Charset },
  372. { RETURN_URL, request.GetReturnUrl() },
  373. { APP_AUTH_TOKEN, appAuthToken }
  374. };
  375. if (request.GetNeedEncrypt())
  376. {
  377. if (string.IsNullOrEmpty(result[BIZ_CONTENT]))
  378. {
  379. throw new Exception("api request Fail ! The reason: encrypt request is not supported!");
  380. }
  381. if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType))
  382. {
  383. throw new Exception("encryptType or encryptKey must not null!");
  384. }
  385. if (!"AES".Equals(Options.EncyptType))
  386. {
  387. throw new Exception("api only support Aes!");
  388. }
  389. var encryptContent = AES.Encrypt(result[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
  390. result.Remove(BIZ_CONTENT);
  391. result.Add(BIZ_CONTENT, encryptContent);
  392. result.Add(ENCRYPT_TYPE, Options.EncyptType);
  393. }
  394. return result;
  395. }
  396. #endregion
  397. #region Model Serialize
  398. private string Serialize(AlipayObject bizModel)
  399. {
  400. return JsonConvert.SerializeObject(bizModel, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
  401. }
  402. #endregion
  403. }
  404. }