Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

微信支付 V3 接口升级 - Reopen #98

Merged
merged 13 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>

<AbpVersion>7.3.0</AbpVersion>
<AbpVersion>7.4.0</AbpVersion>
<MicrosoftNetTestSdkVersion>17.2.0</MicrosoftNetTestSdkVersion>

</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion common.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>3.0.0-preview.7</Version>
<Version>3.0.0-preview.8</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>EasyAbp Team</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.10" />
</ItemGroup>

</Project>
21 changes: 20 additions & 1 deletion src/Common/EasyAbp.Abp.WeChat.Common/Extensions/HashHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Security.Cryptography;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;

namespace EasyAbp.Abp.WeChat.Common.Extensions
Expand All @@ -16,5 +18,22 @@ public static string ToMd5(this string str)

return stringBuilder.ToString();
}

public static byte[] Sha256(this byte[] bytes)
{
using (var sha256 = SHA256.Create())
{
return sha256.ComputeHash(bytes);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool VerifySha256(this byte[] bytes, byte[] hash)
{
using (var sha256 = SHA256.Create())
{
return sha256.ComputeHash(bytes).SequenceEqual(hash);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ namespace EasyAbp.Abp.WeChat.MiniProgram.Services.ACode
/// </summary>
public class ACodeWeService : MiniProgramAbpWeChatServiceBase
{
public ACodeWeService(AbpWeChatMiniProgramOptions options, IAbpLazyServiceProvider lazyServiceProvider) : base(
options, lazyServiceProvider)
public ACodeWeService(AbpWeChatMiniProgramOptions options, IAbpLazyServiceProvider lazyServiceProvider) : base(options, lazyServiceProvider)
{
}

Expand All @@ -20,20 +19,16 @@ public ACodeWeService(AbpWeChatMiniProgramOptions options, IAbpLazyServiceProvid
/// </summary>
/// <param name="scene">最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)</param>
/// <param name="page">必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面</param>
/// <param name="checkPage">检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但 page 有数量上限(60000个)请勿滥用</param>
/// <param name="envVersion">要打开的小程序版本。正式版为 release,体验版为 trial,开发版为 develop</param>
/// <param name="width">二维码的宽度,单位 px,最小 280px,最大 1280px</param>
/// <param name="autoColor">自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调,默认 false</param>
/// <param name="lineColor">auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示</param>
/// <param name="isHyaline">是否需要透明底色,为 true 时,生成透明底色的小程序</param>
public virtual Task<GetUnlimitedACodeResponse> GetUnlimitedACodeAsync(string scene, string page = null,
bool checkPage = true, string envVersion = "release", short width = 430, bool autoColor = false,
LineColorModel lineColor = null, bool isHyaline = false)
short width = 430, bool autoColor = false, LineColorModel lineColor = null, bool isHyaline = false)
{
const string targetUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit";

var request = new GetUnlimitedACodeRequest(
scene, page, checkPage, envVersion, width, autoColor, lineColor, isHyaline);
var request = new GetUnlimitedACodeRequest(scene, page, width, autoColor, lineColor, isHyaline);

return ApiRequester.RequestGetBinaryDataAsync<GetUnlimitedACodeResponse>(
targetUrl, HttpMethod.Post, request, Options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,70 +23,53 @@ public class GetUnlimitedACodeRequest : MiniProgramCommonRequest
[JsonProperty("page")]
public string Page { get; protected set; }

/// <summary>
/// 检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但 page 有数量上限(60000个)请勿滥用
/// </summary>
[JsonPropertyName("check_path")]
[JsonProperty("check_path")]
public bool CheckPage { get; protected set; }

/// <summary>
/// 要打开的小程序版本。正式版为 release,体验版为 trial,开发版为 develop
/// </summary>
[JsonPropertyName("env_version")]
[JsonProperty("env_version")]
public string EnvVersion { get; protected set; }

/// <summary>
/// 二维码的宽度,单位 px,最小 280px,最大 1280px
/// </summary>
[JsonPropertyName("width")]
[JsonProperty("width")]
public short Width { get; protected set; }

/// <summary>
/// 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调,默认 false
/// </summary>
[JsonPropertyName("auto_color")]
[JsonProperty("auto_color")]
public bool AutoColor { get; set; }

/// <summary>
/// auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示
/// </summary>
[JsonPropertyName("line_color")]
[JsonProperty("line_color")]
public LineColorModel LineColor { get; set; }

/// <summary>
/// 是否需要透明底色,为 true 时,生成透明底色的小程序
/// </summary>
[JsonPropertyName("is_hyaline")]
[JsonProperty("is_hyaline")]
public bool IsHyaline { get; set; }

protected GetUnlimitedACodeRequest()
{

}

/// <summary>
/// 构造一个新的 <see cref="GetUnlimitedACodeRequest"/> 实例。
/// </summary>
/// <param name="scene">最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)</param>
/// <param name="page">必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面</param>
/// <param name="checkPage">检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但 page 有数量上限(60000个)请勿滥用</param>
/// <param name="envVersion">要打开的小程序版本。正式版为 release,体验版为 trial,开发版为 develop</param>
/// <param name="width">二维码的宽度,单位 px,最小 280px,最大 1280px</param>
/// <param name="autoColor">自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调,默认 false</param>
/// <param name="lineColor">auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示</param>
/// <param name="isHyaline">是否需要透明底色,为 true 时,生成透明底色的小程序</param>
public GetUnlimitedACodeRequest(string scene, string page, bool checkPage, string envVersion, short width,
bool autoColor, LineColorModel lineColor, bool isHyaline)
public GetUnlimitedACodeRequest(string scene, string page, short width, bool autoColor,
LineColorModel lineColor, bool isHyaline)
{
Scene = scene;
Page = page;
CheckPage = checkPage;
EnvVersion = envVersion;
Width = width;
AutoColor = autoColor;
LineColor = lineColor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
using System.Collections.Generic;
using EasyAbp.Abp.WeChat.Official.Models;
using Newtonsoft.Json;

Expand All @@ -11,31 +10,23 @@ namespace EasyAbp.Abp.WeChat.Official.Services.TemplateMessage.Request
public class CreateTemplateRequest : OfficialCommonRequest
{
/// <summary>
/// 模板库中模板的编号,有TM**”和“OPENTMTM**等形式,对于类目模板,为纯数字ID
/// 模板库中模板的编号,有 "TM**" 和 "OPENTMTM**" 等形式
/// </summary>
[JsonPropertyName("template_id_short")]
[JsonProperty("template_id_short")]
public string TemplateShortId { get; protected set; }

/// <summary>
/// 选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码
/// </summary>
[JsonProperty("keyword_name_list")]
public List<string> KeywordNameList { get; protected set; }

protected CreateTemplateRequest()
{
}

/// <summary>
/// 构建一个新的 <see cref="CreateTemplateRequest"/> 对象。
/// </summary>
/// <param name="templateShortId">模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式,对于类目模板,为纯数字ID</param>
/// <param name="keywordNameList">选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码</param>
public CreateTemplateRequest(string templateShortId, List<string> keywordNameList)
/// <param name="templateShortId">模板库中模板的编号,有 "TM**" 和 "OPENTMTM**" 等形式。</param>
public CreateTemplateRequest(string templateShortId)
{
TemplateShortId = templateShortId;
KeywordNameList = keywordNameList;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using EasyAbp.Abp.WeChat.Official.Models;
Expand Down Expand Up @@ -26,8 +25,7 @@ public class TemplateMessageWeService : OfficialAbpWeChatServiceBase
private const string DeletePrivateTemplateUrl =
"https://api.weixin.qq.com/cgi-bin/template/del_private_template?";

public TemplateMessageWeService(AbpWeChatOfficialOptions options, IAbpLazyServiceProvider lazyServiceProvider) :
base(options, lazyServiceProvider)
public TemplateMessageWeService(AbpWeChatOfficialOptions options, IAbpLazyServiceProvider lazyServiceProvider) : base(options, lazyServiceProvider)
{
}

Expand Down Expand Up @@ -114,15 +112,13 @@ public virtual Task<GetIndustryResponse> GetIndustryAsync()
/// <summary>
/// 根据短模版 Id 创建模版。
/// </summary>
/// <param name="templateShortId">模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式,对于类目模板,为纯数字ID</param>
/// <param name="keywordNameList">选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码</param>
public virtual Task<CreateTemplateResponse> CreateTemplateAsync(string templateShortId,
List<string> keywordNameList)
/// <param name="templateShortId">模板库中模板的编号,有 "TM**" 和 "OPENTMTM**" 等形式。</param>
public virtual Task<CreateTemplateResponse> CreateTemplateAsync(string templateShortId)
{
return ApiRequester.RequestAsync<CreateTemplateResponse>(
GetTemplateIdUrl,
HttpMethod.Post,
new CreateTemplateRequest(templateShortId, keywordNameList),
new CreateTemplateRequest(templateShortId),
Options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
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.Common.Extensions;
using EasyAbp.Abp.WeChat.Pay.Options;
using Volo.Abp;
using Volo.Abp.BlobStoring;
using EasyAbp.Abp.WeChat.Pay.Security;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;

namespace EasyAbp.Abp.WeChat.Pay.ApiRequests;

public class AbpWeChatPayHttpClientFactory : IAbpWeChatPayHttpClientFactory, ITransientDependency
{
// TODO: Confirm checking every 10 second? @gdlc88
public static TimeSpan SkipCertificateBytesCheckUntilDuration = TimeSpan.FromSeconds(10);

private const string DefaultHandlerKey = "__Default__";
Expand All @@ -25,21 +25,23 @@ public class AbpWeChatPayHttpClientFactory : IAbpWeChatPayHttpClientFactory, ITr
protected IClock Clock { get; }
protected IAbpLazyServiceProvider AbpLazyServiceProvider { get; }
protected IAbpWeChatPayOptionsProvider AbpWeChatPayOptionsProvider { get; }
protected ICertificatesManager CertificatesManager { get; }

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

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);
Expand All @@ -55,82 +57,51 @@ protected virtual async Task<HttpMessageHandler> GetOrCreateHttpClientHandlerAsy
}

var handlerCacheModel = await item.Value;

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

var certificateBytes = await GetCertificateBytesOrNullAsync(options);

if (handlerCacheModel.CertificateBytes == certificateBytes &&
handlerCacheModel.CertificateSecret == options.CertificateSecret)
var certificate = await CertificatesManager.GetCertificateAsync(options.MchId);
if (handlerCacheModel.WeChatPayCertificate.CertificateHashCode.VerifySha256(certificate.CertificateHashCode))
{
return handlerCacheModel.Handler;
}

// If the certificate has expired, need to pull the latest one from BLOB again.
CachedHandlers.TryUpdate(
options.MchId ?? DefaultHandlerKey,
new Lazy<Task<HttpMessageHandlerCacheModel>>(() =>
CreateHttpClientHandlerCacheModelAsync(options, certificateBytes)),
CreateHttpClientHandlerCacheModelAsync(certificate)),
item);

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

protected virtual async Task<byte[]> GetCertificateBytesOrNullAsync(AbpWeChatPayOptions options)
protected virtual async Task<HttpMessageHandlerCacheModel> CreateHttpClientHandlerCacheModelAsync(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;
var certificate = await CertificatesManager.GetCertificateAsync(options.MchId);
return await CreateHttpClientHandlerCacheModelAsync(certificate);
}

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)
protected virtual Task<HttpMessageHandlerCacheModel> CreateHttpClientHandlerCacheModelAsync(WeChatPayCertificate weChatPayCertificate)
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};

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

handler.ClientCertificates.Add(weChatPayCertificate.X509Certificate);
handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}

return Task.FromResult(new HttpMessageHandlerCacheModel
{
Handler = handler,
CertificateBytes = certificateBytes,
CertificateSecret = options.CertificateSecret,
WeChatPayCertificate = weChatPayCertificate,
SkipCertificateBytesCheckUntil = Clock.Now.Add(SkipCertificateBytesCheckUntilDuration)
});
}
Expand Down
Loading