using Essensoft.AspNetCore.Payment.Alipay.Parser; using Essensoft.AspNetCore.Payment.Alipay.Request; using Essensoft.AspNetCore.Payment.Alipay.Utility; using Essensoft.AspNetCore.Payment.Security; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace Essensoft.AspNetCore.Payment.Alipay { /// /// Alipay客户端。 /// public class AlipayClient : IAlipayClient { private const string APP_ID = "app_id"; private const string FORMAT = "format"; private const string METHOD = "method"; private const string TIMESTAMP = "timestamp"; private const string VERSION = "version"; private const string SIGN_TYPE = "sign_type"; private const string ACCESS_TOKEN = "auth_token"; private const string SIGN = "sign"; private const string TERMINAL_TYPE = "terminal_type"; private const string TERMINAL_INFO = "terminal_info"; private const string PROD_CODE = "prod_code"; private const string NOTIFY_URL = "notify_url"; private const string CHARSET = "charset"; private const string ENCRYPT_TYPE = "encrypt_type"; private const string BIZ_CONTENT = "biz_content"; private const string APP_AUTH_TOKEN = "app_auth_token"; private const string RETURN_URL = "return_url"; private readonly RSAParameters PrivateRSAParameters; private readonly RSAParameters PublicRSAParameters; public AlipayOptions Options { get; } public virtual ILogger Logger { get; set; } protected internal HttpClientEx Client { get; set; } #region AlipayClient Constructors public AlipayClient( IOptions optionsAccessor, ILogger logger) { Options = optionsAccessor.Value; Logger = logger; Client = new HttpClientEx(); if (string.IsNullOrEmpty(Options.AppId)) { throw new ArgumentNullException(nameof(Options.AppId)); } if (string.IsNullOrEmpty(Options.RsaPrivateKey)) { throw new ArgumentNullException(nameof(Options.RsaPrivateKey)); } if (string.IsNullOrEmpty(Options.RsaPublicKey)) { throw new ArgumentNullException(nameof(Options.RsaPublicKey)); } PrivateRSAParameters = RSAUtilities.GetRSAParametersFormPrivateKey(Options.RsaPrivateKey); PublicRSAParameters = RSAUtilities.GetRSAParametersFormPublicKey(Options.RsaPublicKey); } public AlipayClient(IOptions optionsAccessor) : this(optionsAccessor, null) { } #endregion #region IAlipayClient Members public void SetTimeout(int timeout) { Client.Timeout = new TimeSpan(0, 0, 0, timeout); } #endregion #region IAlipayClient Members public async Task ExecuteAsync(IAlipayRequest request) where T : AlipayResponse { return await ExecuteAsync(request, null); } public async Task ExecuteAsync(IAlipayRequest request, string accessToken) where T : AlipayResponse { return await ExecuteAsync(request, accessToken, null); } #endregion #region IAlipayClient Members public async Task PageExecuteAsync(IAlipayRequest request) where T : AlipayResponse { return await PageExecuteAsync(request, null, "POST"); } #endregion #region IAlipayClient Members public async Task PageExecuteAsync(IAlipayRequest request, string accessToken, string reqMethod) where T : AlipayResponse { string apiVersion = null; if (!string.IsNullOrEmpty(request.GetApiVersion())) { apiVersion = request.GetApiVersion(); } else { apiVersion = Options.Version; } var txtParams = new AlipayDictionary(request.GetParameters()) { // 序列化BizModel { BIZ_CONTENT, Serialize(request.GetBizModel()) }, // 添加协议级请求参数 { METHOD, request.GetApiName() }, { VERSION, apiVersion }, { APP_ID, Options.AppId }, { FORMAT, Options.Format }, { TIMESTAMP, DateTime.Now }, { ACCESS_TOKEN, accessToken }, { SIGN_TYPE, Options.SignType }, { TERMINAL_TYPE, request.GetTerminalType() }, { TERMINAL_INFO, request.GetTerminalInfo() }, { PROD_CODE, request.GetProdCode() }, { NOTIFY_URL, request.GetNotifyUrl() }, { CHARSET, Options.Charset }, { RETURN_URL, request.GetReturnUrl() } }; // 添加签名参数 var signContent = AlipaySignature.GetSignContent(txtParams); txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType)); // 是否需要上传文件 var body = string.Empty; if (request is IAlipayUploadRequest uRequest) { var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters()); body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams); } else { if (reqMethod.ToUpper() == "GET") { //拼接get请求的url var tmpUrl = Options.ServerUrl; if (txtParams != null && txtParams.Count > 0) { if (tmpUrl.Contains("?")) { tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(txtParams); } else { tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(txtParams); } } body = tmpUrl; Logger?.LogTrace(0, "Request Url:{body}", body); } else { //输出post表单 body = BuildHtmlRequest(txtParams, reqMethod); Logger?.LogTrace(0, "Request Html:{body}", body); } } T rsp = null; IAlipayParser parser = null; if ("xml".Equals(Options.Format)) { parser = new AlipayXmlParser(); rsp = parser.Parse(body); } else { parser = new AlipayJsonParser(); rsp = parser.Parse(body); } return rsp; } #endregion #region IAlipayClient Members public async Task ExecuteAsync(IAlipayRequest request, string accessToken, string appAuthToken) where T : AlipayResponse { var apiVersion = string.Empty; if (!string.IsNullOrEmpty(request.GetApiVersion())) { apiVersion = request.GetApiVersion(); } else { apiVersion = Options.Version; } // 添加协议级请求参数 var txtParams = new AlipayDictionary(request.GetParameters()) { // 序列化BizModel { BIZ_CONTENT, Serialize(request.GetBizModel()) }, // 添加协议级请求参数 { METHOD, request.GetApiName() }, { VERSION, apiVersion }, { APP_ID, Options.AppId }, { FORMAT, Options.Format }, { TIMESTAMP, DateTime.Now }, { ACCESS_TOKEN, accessToken }, { SIGN_TYPE, Options.SignType }, { TERMINAL_TYPE, request.GetTerminalType() }, { TERMINAL_INFO, request.GetTerminalInfo() }, { PROD_CODE, request.GetProdCode() }, { CHARSET, Options.Charset } }; if (!string.IsNullOrEmpty(request.GetNotifyUrl())) { txtParams.Add(NOTIFY_URL, request.GetNotifyUrl()); } if (!string.IsNullOrEmpty(appAuthToken)) { txtParams.Add(APP_AUTH_TOKEN, appAuthToken); } if (request.GetNeedEncrypt()) { if (string.IsNullOrEmpty(txtParams[BIZ_CONTENT])) { throw new Exception("api request Fail ! The reason: encrypt request is not supported!"); } if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType)) { throw new Exception("encryptType or encryptKey must not null!"); } if (!"AES".Equals(Options.EncyptType)) { throw new Exception("api only support Aes!"); } var encryptContent = AES.Encrypt(txtParams[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7); txtParams.Remove(BIZ_CONTENT); txtParams.Add(BIZ_CONTENT, encryptContent); txtParams.Add(ENCRYPT_TYPE, Options.EncyptType); } // 添加签名参数 var signContent = AlipaySignature.GetSignContent(txtParams); txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType)); var query = HttpClientEx.BuildQuery(txtParams); Logger?.LogTrace(0, "Request:{query}", query); // 是否需要上传文件 var body = string.Empty; if (request is IAlipayUploadRequest uRequest) { var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters()); body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams); } else { body = await Client.DoPostAsync(Options.ServerUrl, query); } Logger?.LogTrace(1, "Response:{body}", body); T rsp = null; IAlipayParser parser = null; if ("xml".Equals(Options.Format)) { parser = new AlipayXmlParser(); rsp = parser.Parse(body); } else { parser = new AlipayJsonParser(); rsp = parser.Parse(body); } var item = ParseRespItem(request, body, parser, Options.EncyptKey, Options.EncyptType); rsp = parser.Parse(item.realContent); CheckResponseSign(request, item.respContent, rsp.IsError, parser, PublicRSAParameters, Options.SignType); return rsp; } private ResponseParseItem ParseRespItem(IAlipayRequest request, string respBody, IAlipayParser parser, string encryptKey, string encryptType) where T : AlipayResponse { string realContent = null; if (request.GetNeedEncrypt()) { realContent = parser.EncryptSourceData(request, respBody, encryptType, encryptKey); } else { realContent = respBody; } var item = new ResponseParseItem() { realContent = realContent, respContent = respBody }; return item; } private void CheckResponseSign(IAlipayRequest request, string responseBody, bool isError, IAlipayParser parser, RSAParameters parameters, string signType) where T : AlipayResponse { var signItem = parser.GetSignItem(request, responseBody); if (signItem == null) { throw new Exception("sign check fail: Body is Empty!"); } if (!isError || (isError && !string.IsNullOrEmpty(signItem.Sign))) { var rsaCheckContent = AlipaySignature.RSACheckContent(signItem.SignSourceDate, signItem.Sign, parameters, signType); if (!rsaCheckContent) { if (!string.IsNullOrEmpty(signItem.SignSourceDate) && signItem.SignSourceDate.Contains("\\/")) { var srouceData = signItem.SignSourceDate.Replace("\\/", "/"); var jsonCheck = AlipaySignature.RSACheckContent(srouceData, signItem.Sign, parameters, signType); if (!jsonCheck) { throw new Exception("sign check fail: check Sign and Data Fail JSON also"); } } else { throw new Exception("sign check fail: check Sign and Data Fail!"); } } } } #endregion #region IAlipayClient Members public string BuildHtmlRequest(IDictionary sParaTemp, string strMethod) { //待请求参数数组 var dicPara = new Dictionary(sParaTemp); var sbHtml = new StringBuilder(); sbHtml.Append(""); //表单实现自动提交 sbHtml.Append(""); return sbHtml.ToString(); } #endregion #region SDK Execute public Task SdkExecuteAsync(IAlipayRequest request) where T : AlipayResponse { // 构造请求参数 var requestParams = BuildRequestParams(request, null, null); // 字典排序 var sortedParams = new SortedDictionary(requestParams); var sortedAlipayDic = new AlipayDictionary(sortedParams); // 参数签名 var signContent = AlipaySignature.GetSignContent(sortedAlipayDic); var signResult = AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType); // 添加签名结果参数 sortedAlipayDic.Add(SIGN, signResult); // 参数拼接 var signedResult = HttpClientEx.BuildQuery(sortedAlipayDic); // 构造结果 var rsp = Activator.CreateInstance(); rsp.Body = signedResult; return Task.FromResult(rsp); } #endregion #region Common Method private AlipayDictionary BuildRequestParams(IAlipayRequest request, string accessToken, string appAuthToken) where T : AlipayResponse { // 默认参数 var result = new AlipayDictionary(request.GetParameters()) { // 序列化BizModel { BIZ_CONTENT, Serialize(request.GetBizModel()) }, // 添加协议级请求参数,为空的参数后面会自动过滤,这里不做处理。 { METHOD, request.GetApiName() }, { VERSION, string.IsNullOrEmpty(request.GetApiVersion()) ? Options.Version : request.GetApiVersion() }, { APP_ID, Options.AppId }, { FORMAT, Options.Format }, { TIMESTAMP, DateTime.Now }, { ACCESS_TOKEN, accessToken }, { SIGN_TYPE, Options.SignType }, { TERMINAL_TYPE, request.GetTerminalType() }, { TERMINAL_INFO, request.GetTerminalInfo() }, { PROD_CODE, request.GetProdCode() }, { NOTIFY_URL, request.GetNotifyUrl() }, { CHARSET, Options.Charset }, { RETURN_URL, request.GetReturnUrl() }, { APP_AUTH_TOKEN, appAuthToken } }; if (request.GetNeedEncrypt()) { if (string.IsNullOrEmpty(result[BIZ_CONTENT])) { throw new Exception("api request Fail ! The reason: encrypt request is not supported!"); } if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType)) { throw new Exception("encryptType or encryptKey must not null!"); } if (!"AES".Equals(Options.EncyptType)) { throw new Exception("api only support Aes!"); } var encryptContent = AES.Encrypt(result[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7); result.Remove(BIZ_CONTENT); result.Add(BIZ_CONTENT, encryptContent); result.Add(ENCRYPT_TYPE, Options.EncyptType); } return result; } #endregion #region Model Serialize private string Serialize(AlipayObject bizModel) { return JsonConvert.SerializeObject(bizModel, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); } #endregion } }