Skip to content

Commit

Permalink
Merge pull request #69 from EasyAbp/pay-multi-cert
Browse files Browse the repository at this point in the history
Implement WeChat pay multi-merchant certifications
  • Loading branch information
gdlcf88 committed Jan 7, 2023
2 parents c4552e1 + 3e1753b commit 49ac6fa
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public virtual Task<ActionResult> NotifyAuth4Async([CanBeNull] string tenantId,
var contentType = new MediaTypeHeaderValue(result.ResponseToWeChatModel switch
{
JsonResponseToWeChatModel => "application/json",
XmlResponseToWeChatModel => "text/xml",
XmlResponseToWeChatModel => "application/xml",
null => "text/plain",
_ => "text/plain"
})
Expand Down
45 changes: 1 addition & 44 deletions src/Pay/EasyAbp.Abp.WeChat.Pay/AbpWeChatPayModule.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System;
using System.IO;
using System.Net.Http;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using EasyAbp.Abp.WeChat.Common;
using EasyAbp.Abp.WeChat.Pay.Options;
using Microsoft.Extensions.DependencyInjection;
using EasyAbp.Abp.WeChat.Common;
using Volo.Abp.BlobStoring;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;

namespace EasyAbp.Abp.WeChat.Pay
{
Expand All @@ -19,40 +11,5 @@ namespace EasyAbp.Abp.WeChat.Pay
)]
public class AbpWeChatPayModule : AbpModule
{
public override void PostConfigureServices(ServiceConfigurationContext context)
{
ConfigureWeChatPayHttpClient(context);
}

private void ConfigureWeChatPayHttpClient(ServiceConfigurationContext context)
{
// todo: 证书需支持多商户
context.Services.AddHttpClient("WeChatPay").ConfigurePrimaryHttpMessageHandler(builder =>
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var options = AsyncHelper.RunSync(() => builder.GetRequiredService<IAbpWeChatPayOptionsProvider>().GetAsync(null));
if (string.IsNullOrEmpty(options.CertificateBlobName)) return handler;
var blobContainer = options.CertificateBlobContainerName.IsNullOrWhiteSpace()
? builder.GetRequiredService<IBlobContainer>()
: builder.GetRequiredService<IBlobContainerFactory>().Create(options.CertificateBlobContainerName);
var certificateBytes = AsyncHelper.RunSync(() => blobContainer.GetAllBytesOrNullAsync(options.CertificateBlobName));
if (certificateBytes == null) throw new FileNotFoundException("指定的证书路径无效,请重新指定有效的证书文件路径。");
handler.ClientCertificates.Add(new X509Certificate2(
certificateBytes,
options.CertificateSecret,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet));
handler.ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true;
return handler;
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using EasyAbp.Abp.WeChat.Pay.Options;
using Volo.Abp;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;

namespace EasyAbp.Abp.WeChat.Pay.ApiRequests;

public class AbpWeChatPayHttpClientFactory : IAbpWeChatPayHttpClientFactory, ITransientDependency
{
public static TimeSpan SkipCertificateBytesCheckUntilDuration = TimeSpan.FromSeconds(10);

private const string DefaultHandlerKey = "__Default__";

protected static ConcurrentDictionary<string, Lazy<Task<HttpMessageHandlerCacheModel>>> CachedHandlers { get; } =
new();

protected IClock Clock { get; }
protected IAbpLazyServiceProvider AbpLazyServiceProvider { get; }
protected IAbpWeChatPayOptionsProvider AbpWeChatPayOptionsProvider { get; }

public AbpWeChatPayHttpClientFactory(
IClock clock,
IAbpLazyServiceProvider abpLazyServiceProvider,
IAbpWeChatPayOptionsProvider abpWeChatPayOptionsProvider)
{
Clock = clock;
AbpLazyServiceProvider = abpLazyServiceProvider;
AbpWeChatPayOptionsProvider = abpWeChatPayOptionsProvider;
}

public virtual async Task<HttpClient> CreateAsync(string mchId)
{
var options = await AbpWeChatPayOptionsProvider.GetAsync(mchId);

var handler = await GetOrCreateHttpClientHandlerAsync(options);

return new HttpClient(handler, disposeHandler: false);
}

protected virtual async Task<HttpMessageHandler> GetOrCreateHttpClientHandlerAsync(AbpWeChatPayOptions options)
{
if (!CachedHandlers.TryGetValue(options.MchId, out var item))
{
return (await CachedHandlers.GetOrAdd(options.MchId ?? DefaultHandlerKey,
_ => new Lazy<Task<HttpMessageHandlerCacheModel>>(() =>
CreateHttpClientHandlerCacheModelAsync(options))).Value).Handler;
}

var handlerCacheModel = await item.Value;

if (handlerCacheModel.SkipCertificateBytesCheckUntil > Clock.Now)
{
return handlerCacheModel.Handler;
}

var certificateBytes = await GetCertificateBytesOrNullAsync(options);

if (handlerCacheModel.CertificateBytes == certificateBytes)
{
return handlerCacheModel.Handler;
}

CachedHandlers.TryUpdate(
options.MchId ?? DefaultHandlerKey,
new Lazy<Task<HttpMessageHandlerCacheModel>>(() =>
CreateHttpClientHandlerCacheModelAsync(options, certificateBytes)),
item);

return (await CachedHandlers.GetOrDefault(options.MchId).Value).Handler;
}

protected virtual async Task<byte[]> GetCertificateBytesOrNullAsync(AbpWeChatPayOptions options)
{
if (options.CertificateBlobName.IsNullOrEmpty())
{
return null;
}

var blobContainer = options.CertificateBlobContainerName.IsNullOrWhiteSpace()
? AbpLazyServiceProvider.LazyGetRequiredService<IBlobContainer>()
: AbpLazyServiceProvider.LazyGetRequiredService<IBlobContainerFactory>()
.Create(options.CertificateBlobContainerName);

var certificateBytes = await blobContainer.GetAllBytesOrNullAsync(options.CertificateBlobName);
if (certificateBytes == null)
{
throw new AbpException("指定的证书 Blob 无效,请重新指定有效的证书 Blob。");
}

return certificateBytes;
}

protected virtual async Task<HttpMessageHandlerCacheModel> CreateHttpClientHandlerCacheModelAsync(
AbpWeChatPayOptions options)
{
var certificateBytes = await GetCertificateBytesOrNullAsync(options);

return await CreateHttpClientHandlerCacheModelAsync(options, certificateBytes);
}

protected virtual Task<HttpMessageHandlerCacheModel> CreateHttpClientHandlerCacheModelAsync(
AbpWeChatPayOptions options, byte[] certificateBytes)
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};

if (!certificateBytes.IsNullOrEmpty())
{
handler.ClientCertificates.Add(new X509Certificate2(
certificateBytes,
options.CertificateSecret,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet));

handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}

return Task.FromResult(new HttpMessageHandlerCacheModel
{
Handler = handler,
CertificateBytes = certificateBytes
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Volo.Abp.DependencyInjection;
Expand All @@ -8,35 +9,36 @@ namespace EasyAbp.Abp.WeChat.Pay.ApiRequests
[Dependency(TryRegister = true)]
public class DefaultWeChatPayApiRequester : IWeChatPayApiRequester, ITransientDependency
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IAbpWeChatPayHttpClientFactory _httpClientFactory;

public DefaultWeChatPayApiRequester(IHttpClientFactory httpClientFactory)
public DefaultWeChatPayApiRequester(IAbpWeChatPayHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public virtual async Task<XmlDocument> RequestAsync(string url, string body)
public virtual async Task<XmlDocument> RequestAsync(string url, string body, string mchId)
{
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(body)
Content = new StringContent(body, Encoding.UTF8, "application/xml")
};

var client = _httpClientFactory.CreateClient("WeChatPay");
var client = await _httpClientFactory.CreateAsync(mchId);
var responseMessage = await client.SendAsync(request);
var readAsString = await responseMessage.Content.ReadAsStringAsync();

if (!responseMessage.IsSuccessStatusCode)
{
throw new HttpRequestException($"微信支付接口请求失败。\n错误码: {responseMessage.StatusCode}\n响应内容: {readAsString}");
throw new HttpRequestException(
$"微信支付接口请求失败。\n错误码: {responseMessage.StatusCode}\n响应内容: {readAsString}");
}

var newXmlDocument = new XmlDocument();
try
{
newXmlDocument.LoadXml(readAsString);
}
catch (XmlException e)
catch (XmlException)
{
throw new HttpRequestException($"请求接口失败,返回的不是一个标准的 XML 文档。\n响应内容: {readAsString}");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Net.Http;

namespace EasyAbp.Abp.WeChat.Pay.ApiRequests;

public class HttpMessageHandlerCacheModel
{
public HttpMessageHandler Handler { get; set; }

public byte[] CertificateBytes { get; set; }

public DateTime SkipCertificateBytesCheckUntil { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Net.Http;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace EasyAbp.Abp.WeChat.Pay.ApiRequests;

public interface IAbpWeChatPayHttpClientFactory
{
Task<HttpClient> CreateAsync([CanBeNull] string mchId);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Threading.Tasks;
using System.Xml;
using JetBrains.Annotations;

namespace EasyAbp.Abp.WeChat.Pay.ApiRequests
{
public interface IWeChatPayApiRequester
{
Task<XmlDocument> RequestAsync(string url, string body);
Task<XmlDocument> RequestAsync([NotNull] string url, [NotNull] string body, [CanBeNull] string mchId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public OrdinaryMerchantPayWeService(AbpWeChatPayOptions options, IAbpLazyService
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(await GetRequestUrl(UnifiedOrderUrl), request);
return await RequestAndGetReturnValueAsync(await GetRequestUrl(UnifiedOrderUrl), request, mchId);
}

/// <summary>
Expand Down Expand Up @@ -155,7 +155,7 @@ public OrdinaryMerchantPayWeService(AbpWeChatPayOptions options, IAbpLazyService
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(await GetRequestUrl(RefundUrl), request);
return await RequestAndGetReturnValueAsync(await GetRequestUrl(RefundUrl), request, mchId);
}

#endregion
Expand Down Expand Up @@ -200,7 +200,7 @@ public OrdinaryMerchantPayWeService(AbpWeChatPayOptions options, IAbpLazyService
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(await GetRequestUrl(OrderQueryUrl), request);
return await RequestAndGetReturnValueAsync(await GetRequestUrl(OrderQueryUrl), request, mchId);
}

#endregion
Expand Down Expand Up @@ -230,7 +230,7 @@ public virtual async Task<XmlDocument> CloseOrderAsync(string appId, string mchI
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(await GetRequestUrl(CloseOrderUrl), request);
return await RequestAndGetReturnValueAsync(await GetRequestUrl(CloseOrderUrl), request, mchId);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public ServiceProviderPayWeService(AbpWeChatPayOptions options, IAbpLazyServiceP
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(UnifiedOrderUrl, request);
return await RequestAndGetReturnValueAsync(UnifiedOrderUrl, request, mchId);
}

/// <summary>
Expand Down Expand Up @@ -155,7 +155,7 @@ public ServiceProviderPayWeService(AbpWeChatPayOptions options, IAbpLazyServiceP
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(RefundUrl, request);
return await RequestAndGetReturnValueAsync(RefundUrl, request, mchId);
}

/// <summary>
Expand Down Expand Up @@ -190,7 +190,7 @@ public ServiceProviderPayWeService(AbpWeChatPayOptions options, IAbpLazyServiceP
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(OrderQueryUrl, request);
return await RequestAndGetReturnValueAsync(OrderQueryUrl, request, mchId);
}

/// <summary>
Expand Down Expand Up @@ -218,7 +218,7 @@ public ServiceProviderPayWeService(AbpWeChatPayOptions options, IAbpLazyServiceP
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(CloseOrderUrl, request);
return await RequestAndGetReturnValueAsync(CloseOrderUrl, request, mchId);
}

/// <summary>
Expand Down Expand Up @@ -257,7 +257,7 @@ public ServiceProviderPayWeService(AbpWeChatPayOptions options, IAbpLazyServiceP
var signStr = SignatureGenerator.Generate(request, MD5.Create(), Options.ApiKey);
request.AddParameter("sign", signStr);

return await RequestAndGetReturnValueAsync(RefundQueryUrl, request);
return await RequestAndGetReturnValueAsync(RefundQueryUrl, request, mchId);
}
}
}

0 comments on commit 49ac6fa

Please sign in to comment.