AlipayClient.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net.Http;
  4. using System.Text;
  5. using System.Text.Encodings.Web;
  6. using System.Text.Json;
  7. using System.Threading.Tasks;
  8. using Essensoft.AspNetCore.Payment.Alipay.Parser;
  9. using Essensoft.AspNetCore.Payment.Alipay.Request;
  10. using Essensoft.AspNetCore.Payment.Alipay.Utility;
  11. namespace Essensoft.AspNetCore.Payment.Alipay
  12. {
  13. /// <summary>
  14. /// Alipay 客户端。
  15. /// </summary>
  16. public class AlipayClient : IAlipayClient
  17. {
  18. private readonly IHttpClientFactory _httpClientFactory;
  19. private readonly AlipayCertificateManager _certificateManager;
  20. #region AlipayClient Constructors
  21. public AlipayClient(IHttpClientFactory httpClientFactory, AlipayCertificateManager certificateManager)
  22. {
  23. _httpClientFactory = httpClientFactory;
  24. _certificateManager = certificateManager;
  25. }
  26. #endregion
  27. #region IAlipayClient Members
  28. public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options) where T : AlipayResponse
  29. {
  30. return await PageExecuteAsync(request, options, null, "POST");
  31. }
  32. public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options, string accessToken, string reqMethod) where T : AlipayResponse
  33. {
  34. if (options == null)
  35. {
  36. throw new ArgumentNullException(nameof(options));
  37. }
  38. if (string.IsNullOrEmpty(options.AppId))
  39. {
  40. throw new ArgumentNullException(nameof(options.AppId));
  41. }
  42. if (string.IsNullOrEmpty(options.SignType))
  43. {
  44. throw new ArgumentNullException(nameof(options.SignType));
  45. }
  46. if (string.IsNullOrEmpty(options.AppPrivateKey))
  47. {
  48. throw new ArgumentNullException(nameof(options.AppPrivateKey));
  49. }
  50. if (string.IsNullOrEmpty(options.ServerUrl))
  51. {
  52. throw new ArgumentNullException(nameof(options.ServerUrl));
  53. }
  54. var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
  55. // 添加协议级请求参数
  56. var txtParams = new AlipayDictionary(request.GetParameters())
  57. {
  58. { AlipayConstants.METHOD, request.GetApiName() },
  59. { AlipayConstants.VERSION, apiVersion },
  60. { AlipayConstants.APP_ID, options.AppId },
  61. { AlipayConstants.FORMAT, options.Format },
  62. { AlipayConstants.TIMESTAMP, DateTime.Now },
  63. { AlipayConstants.ACCESS_TOKEN, accessToken },
  64. { AlipayConstants.SIGN_TYPE, options.SignType },
  65. { AlipayConstants.TERMINAL_TYPE, request.GetTerminalType() },
  66. { AlipayConstants.TERMINAL_INFO, request.GetTerminalInfo() },
  67. { AlipayConstants.PROD_CODE, request.GetProdCode() },
  68. { AlipayConstants.NOTIFY_URL, request.GetNotifyUrl() },
  69. { AlipayConstants.CHARSET, options.Charset },
  70. { AlipayConstants.RETURN_URL, request.GetReturnUrl() },
  71. { AlipayConstants.ALIPAY_ROOT_CERT_SN, options.RootCertSN },
  72. { AlipayConstants.APP_CERT_SN, options.AppCertSN }
  73. };
  74. // 序列化BizModel
  75. txtParams = SerializeBizModel(txtParams, request);
  76. // 添加签名参数
  77. var signContent = AlipaySignature.GetSignContent(txtParams);
  78. txtParams.Add(AlipayConstants.SIGN, AlipaySignature.RSASignContent(signContent, options.AppPrivateKey, options.Charset, options.SignType));
  79. string body;
  80. // 是否需要上传文件
  81. if (request is IAlipayUploadRequest<T> uploadRequest)
  82. {
  83. var fileParams = AlipayUtility.CleanupDictionary(uploadRequest.GetFileParameters());
  84. var client = _httpClientFactory.CreateClient(nameof(AlipayClient));
  85. body = await client.PostAsync(options.ServerUrl, txtParams, fileParams);
  86. }
  87. else
  88. {
  89. if (reqMethod.ToUpperInvariant() == "GET")
  90. {
  91. var url = options.ServerUrl;
  92. if (txtParams != null && txtParams.Count > 0)
  93. {
  94. if (url.Contains("?"))
  95. {
  96. url += "&" + AlipayUtility.BuildQuery(txtParams);
  97. }
  98. else
  99. {
  100. url += "?" + AlipayUtility.BuildQuery(txtParams);
  101. }
  102. }
  103. body = url;
  104. }
  105. else
  106. {
  107. body = BuildHtmlRequest(txtParams, options.ServerUrl, options.Charset, reqMethod);
  108. }
  109. }
  110. var parser = new AlipayJsonParser<T>();
  111. var rsp = parser.Parse(body);
  112. return rsp;
  113. }
  114. #endregion
  115. #region IAlipayClient Members
  116. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options) where T : AlipayResponse
  117. {
  118. return await ExecuteAsync(request, options, null);
  119. }
  120. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options, string accessToken) where T : AlipayResponse
  121. {
  122. return await ExecuteAsync(request, options, accessToken, null);
  123. }
  124. public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options, string accessToken, string appAuthToken) where T : AlipayResponse
  125. {
  126. if (options == null)
  127. {
  128. throw new ArgumentNullException(nameof(options));
  129. }
  130. if (string.IsNullOrEmpty(options.AppId))
  131. {
  132. throw new ArgumentNullException(nameof(options.AppId));
  133. }
  134. if (string.IsNullOrEmpty(options.SignType))
  135. {
  136. throw new ArgumentNullException(nameof(options.SignType));
  137. }
  138. if (string.IsNullOrEmpty(options.AlipayPublicKey))
  139. {
  140. throw new ArgumentNullException(nameof(options.AlipayPublicKey));
  141. }
  142. if (string.IsNullOrEmpty(options.AppPrivateKey))
  143. {
  144. throw new ArgumentNullException(nameof(options.AppPrivateKey));
  145. }
  146. if (string.IsNullOrEmpty(options.ServerUrl))
  147. {
  148. throw new ArgumentNullException(nameof(options.ServerUrl));
  149. }
  150. var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
  151. // 添加协议级请求参数
  152. var txtParams = new AlipayDictionary(request.GetParameters())
  153. {
  154. { AlipayConstants.METHOD, request.GetApiName() },
  155. { AlipayConstants.VERSION, apiVersion },
  156. { AlipayConstants.APP_ID, options.AppId },
  157. { AlipayConstants.FORMAT, options.Format },
  158. { AlipayConstants.TIMESTAMP, DateTime.Now },
  159. { AlipayConstants.ACCESS_TOKEN, accessToken },
  160. { AlipayConstants.SIGN_TYPE, options.SignType },
  161. { AlipayConstants.TERMINAL_TYPE, request.GetTerminalType() },
  162. { AlipayConstants.TERMINAL_INFO, request.GetTerminalInfo() },
  163. { AlipayConstants.PROD_CODE, request.GetProdCode() },
  164. { AlipayConstants.CHARSET, options.Charset }
  165. };
  166. // 序列化BizModel
  167. txtParams = SerializeBizModel(txtParams, request);
  168. if (!string.IsNullOrEmpty(request.GetNotifyUrl()))
  169. {
  170. txtParams.Add(AlipayConstants.NOTIFY_URL, request.GetNotifyUrl());
  171. }
  172. if (!string.IsNullOrEmpty(appAuthToken))
  173. {
  174. txtParams.Add(AlipayConstants.APP_AUTH_TOKEN, appAuthToken);
  175. }
  176. if (request.GetNeedEncrypt())
  177. {
  178. if (string.IsNullOrEmpty(txtParams[AlipayConstants.BIZ_CONTENT]))
  179. {
  180. throw new AlipayException("api request Fail ! The reason: encrypt request is not supported!");
  181. }
  182. if (string.IsNullOrEmpty(options.EncyptKey) || string.IsNullOrEmpty(options.EncyptType))
  183. {
  184. throw new AlipayException("encryptType or encryptKey must not null!");
  185. }
  186. if (!"AES".Equals(options.EncyptType))
  187. {
  188. throw new AlipayException("api only support Aes!");
  189. }
  190. var encryptContent = AlipaySignature.AESEncrypt(txtParams[AlipayConstants.BIZ_CONTENT], options.EncyptKey);
  191. txtParams.Remove(AlipayConstants.BIZ_CONTENT);
  192. txtParams.Add(AlipayConstants.BIZ_CONTENT, encryptContent);
  193. txtParams.Add(AlipayConstants.ENCRYPT_TYPE, options.EncyptType);
  194. }
  195. // 添加签名参数
  196. var signContent = AlipaySignature.GetSignContent(txtParams);
  197. txtParams.Add(AlipayConstants.SIGN, AlipaySignature.RSASignContent(signContent, options.AppPrivateKey, options.Charset, options.SignType));
  198. string body;
  199. var client = _httpClientFactory.CreateClient(nameof(AlipayClient));
  200. // 是否需要上传文件
  201. if (request is IAlipayUploadRequest<T> uRequest)
  202. {
  203. var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
  204. body = await client.PostAsync(options.ServerUrl, txtParams, fileParams);
  205. }
  206. else
  207. {
  208. body = await client.PostAsync(options.ServerUrl, txtParams);
  209. }
  210. var parser = new AlipayJsonParser<T>();
  211. var item = ParseRespItem(request, body, parser, options.EncyptKey, options.EncyptType);
  212. var rsp = parser.Parse(item.RealContent);
  213. CheckResponseSign(request, item.RespContent, rsp.IsError, parser, options);
  214. return rsp;
  215. }
  216. private void CheckResponseSign<T>(IAlipayRequest<T> request, string responseBody, bool isError, IAlipayParser<T> parser, AlipayOptions options) where T : AlipayResponse
  217. {
  218. var signItem = parser.GetSignItem(request, responseBody);
  219. if (signItem == null)
  220. {
  221. throw new AlipayException("sign check fail: Body is Empty!");
  222. }
  223. if (!isError || isError && !string.IsNullOrEmpty(signItem.Sign))
  224. {
  225. var rsaCheckContent = AlipaySignature.RSACheckContent(signItem.SignSourceDate, signItem.Sign, options.AlipayPublicKey, options.Charset, options.SignType);
  226. if (!rsaCheckContent)
  227. {
  228. if (!string.IsNullOrEmpty(signItem.SignSourceDate) && signItem.SignSourceDate.Contains("\\/"))
  229. {
  230. var srouceData = signItem.SignSourceDate.Replace("\\/", "/");
  231. var jsonCheck = AlipaySignature.RSACheckContent(srouceData, signItem.Sign, options.AlipayPublicKey, options.Charset, options.SignType);
  232. if (!jsonCheck)
  233. {
  234. throw new AlipayException("sign check fail: check Sign and Data Fail JSON also");
  235. }
  236. }
  237. else
  238. {
  239. throw new AlipayException("sign check fail: check Sign and Data Fail!");
  240. }
  241. }
  242. }
  243. }
  244. #endregion
  245. #region IAlipayClient Members
  246. public async Task<T> CertificateExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options) where T : AlipayResponse
  247. {
  248. return await CertificateExecuteAsync(request, options, null);
  249. }
  250. public async Task<T> CertificateExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options, string accessToken) where T : AlipayResponse
  251. {
  252. return await CertificateExecuteAsync(request, options, accessToken, null);
  253. }
  254. public async Task<T> CertificateExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options, string accessToken, string appAuthToken) where T : AlipayResponse
  255. {
  256. if (options == null)
  257. {
  258. throw new ArgumentNullException(nameof(options));
  259. }
  260. if (string.IsNullOrEmpty(options.AppId))
  261. {
  262. throw new ArgumentNullException(nameof(options.AppId));
  263. }
  264. if (string.IsNullOrEmpty(options.SignType))
  265. {
  266. throw new ArgumentNullException(nameof(options.SignType));
  267. }
  268. if (string.IsNullOrEmpty(options.AppPrivateKey))
  269. {
  270. throw new ArgumentNullException(nameof(options.AppPrivateKey));
  271. }
  272. if (string.IsNullOrEmpty(options.AppCert))
  273. {
  274. throw new ArgumentNullException(nameof(options.AppCert));
  275. }
  276. if (string.IsNullOrEmpty(options.AlipayPublicCert))
  277. {
  278. throw new ArgumentNullException(nameof(options.AlipayPublicCert));
  279. }
  280. if (string.IsNullOrEmpty(options.RootCert))
  281. {
  282. throw new ArgumentNullException(nameof(options.RootCert));
  283. }
  284. if (string.IsNullOrEmpty(options.ServerUrl))
  285. {
  286. throw new ArgumentNullException(nameof(options.ServerUrl));
  287. }
  288. var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
  289. // 添加协议级请求参数
  290. var txtParams = new AlipayDictionary(request.GetParameters())
  291. {
  292. { AlipayConstants.METHOD, request.GetApiName() },
  293. { AlipayConstants.VERSION, apiVersion },
  294. { AlipayConstants.APP_ID, options.AppId },
  295. { AlipayConstants.FORMAT, options.Format },
  296. { AlipayConstants.TIMESTAMP, DateTime.Now },
  297. { AlipayConstants.ACCESS_TOKEN, accessToken },
  298. { AlipayConstants.SIGN_TYPE, options.SignType },
  299. { AlipayConstants.TERMINAL_TYPE, request.GetTerminalType() },
  300. { AlipayConstants.TERMINAL_INFO, request.GetTerminalInfo() },
  301. { AlipayConstants.PROD_CODE, request.GetProdCode() },
  302. { AlipayConstants.CHARSET, options.Charset },
  303. { AlipayConstants.APP_CERT_SN, options.AppCertSN },
  304. { AlipayConstants.ALIPAY_ROOT_CERT_SN, options.RootCertSN }
  305. };
  306. // 序列化BizModel
  307. txtParams = SerializeBizModel(txtParams, request);
  308. if (!string.IsNullOrEmpty(request.GetNotifyUrl()))
  309. {
  310. txtParams.Add(AlipayConstants.NOTIFY_URL, request.GetNotifyUrl());
  311. }
  312. if (!string.IsNullOrEmpty(appAuthToken))
  313. {
  314. txtParams.Add(AlipayConstants.APP_AUTH_TOKEN, appAuthToken);
  315. }
  316. if (request.GetNeedEncrypt())
  317. {
  318. if (string.IsNullOrEmpty(txtParams[AlipayConstants.BIZ_CONTENT]))
  319. {
  320. throw new AlipayException("api request Fail ! The reason: encrypt request is not supported!");
  321. }
  322. if (string.IsNullOrEmpty(options.EncyptKey) || string.IsNullOrEmpty(options.EncyptType))
  323. {
  324. throw new AlipayException("encryptType or encryptKey must not null!");
  325. }
  326. if (!"AES".Equals(options.EncyptType))
  327. {
  328. throw new AlipayException("api only support Aes!");
  329. }
  330. var encryptContent = AlipaySignature.AESEncrypt(txtParams[AlipayConstants.BIZ_CONTENT], options.EncyptKey);
  331. txtParams.Remove(AlipayConstants.BIZ_CONTENT);
  332. txtParams.Add(AlipayConstants.BIZ_CONTENT, encryptContent);
  333. txtParams.Add(AlipayConstants.ENCRYPT_TYPE, options.EncyptType);
  334. }
  335. // 添加签名参数
  336. var signContent = AlipaySignature.GetSignContent(txtParams);
  337. txtParams.Add(AlipayConstants.SIGN, AlipaySignature.RSASignContent(signContent, options.AppPrivateKey, options.Charset, options.SignType));
  338. string body;
  339. var client = _httpClientFactory.CreateClient(nameof(AlipayClient));
  340. // 是否需要上传文件
  341. if (request is IAlipayUploadRequest<T> uRequest)
  342. {
  343. var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
  344. body = await client.PostAsync(options.ServerUrl, txtParams, fileParams);
  345. }
  346. else
  347. {
  348. body = await client.PostAsync(options.ServerUrl, txtParams);
  349. }
  350. var parser = new AlipayJsonParser<T>();
  351. var item = ParseRespItem(request, body, parser, options.EncyptKey, options.EncyptType);
  352. var rsp = parser.Parse(item.RealContent);
  353. await CheckResponseCertSignAsync(request, item.RespContent, rsp.IsError, parser, options);
  354. return rsp;
  355. }
  356. private async Task CheckResponseCertSignAsync<T>(IAlipayRequest<T> request, string responseBody, bool isError, IAlipayParser<T> parser, AlipayOptions options) where T : AlipayResponse
  357. {
  358. if (request is AlipayOpenAppAlipaycertDownloadRequest)
  359. {
  360. return;
  361. }
  362. var certItem = parser.GetCertItem(request, responseBody);
  363. if (certItem == null)
  364. {
  365. throw new AlipayException("sign check fail: Body is Empty!");
  366. }
  367. if (!isError || isError && !string.IsNullOrEmpty(certItem.Sign))
  368. {
  369. var currentAlipayPublicKey = await LoadAlipayPublicKeyAsync(certItem, options);
  370. var rsaCheckContent = AlipaySignature.RSACheckContent(certItem.SignSourceDate, certItem.Sign, currentAlipayPublicKey, options.Charset, options.SignType);
  371. if (!rsaCheckContent)
  372. {
  373. if (!string.IsNullOrEmpty(certItem.SignSourceDate) && certItem.SignSourceDate.Contains("\\/"))
  374. {
  375. var srouceData = certItem.SignSourceDate.Replace("\\/", "/");
  376. var jsonCheck = AlipaySignature.RSACheckContent(srouceData, certItem.Sign, currentAlipayPublicKey, options.Charset, options.SignType);
  377. if (!jsonCheck)
  378. {
  379. throw new AlipayException("sign check fail: check Sign and Data Fail JSON also");
  380. }
  381. }
  382. else
  383. {
  384. throw new AlipayException("sign check fail: check Sign and Data Fail!");
  385. }
  386. }
  387. }
  388. }
  389. private async Task<string> LoadAlipayPublicKeyAsync(CertItem certItem, AlipayOptions options)
  390. {
  391. //为空时添加默认支付宝公钥证书密钥
  392. if (_certificateManager.IsEmpty)
  393. {
  394. _certificateManager.TryAdd(options.AlipayPublicCertSN, options.AlipayPublicCertKey);
  395. }
  396. //如果响应的支付宝公钥证书序号已经缓存过,则直接使用缓存的公钥
  397. if (_certificateManager.TryGet(certItem.CertSN, out var publicKey))
  398. {
  399. return publicKey;
  400. }
  401. //否则重新下载新的支付宝公钥证书并更新缓存
  402. var request = new AlipayOpenAppAlipaycertDownloadRequest
  403. {
  404. BizContent = "{\"alipay_cert_sn\":\"" + certItem.CertSN + "\"}"
  405. };
  406. var response = await CertificateExecuteAsync(request, options);
  407. if (response.IsError)
  408. {
  409. throw new AlipayException("支付宝公钥证书校验失败,请确认是否为支付宝签发的有效公钥证书");
  410. }
  411. if (!AntCertificationUtil.IsTrusted(response.AlipayCertContent, options.RootCert))
  412. {
  413. throw new AlipayException("支付宝公钥证书校验失败,请确认是否为支付宝签发的有效公钥证书");
  414. }
  415. var alipayCert = AntCertificationUtil.ParseCert(response.AlipayCertContent);
  416. var alipayCertSN = AntCertificationUtil.GetCertSN(alipayCert);
  417. var newAlipayPublicKey = AntCertificationUtil.ExtractPemPublicKeyFromCert(alipayCert);
  418. _certificateManager.TryAdd(alipayCertSN, newAlipayPublicKey);
  419. return newAlipayPublicKey;
  420. }
  421. #endregion
  422. #region Common Method
  423. private ResponseParseItem ParseRespItem<T>(IAlipayRequest<T> request, string respBody, IAlipayParser<T> parser, string encryptKey, string encryptType) where T : AlipayResponse
  424. {
  425. string realContent;
  426. if (request.GetNeedEncrypt())
  427. {
  428. realContent = parser.EncryptSourceData(request, respBody, encryptType, encryptKey);
  429. }
  430. else
  431. {
  432. realContent = respBody;
  433. }
  434. return new ResponseParseItem
  435. {
  436. RealContent = realContent,
  437. RespContent = respBody
  438. };
  439. }
  440. private string BuildHtmlRequest(IDictionary<string, string> dictionary, string serverUrl, string charset, string strMethod)
  441. {
  442. var sb = new StringBuilder();
  443. sb.Append($"<form id='submit' name='submit' action='{serverUrl}?charset={charset}' method='{strMethod}' style='display:none;'>");
  444. foreach (var iter in dictionary)
  445. {
  446. sb.Append("<input name='" + iter.Key + "' value='" + iter.Value + "'/>");
  447. }
  448. sb.Append("<input type='submit' style='display:none;'></form>");
  449. sb.Append("<script>document.forms['submit'].submit();</script>");
  450. return sb.ToString();
  451. }
  452. private AlipayDictionary BuildRequestParams<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken, AlipayOptions options) where T : AlipayResponse
  453. {
  454. var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
  455. // 添加协议级请求参数
  456. var result = new AlipayDictionary(request.GetParameters())
  457. {
  458. { AlipayConstants.METHOD, request.GetApiName() },
  459. { AlipayConstants.VERSION, apiVersion },
  460. { AlipayConstants.APP_ID, options.AppId },
  461. { AlipayConstants.FORMAT, options.Format },
  462. { AlipayConstants.TIMESTAMP, DateTime.Now },
  463. { AlipayConstants.ACCESS_TOKEN, accessToken },
  464. { AlipayConstants.SIGN_TYPE, options.SignType },
  465. { AlipayConstants.TERMINAL_TYPE, request.GetTerminalType() },
  466. { AlipayConstants.TERMINAL_INFO, request.GetTerminalInfo() },
  467. { AlipayConstants.PROD_CODE, request.GetProdCode() },
  468. { AlipayConstants.NOTIFY_URL, request.GetNotifyUrl() },
  469. { AlipayConstants.CHARSET, options.Charset },
  470. { AlipayConstants.RETURN_URL, request.GetReturnUrl() },
  471. { AlipayConstants.APP_AUTH_TOKEN, appAuthToken },
  472. { AlipayConstants.ALIPAY_ROOT_CERT_SN, options.RootCertSN },
  473. { AlipayConstants.APP_CERT_SN, options.AppCertSN }
  474. };
  475. // 序列化BizModel
  476. result = SerializeBizModel(result, request);
  477. if (request.GetNeedEncrypt())
  478. {
  479. if (string.IsNullOrEmpty(result[AlipayConstants.BIZ_CONTENT]))
  480. {
  481. throw new AlipayException("api request Fail ! The reason: encrypt request is not supported!");
  482. }
  483. if (string.IsNullOrEmpty(options.EncyptKey) || string.IsNullOrEmpty(options.EncyptType))
  484. {
  485. throw new AlipayException("encryptType or encryptKey must not null!");
  486. }
  487. if (!"AES".Equals(options.EncyptType))
  488. {
  489. throw new AlipayException("api only support Aes!");
  490. }
  491. var encryptContent = AlipaySignature.AESEncrypt(result[AlipayConstants.BIZ_CONTENT], options.EncyptKey);
  492. result.Remove(AlipayConstants.BIZ_CONTENT);
  493. result.Add(AlipayConstants.BIZ_CONTENT, encryptContent);
  494. result.Add(AlipayConstants.ENCRYPT_TYPE, options.EncyptType);
  495. }
  496. return result;
  497. }
  498. #endregion
  499. #region SDK Execute
  500. public Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request, AlipayOptions options) where T : AlipayResponse
  501. {
  502. if (options == null)
  503. {
  504. throw new ArgumentNullException(nameof(options));
  505. }
  506. if (string.IsNullOrEmpty(options.AppId))
  507. {
  508. throw new ArgumentNullException(nameof(options.AppId));
  509. }
  510. if (string.IsNullOrEmpty(options.SignType))
  511. {
  512. throw new ArgumentNullException(nameof(options.SignType));
  513. }
  514. if (string.IsNullOrEmpty(options.AppPrivateKey))
  515. {
  516. throw new ArgumentNullException(nameof(options.AppPrivateKey));
  517. }
  518. if (string.IsNullOrEmpty(options.ServerUrl))
  519. {
  520. throw new ArgumentNullException(nameof(options.ServerUrl));
  521. }
  522. // 构造请求参数
  523. var requestParams = BuildRequestParams(request, null, null, options);
  524. // 字典排序
  525. var sortedParams = new SortedDictionary<string, string>(requestParams);
  526. var sortedDic = new AlipayDictionary(sortedParams);
  527. // 参数签名
  528. var signContent = AlipaySignature.GetSignContent(sortedDic);
  529. var signResult = AlipaySignature.RSASignContent(signContent, options.AppPrivateKey, options.Charset, options.SignType);
  530. // 添加签名结果参数
  531. sortedDic.Add(AlipayConstants.SIGN, signResult);
  532. // 参数拼接
  533. var signedResult = AlipayUtility.BuildQuery(sortedDic);
  534. // 构造结果
  535. var rsp = Activator.CreateInstance<T>();
  536. rsp.ResponseBody = signedResult;
  537. return Task.FromResult(rsp);
  538. }
  539. #endregion
  540. #region Model Serialize
  541. private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
  542. private AlipayDictionary SerializeBizModel<T>(AlipayDictionary requestParams, IAlipayRequest<T> request) where T : AlipayResponse
  543. {
  544. var result = requestParams;
  545. var isBizContentEmpty = !requestParams.ContainsKey(AlipayConstants.BIZ_CONTENT) || string.IsNullOrEmpty(requestParams[AlipayConstants.BIZ_CONTENT]);
  546. var bizModel = request.GetBizModel();
  547. if (isBizContentEmpty && bizModel != null)
  548. {
  549. var content = JsonSerializer.Serialize(bizModel, bizModel.GetType(), jsonSerializerOptions);
  550. result.Add(AlipayConstants.BIZ_CONTENT, content);
  551. }
  552. return result;
  553. }
  554. #endregion
  555. }
  556. }