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
}
}