From 3cee797e7d4fc9ce9b39823b0339a16e9118584b Mon Sep 17 00:00:00 2001 From: real-zony Date: Tue, 9 Jan 2024 20:29:13 +0800 Subject: [PATCH 01/11] chore: Marked a spot for performance optimization. --- .../Security/PlatformCertificate/PlatformCertificateEntity.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs index 5a08815..3803518 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs @@ -6,6 +6,7 @@ namespace EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; +// TODO: There might be performance issues here because serialization and deserialization operations are involved every time the cache is accessed. The best practice would be to cache the certificates directly in memory. public class X509Certificate2JsonConverter : JsonConverter { public override void WriteJson(JsonWriter writer, X509Certificate2 value, JsonSerializer serializer) From c8cd59af00875f9696655253266e3392a98e3db3 Mon Sep 17 00:00:00 2001 From: real-zony Date: Tue, 9 Jan 2024 20:31:11 +0800 Subject: [PATCH 02/11] test: Fixed an issue causing unit tests to fail. --- tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs index f03717f..5bab53b 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs @@ -9,5 +9,6 @@ public class AbpWeChatPayTestConsts public const string AppId = ""; public const string OpenId = ""; public const string NotifyUrl = ""; + public const string SerialNo = ""; } } \ No newline at end of file From 1b2e3bf91fe0969ffbf3ee6110324ee9086f2ccf Mon Sep 17 00:00:00 2001 From: real-zony Date: Tue, 9 Jan 2024 23:22:53 +0800 Subject: [PATCH 03/11] feat: Completed the notification callback interface for WeChat Pay V3. --- .../Controller/WeChatPayController.cs | 47 ++++++++++--------- .../RequestHandling/IWeChatPayEventHandler.cs | 2 +- .../RequestHandling/WeChatPayEventModel.cs | 4 +- .../WeChatPayEventRequestHandlingService.cs | 21 +++++++-- .../BasicPayment/Models/QueryOrderResponse.cs | 2 +- .../PlatformCertificateManagerTests.cs | 30 ++++++++++++ 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs index 0fef758..f715fd9 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text.Json; using System.Threading.Tasks; using EasyAbp.Abp.WeChat.Common; using EasyAbp.Abp.WeChat.Pay.RequestHandling; @@ -7,7 +8,6 @@ using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; @@ -41,27 +41,30 @@ public virtual async Task NotifyAsync([CanBeNull] [FromQuery] stri using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); var body = await GetPostDataAsync(); - // var result = await _eventRequestHandlingService.PaidNotifyAsync(new PaidNotifyInput - // { - // MchId = mchId, - // RequestBodyString = body, - // RequestBody = JsonConvert.DeserializeObject(body), - // SerialNumber = Request.Headers["Wechatpay-Serial"], - // Timestamp = Request.Headers["Wechatpay-TimeStamp"], - // Nonce = Request.Headers["Wechatpay-Nonce"], - // Signature = Request.Headers["Wechatpay-Signature"] - // }); - // - // if (!result.Success) - // { - // return BadRequest(new PaymentNotifyCallbackResponse - // { - // Code = "FAIL", - // Message = "处理失败" - // }); - // } - - return Ok(); + var result = await _eventRequestHandlingService.PaidNotifyAsync(new PaidNotifyInput + { + MchId = mchId, + RequestBodyString = body, + RequestBody = JsonSerializer.Deserialize(body), + SerialNumber = Request.Headers["Wechatpay-Serial"], + Timestamp = Request.Headers["Wechatpay-TimeStamp"], + Nonce = Request.Headers["Wechatpay-Nonce"], + Signature = Request.Headers["Wechatpay-Signature"] + }); + + if (!result.Success) + { + return BadRequest(new PaymentNotifyCallbackResponse + { + Code = "FAIL", + Message = "处理失败" + }); + } + + return Ok(new PaymentNotifyCallbackResponse + { + Code = "SUCCESS" + }); } /// diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/IWeChatPayEventHandler.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/IWeChatPayEventHandler.cs index 91c7ca6..5e85db8 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/IWeChatPayEventHandler.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/IWeChatPayEventHandler.cs @@ -10,6 +10,6 @@ public interface IWeChatPayEventHandler { WeChatHandlerType Type { get; } - Task HandleAsync(WeChatPayEventModel model); + Task HandleAsync(WeChatPayEventModel model); } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventModel.cs index ce3e341..fd8ec4b 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventModel.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventModel.cs @@ -2,7 +2,9 @@ namespace EasyAbp.Abp.WeChat.Pay.RequestHandling; -public class WeChatPayEventModel +public class WeChatPayEventModel { public AbpWeChatPayOptions Options { get; set; } + + public TResource Resource { get; set; } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs index daf0d78..a5542c2 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs @@ -5,7 +5,10 @@ using EasyAbp.Abp.WeChat.Common.RequestHandling; using EasyAbp.Abp.WeChat.Pay.Options; using EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; +using EasyAbp.Abp.WeChat.Pay.Security.Extensions; using EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; +using EasyAbp.Abp.WeChat.Pay.Services.BasicPayment.Models; +using Newtonsoft.Json; using Volo.Abp.DependencyInjection; namespace EasyAbp.Abp.WeChat.Pay.RequestHandling; @@ -37,9 +40,12 @@ public virtual async Task PaidNotifyAsync(PaidNotif return new WeChatRequestHandlingResult(false, "签名验证不通过"); } - var model = new WeChatPayEventModel + var decryptingResult = DecryptResource(input, options); + + var model = new WeChatPayEventModel { - Options = options + Options = options, + Resource = decryptingResult }; foreach (var handler in handlers.Where(x => x.Type == WeChatHandlerType.Paid)) @@ -62,14 +68,12 @@ public virtual async Task RefundNotifyAsync(RefundN var handlers = LazyServiceProvider.LazyGetService>() .Where(x => x.Type == WeChatHandlerType.Refund); - // var (decryptingResult, decryptedXmlDocument) = await _xmlDecrypter.TryDecryptAsync(xmlDocument, options); - // if (!decryptingResult) // { // return new WeChatRequestHandlingResult(false, "微信消息体解码失败"); // } - var model = new WeChatPayEventModel + var model = new WeChatPayEventModel { Options = options }; @@ -96,4 +100,11 @@ protected virtual async Task IsSignValidAsync(PaidNotifyInput input, AbpWe .Append(input.RequestBodyString).Append("\n"); return certificate.VerifySignature(sb.ToString(), input.Signature); } + + protected virtual TObject DecryptResource(PaidNotifyInput input, AbpWeChatPayOptions options) + { + var sourceJson = WeChatPaySecurityUtility.AesGcmDecrypt(options.ApiV3Key, input.RequestBody.Resource.AssociatedData, + input.RequestBody.Resource.Nonce, input.RequestBody.Resource.Ciphertext); + return JsonConvert.DeserializeObject(sourceJson); + } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryOrderResponse.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryOrderResponse.cs index 12e8388..62e0a92 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryOrderResponse.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryOrderResponse.cs @@ -68,7 +68,7 @@ public class QueryOrderResponse : WeChatPayCommonErrorResponse /// 交易类型。 /// /// - /// 交易类型为美剧值,具体值可参考 中找到对应的定义。 + /// 交易类型为枚举值,具体值可参考 中找到对应的定义。 /// /// /// 示例值: MICROPAY。()。 diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs index 6e76693..40ab95b 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs @@ -1,3 +1,8 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading.Tasks; using EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; using Shouldly; @@ -25,4 +30,29 @@ public async Task GetPlatformCertificateAsync_Test() certificate.ShouldNotBeNull(); certificate.SerialNo.ShouldNotBeNull(); } + + [Fact(Skip = "This test is used to generate a new certificate")] + public async Task VerifySignature_Test() + { + // Arrange & Act + var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(AbpWeChatPayTestConsts.MchId, + AbpWeChatPayTestConsts.SerialNo); + + const string jsonBody = """ + 1704808380 + aBbS9ae1bThgNcB1MBlDkTH6NPJAmukF + {"id":"3d7a841c-9ce9-58c9-8840-f564bea68183","create_time":"2024-01-09T21:52:26+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"cmGZ4vFOCRa3xdtLfchYvkQu2n4QEOeCuCWCfDVIt3dvfz6Nw4qax1E5AU7F6zPs1sakAJ9v+Uv/NQ6+MFZEJdnkgfLF4wVtyzFPYNjKpMn+XB0z4/q5iO6D60Sq1Dn/9CYdVWW4364xEoHO3x2+BOF0u9gk8Ql/smB8R3d1KnraD5hV9g+qbmNpd1ghImcnwI/uVZ0ee2jC4RW8urFJSFgA1jPfpK+93vTk7KSxwXNL9brdxctzHgXFeRZG+2D4zaWzvkTvbtX2kpOUYRf1WhS8vYNMDebgpjsfm9ZYwdGC3IswaYhPjzVu178i1k2PyMbYLmJ6F0kP+/obUj0j05BYALClL3wYCSx+2QFS/3w9afahDDrRHEb7S+fidYWFIN6gsUxhYJMXOpdtIzylGQ3EYwNZWOpmXzZx1E/LnGda64B5KcZrBHdJDSFM2DcVlnhN0QZrGMauo6+ItnP72vBYsItoeR1WlSuMtCh277n2ulKl6M4IofmyxhvpooQxttsGYxrt0jE8e7CempDIOL/lMRph5tXKR4uT0VI926teppXXSYhPrjzNtzXxkS4dDhUdb6q+tlEPpGeMuQ==","associated_data":"transaction","nonce":"o583v1AJfowW"}} + + """; + const string sign = "NoEJmfUs2exiKPFK79hJ7wmIKiBU5lCt472EZjhO4/lIEoy2ZBnPabee+zXepUp+wZe7Hs55FmJESb5UnkT9uy68SSnelF8ewXk6XBE+n1oMiFNAtRhVcvBbFaRJYN9tyoU8weTqvBv6mJGX5xfSTCtVCfllf1j4kUbMe0fPv5FOeOKrhRVcBfFh8BxbSmJqDxhPNeWhlh1X0fKjn3OS64ZMsAexJh+RXrTPJa1Y8UWTzCEmS/3SGlwaAGrdDQZAS07TjS6WzvG2MhC5bhn2xtDIP+FtQKXqofZZxOry0Twhd7lDZFXIjJGkLFPHIzGdsBUdwTCVXDN4Anyl6ynZQQ=="; + + var sb = new StringBuilder(); + sb.Append("1704808380").Append('\n') + .Append("aBbS9ae1bThgNcB1MBlDkTH6NPJAmukF").Append('\n') + .Append(jsonBody).Append('\n'); + + // Act + var verifyResponse = certificate.VerifySignature(jsonBody,sign); + verifyResponse.ShouldBeTrue(); + } } \ No newline at end of file From f5c85a3414878128a97b884f04dd6d05c05dd128 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 09:35:39 +0800 Subject: [PATCH 04/11] refactor: Adjusted the structure of Input to allow for parameter reuse. --- .../Dtos/NotifyHttpHeaderModel.cs | 20 +++++++++++++++++++ .../RequestHandling/Dtos/PaidNotifyInput.cs | 12 +++-------- .../Controller/WeChatPayController.cs | 15 +++++++------- .../WeChatPayEventRequestHandlingService.cs | 8 ++++---- 4 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyHttpHeaderModel.cs diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyHttpHeaderModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyHttpHeaderModel.cs new file mode 100644 index 0000000..b863015 --- /dev/null +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyHttpHeaderModel.cs @@ -0,0 +1,20 @@ +namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; + +public class NotifyHttpHeaderModel +{ + public string SerialNumber { get; set; } + + public string Timestamp { get; set; } + + public string Nonce { get; set; } + + public string Signature { get; set; } + + public NotifyHttpHeaderModel(string serialNumber, string timestamp, string nonce, string signature) + { + SerialNumber = serialNumber; + Timestamp = timestamp; + Nonce = nonce; + Signature = signature; + } +} \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs index 96ca8df..1a51186 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs @@ -8,15 +8,9 @@ public class PaidNotifyInput { [CanBeNull] public string MchId { get; set; } - public PaymentNotifyCallbackRequest RequestBody { get; set; } - - public string SerialNumber { get; set; } - - public string Timestamp { get; set; } - - public string Nonce { get; set; } - public string RequestBodyString { get; set; } - public string Signature { get; set; } + public PaymentNotifyCallbackRequest RequestBody { get; set; } + + public NotifyHttpHeaderModel HttpHeader { get; set; } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs index f715fd9..671bb1c 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs @@ -40,16 +40,16 @@ public virtual async Task NotifyAsync([CanBeNull] [FromQuery] stri { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); - var body = await GetPostDataAsync(); + var requestBody = await GetPostDataAsync(); var result = await _eventRequestHandlingService.PaidNotifyAsync(new PaidNotifyInput { MchId = mchId, - RequestBodyString = body, - RequestBody = JsonSerializer.Deserialize(body), - SerialNumber = Request.Headers["Wechatpay-Serial"], - Timestamp = Request.Headers["Wechatpay-TimeStamp"], - Nonce = Request.Headers["Wechatpay-Nonce"], - Signature = Request.Headers["Wechatpay-Signature"] + RequestBodyString = requestBody, + RequestBody = JsonSerializer.Deserialize(requestBody), + HttpHeader = new NotifyHttpHeaderModel(Request.Headers["Wechatpay-Serial"], + Request.Headers["Wechatpay-TimeStamp"], + Request.Headers["Wechatpay-Nonce"], + Request.Headers["Wechatpay-Signature"]) }); if (!result.Success) @@ -108,6 +108,7 @@ public virtual Task Notify4Async([CanBeNull] string tenantId, [Can public virtual async Task RefundNotifyAsync([CanBeNull] string tenantId, [CanBeNull] string mchId) { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); + var requestBody = await GetPostDataAsync(); var result = await _eventRequestHandlingService.RefundNotifyAsync(new RefundNotifyInput { diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs index a5542c2..c803b4d 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs @@ -93,12 +93,12 @@ public virtual async Task RefundNotifyAsync(RefundN protected virtual async Task IsSignValidAsync(PaidNotifyInput input, AbpWeChatPayOptions options) { - var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(options.MchId, input.SerialNumber); + var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(options.MchId, input.HttpHeader.SerialNumber); var sb = new StringBuilder(); - sb.Append(input.Timestamp).Append("\n") - .Append(input.Nonce).Append("\n") + sb.Append(input.HttpHeader.Timestamp).Append("\n") + .Append(input.HttpHeader.Nonce).Append("\n") .Append(input.RequestBodyString).Append("\n"); - return certificate.VerifySignature(sb.ToString(), input.Signature); + return certificate.VerifySignature(sb.ToString(), input.HttpHeader.Signature); } protected virtual TObject DecryptResource(PaidNotifyInput input, AbpWeChatPayOptions options) From 811a1c3b670a7e6d5c48f61a8d7be8ac9eb54da8 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 09:42:07 +0800 Subject: [PATCH 05/11] refactor: Replaced ActionResult with IActionResult. --- .../Controller/WeChatPayController.cs | 68 ++++++++----------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs index 671bb1c..53fc457 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs @@ -35,7 +35,7 @@ public WeChatPayController( /// [HttpPost] [Route("notify")] - public virtual async Task NotifyAsync([CanBeNull] [FromQuery] string tenantId, + public virtual async Task NotifyAsync([CanBeNull] [FromQuery] string tenantId, [CanBeNull] [FromQuery] string mchId) { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); @@ -52,19 +52,7 @@ public virtual async Task NotifyAsync([CanBeNull] [FromQuery] stri Request.Headers["Wechatpay-Signature"]) }); - if (!result.Success) - { - return BadRequest(new PaymentNotifyCallbackResponse - { - Code = "FAIL", - Message = "处理失败" - }); - } - - return Ok(new PaymentNotifyCallbackResponse - { - Code = "SUCCESS" - }); + return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } /// @@ -73,7 +61,7 @@ public virtual async Task NotifyAsync([CanBeNull] [FromQuery] stri /// [HttpPost] [Route("notify/tenant-id/{tenantId}")] - public virtual Task Notify2Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task Notify2Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return NotifyAsync(tenantId, mchId); } @@ -84,7 +72,7 @@ public virtual Task Notify2Async([CanBeNull] string tenantId, [Can /// [HttpPost] [Route("notify/mch-id/{mchId}")] - public virtual Task Notify3Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task Notify3Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return NotifyAsync(tenantId, mchId); } @@ -95,7 +83,7 @@ public virtual Task Notify3Async([CanBeNull] string tenantId, [Can /// [HttpPost] [Route("notify/tenant-id/{tenantId}/mch-id/{mchId}")] - public virtual Task Notify4Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task Notify4Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return NotifyAsync(tenantId, mchId); } @@ -105,7 +93,7 @@ public virtual Task Notify4Async([CanBeNull] string tenantId, [Can /// [HttpPost] [Route("refund-notify")] - public virtual async Task RefundNotifyAsync([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual async Task RefundNotifyAsync([CanBeNull] string tenantId, [CanBeNull] string mchId) { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); var requestBody = await GetPostDataAsync(); @@ -115,13 +103,8 @@ public virtual async Task RefundNotifyAsync([CanBeNull] string ten MchId = mchId, Xml = await GetPostDataAsync() }); - - if (!result.Success) - { - return BadRequest(BuildFailedXml(result.FailureReason)); - } - - return Ok(BuildSuccessXml()); + + return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } /// @@ -130,7 +113,7 @@ public virtual async Task RefundNotifyAsync([CanBeNull] string ten /// [HttpPost] [Route("refund-notify/tenant-id/{tenantId}")] - public virtual Task RefundNotify2Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task RefundNotify2Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return RefundNotifyAsync(tenantId, mchId); } @@ -141,7 +124,7 @@ public virtual Task RefundNotify2Async([CanBeNull] string tenantId /// [HttpPost] [Route("refund-notify/mch-id/{mchId}")] - public virtual Task RefundNotify3Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task RefundNotify3Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return RefundNotifyAsync(tenantId, mchId); } @@ -152,7 +135,7 @@ public virtual Task RefundNotify3Async([CanBeNull] string tenantId /// [HttpPost] [Route("refund-notify/tenant-id/{tenantId}/mch-id/{mchId}")] - public virtual Task RefundNotify4Async([CanBeNull] string tenantId, [CanBeNull] string mchId) + public virtual Task RefundNotify4Async([CanBeNull] string tenantId, [CanBeNull] string mchId) { return RefundNotifyAsync(tenantId, mchId); } @@ -165,8 +148,8 @@ public virtual Task RefundNotify4Async([CanBeNull] string tenantId /// 商户 Id [HttpGet] [Route("js-sdk-config-parameters")] - public virtual async Task GetJsSdkWeChatPayParametersAsync( - string mchId, [FromQuery] string appId, string prepayId) + public virtual async Task GetJsSdkWeChatPayParametersAsync(string mchId, + [FromQuery] string appId, string prepayId) { var result = await _clientRequestHandlingService.GetJsSdkWeChatPayParametersAsync( new GetJsSdkWeChatPayParametersInput @@ -186,20 +169,23 @@ public virtual async Task GetJsSdkWeChatPayParametersAsync( }); } - private string BuildSuccessXml() + #region > Utilities methods < + + private IActionResult NotifySuccess() { - return @" - - - "; + return Ok(new PaymentNotifyCallbackResponse + { + Code = "SUCCESS" + }); } - private string BuildFailedXml(string failedReason) + private IActionResult NotifyFailure(string message) { - return $@" - - - "; + return BadRequest(new PaymentNotifyCallbackResponse + { + Code = "FAIL", + Message = message + }); } protected virtual async Task GetPostDataAsync() @@ -214,5 +200,7 @@ protected virtual async Task GetPostDataAsync() return postData; } + + #endregion } } \ No newline at end of file From a71c96840324ea46ebee1d956e14988809e599b1 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 09:51:31 +0800 Subject: [PATCH 06/11] feat: Completed the development of the refund notification interface. --- .../{PaidNotifyInput.cs => NotifyInputDto.cs} | 4 +- .../RequestHandling/Dtos/RefundNotifyInput.cs | 13 ---- ...quest.cs => WeChatPayNotificationInput.cs} | 3 +- ...onse.cs => WeChatPayNotificationOutput.cs} | 4 +- .../IWeChatPayEventRequestHandlingService.cs | 4 +- .../Controller/WeChatPayController.cs | 40 ++++++------ .../WeChatPayEventRequestHandlingService.cs | 46 +++++++------- ...ChatPayEventRequestHandlingServiceTests.cs | 63 ------------------- 8 files changed, 53 insertions(+), 124 deletions(-) rename src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/{PaidNotifyInput.cs => NotifyInputDto.cs} (74%) delete mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/RefundNotifyInput.cs rename src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/{PaymentNotifyCallbackRequest.cs => WeChatPayNotificationInput.cs} (97%) rename src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/{PaymentNotifyCallbackResponse.cs => WeChatPayNotificationOutput.cs} (83%) delete mode 100644 tests/EasyAbp.Abp.WeChat.Pay.Tests/Infrastructure/WeChatPayEventRequestHandlingServiceTests.cs diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyInputDto.cs similarity index 74% rename from src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs rename to src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyInputDto.cs index 1a51186..282dc09 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyInputDto.cs @@ -4,13 +4,13 @@ namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; [Serializable] -public class PaidNotifyInput +public class NotifyInputDto { [CanBeNull] public string MchId { get; set; } public string RequestBodyString { get; set; } - public PaymentNotifyCallbackRequest RequestBody { get; set; } + public WeChatPayNotificationInput RequestBody { get; set; } public NotifyHttpHeaderModel HttpHeader { get; set; } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/RefundNotifyInput.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/RefundNotifyInput.cs deleted file mode 100644 index 1a5555e..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/RefundNotifyInput.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; - -[Serializable] -public class RefundNotifyInput -{ - public string MchId { get; set; } - - [Required] - public string Xml { get; set; } -} \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackRequest.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationInput.cs similarity index 97% rename from src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackRequest.cs rename to src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationInput.cs index 85372e1..1a4c2c9 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackRequest.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationInput.cs @@ -3,7 +3,8 @@ namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; -public class PaymentNotifyCallbackRequest +[Serializable] +public class WeChatPayNotificationInput { /// /// 通知 ID。 diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackResponse.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationOutput.cs similarity index 83% rename from src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackResponse.cs rename to src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationOutput.cs index 280f427..1f54e2f 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaymentNotifyCallbackResponse.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/WeChatPayNotificationOutput.cs @@ -1,8 +1,10 @@ +using System; using System.Text.Json.Serialization; namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; -public class PaymentNotifyCallbackResponse +[Serializable] +public class WeChatPayNotificationOutput { /// /// 返回状态码。 diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/IWeChatPayEventRequestHandlingService.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/IWeChatPayEventRequestHandlingService.cs index 10ab1d5..a9250f5 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/IWeChatPayEventRequestHandlingService.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/IWeChatPayEventRequestHandlingService.cs @@ -6,7 +6,7 @@ namespace EasyAbp.Abp.WeChat.Pay.RequestHandling; public interface IWeChatPayEventRequestHandlingService { - Task PaidNotifyAsync(PaidNotifyInput input); + Task PaidNotifyAsync(NotifyInputDto inputDto); - Task RefundNotifyAsync(RefundNotifyInput input); + Task RefundNotifyAsync(NotifyInputDto inputDto); } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs index 53fc457..a7a2f9c 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.HttpApi/Controller/WeChatPayController.cs @@ -40,17 +40,8 @@ public virtual async Task NotifyAsync([CanBeNull] [FromQuery] str { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); - var requestBody = await GetPostDataAsync(); - var result = await _eventRequestHandlingService.PaidNotifyAsync(new PaidNotifyInput - { - MchId = mchId, - RequestBodyString = requestBody, - RequestBody = JsonSerializer.Deserialize(requestBody), - HttpHeader = new NotifyHttpHeaderModel(Request.Headers["Wechatpay-Serial"], - Request.Headers["Wechatpay-TimeStamp"], - Request.Headers["Wechatpay-Nonce"], - Request.Headers["Wechatpay-Signature"]) - }); + var result = await _eventRequestHandlingService.PaidNotifyAsync( + BuildNotifyInputDto(await GetPostDataAsync(), mchId)); return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } @@ -96,14 +87,9 @@ public virtual Task Notify4Async([CanBeNull] string tenantId, [Ca public virtual async Task RefundNotifyAsync([CanBeNull] string tenantId, [CanBeNull] string mchId) { using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId!)); - var requestBody = await GetPostDataAsync(); - var result = await _eventRequestHandlingService.RefundNotifyAsync(new RefundNotifyInput - { - MchId = mchId, - Xml = await GetPostDataAsync() - }); - + var result = await _eventRequestHandlingService.RefundNotifyAsync(BuildNotifyInputDto(await GetPostDataAsync(), mchId)); + return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } @@ -173,7 +159,7 @@ public virtual async Task GetJsSdkWeChatPayParametersAsync(string private IActionResult NotifySuccess() { - return Ok(new PaymentNotifyCallbackResponse + return Ok(new WeChatPayNotificationOutput { Code = "SUCCESS" }); @@ -181,13 +167,27 @@ private IActionResult NotifySuccess() private IActionResult NotifyFailure(string message) { - return BadRequest(new PaymentNotifyCallbackResponse + return BadRequest(new WeChatPayNotificationOutput { Code = "FAIL", Message = message }); } + private NotifyInputDto BuildNotifyInputDto(string requestBody, string mchId) + { + return new NotifyInputDto + { + MchId = mchId, + RequestBodyString = requestBody, + RequestBody = JsonSerializer.Deserialize(requestBody), + HttpHeader = new NotifyHttpHeaderModel(Request.Headers["Wechatpay-Serial"], + Request.Headers["Wechatpay-TimeStamp"], + Request.Headers["Wechatpay-Nonce"], + Request.Headers["Wechatpay-Signature"]) + }; + } + protected virtual async Task GetPostDataAsync() { Request.EnableBuffering(); diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs index c803b4d..944c6e9 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/RequestHandling/WeChatPayEventRequestHandlingService.cs @@ -28,18 +28,18 @@ public WeChatPayEventRequestHandlingService( _platformCertificateManager = platformCertificateManager; } - public virtual async Task PaidNotifyAsync(PaidNotifyInput input) + public virtual async Task PaidNotifyAsync(NotifyInputDto input) { var options = await _optionsProvider.GetAsync(input.MchId); - var handlers = LazyServiceProvider.LazyGetService>() - .Where(h => h.Type == WeChatHandlerType.Paid); - if (!await IsSignValidAsync(input, options)) { return new WeChatRequestHandlingResult(false, "签名验证不通过"); } + var handlers = LazyServiceProvider.LazyGetService>() + .Where(h => h.Type == WeChatHandlerType.Paid); + var decryptingResult = DecryptResource(input, options); var model = new WeChatPayEventModel @@ -61,24 +61,26 @@ public virtual async Task PaidNotifyAsync(PaidNotif return new WeChatRequestHandlingResult(true); } - public virtual async Task RefundNotifyAsync(RefundNotifyInput input) + public virtual async Task RefundNotifyAsync(NotifyInputDto input) { var options = await _optionsProvider.GetAsync(input.MchId); + if (!await IsSignValidAsync(input, options)) + { + return new WeChatRequestHandlingResult(false, "签名验证不通过"); + } + var handlers = LazyServiceProvider.LazyGetService>() .Where(x => x.Type == WeChatHandlerType.Refund); - // if (!decryptingResult) - // { - // return new WeChatRequestHandlingResult(false, "微信消息体解码失败"); - // } - - var model = new WeChatPayEventModel + var decryptingResult = DecryptResource(input, options); + var model = new WeChatPayEventModel { - Options = options + Options = options, + Resource = decryptingResult }; - foreach (var handler in handlers) + foreach (var handler in handlers.Where(x => x.Type == WeChatHandlerType.Refund)) { var result = await handler.HandleAsync(model); @@ -91,20 +93,20 @@ public virtual async Task RefundNotifyAsync(RefundN return new WeChatRequestHandlingResult(true); } - protected virtual async Task IsSignValidAsync(PaidNotifyInput input, AbpWeChatPayOptions options) + protected virtual async Task IsSignValidAsync(NotifyInputDto inputDto, AbpWeChatPayOptions options) { - var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(options.MchId, input.HttpHeader.SerialNumber); + var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(options.MchId, inputDto.HttpHeader.SerialNumber); var sb = new StringBuilder(); - sb.Append(input.HttpHeader.Timestamp).Append("\n") - .Append(input.HttpHeader.Nonce).Append("\n") - .Append(input.RequestBodyString).Append("\n"); - return certificate.VerifySignature(sb.ToString(), input.HttpHeader.Signature); + sb.Append(inputDto.HttpHeader.Timestamp).Append("\n") + .Append(inputDto.HttpHeader.Nonce).Append("\n") + .Append(inputDto.RequestBodyString).Append("\n"); + return certificate.VerifySignature(sb.ToString(), inputDto.HttpHeader.Signature); } - protected virtual TObject DecryptResource(PaidNotifyInput input, AbpWeChatPayOptions options) + protected virtual TObject DecryptResource(NotifyInputDto inputDto, AbpWeChatPayOptions options) { - var sourceJson = WeChatPaySecurityUtility.AesGcmDecrypt(options.ApiV3Key, input.RequestBody.Resource.AssociatedData, - input.RequestBody.Resource.Nonce, input.RequestBody.Resource.Ciphertext); + var sourceJson = WeChatPaySecurityUtility.AesGcmDecrypt(options.ApiV3Key, inputDto.RequestBody.Resource.AssociatedData, + inputDto.RequestBody.Resource.Nonce, inputDto.RequestBody.Resource.Ciphertext); return JsonConvert.DeserializeObject(sourceJson); } } \ No newline at end of file diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Infrastructure/WeChatPayEventRequestHandlingServiceTests.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Infrastructure/WeChatPayEventRequestHandlingServiceTests.cs deleted file mode 100644 index fe61fef..0000000 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Infrastructure/WeChatPayEventRequestHandlingServiceTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Threading.Tasks; -using EasyAbp.Abp.WeChat.Pay.RequestHandling; -using EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; -using Newtonsoft.Json; -using Shouldly; -using Xunit; - -namespace EasyAbp.Abp.WeChat.Pay.Tests.Infrastructure; - -public class WeChatPayEventRequestHandlingServiceTests : AbpWeChatPayTestBase -{ - protected readonly IWeChatPayEventRequestHandlingService Service; - - public WeChatPayEventRequestHandlingServiceTests() - { - Service = GetRequiredService(); - } - - [Fact] - public async Task Should_Handle_Refund() - { - const string xml = @" -SUCCESS - - - - -"; - - var result = await Service.RefundNotifyAsync(new RefundNotifyInput - { - MchId = "10000100", - Xml = xml - }); - - result.Success.ShouldBeTrue(); - } - - [Fact] - public async Task Should_Handle_Paid() - { - var json = """ - { - "id": "EV-2018022511223320873", - "create_time": "2015-05-20T13:29:35+08:00", - "resource_type": "encrypt-resource", - "event_type": "TRANSACTION.SUCCESS", - "summary": "支付成功", - "resource": { - "original_type": "transaction", - "algorithm": "AEAD_AES_256_GCM", - "ciphertext": "", - "associated_data": "", - "nonce": "" - } - } - """; - - // var result = await Service.PaidNotifyAsync(JsonConvert.DeserializeObject(json), null); - - // result.Success.ShouldBe(true); - } -} \ No newline at end of file From 9143a86af4fbf90e2f2408c7ae6e5dafd0a672d0 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 10:12:42 +0800 Subject: [PATCH 07/11] fix: Added functionality to ignore parameters. --- .../Extensions/WeChatReflectionHelper.cs | 3 +++ .../JSPayment/Models/QueryOrderByOutTradeNumberRequest.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Common/EasyAbp.Abp.WeChat.Common/Extensions/WeChatReflectionHelper.cs b/src/Common/EasyAbp.Abp.WeChat.Common/Extensions/WeChatReflectionHelper.cs index 3fcb512..ce820a5 100644 --- a/src/Common/EasyAbp.Abp.WeChat.Common/Extensions/WeChatReflectionHelper.cs +++ b/src/Common/EasyAbp.Abp.WeChat.Common/Extensions/WeChatReflectionHelper.cs @@ -19,6 +19,9 @@ public static string ConvertToQueryString(object obj) continue; } + var ignoreAttribute = propertyInfo.GetCustomAttribute(); + if(ignoreAttribute != null) continue; + var jsonPropertyAttribute = propertyInfo.GetCustomAttribute(); var name = jsonPropertyAttribute?.PropertyName ?? propertyInfo.Name; var type = propertyInfo.PropertyType; diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByOutTradeNumberRequest.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByOutTradeNumberRequest.cs index 0ac8375..e5db5ab 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByOutTradeNumberRequest.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByOutTradeNumberRequest.cs @@ -27,5 +27,6 @@ public class QueryOrderByOutTradeNumberRequest [JsonProperty("out_trade_no")] [Required] [StringLength(32, MinimumLength = 6)] + [JsonIgnore] public string OutTradeNo { get; set; } } \ No newline at end of file From cff66c15c6cf0743786d722d34968c40f62fab52 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 10:13:08 +0800 Subject: [PATCH 08/11] fix: Fixed an issue with incorrect signature for GET requests with parameters. --- .../ApiRequests/WeChatPayApiRequestModel.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs index 8359054..76ca24c 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs @@ -23,10 +23,18 @@ public WeChatPayApiRequestModel(HttpMethod method, string randomString) { Method = method; - Url = url; - Body = body; Timestamp = timestamp; RandomString = randomString; + if (method == HttpMethod.Get) + { + Body = null; + Url = $"{url}?{body}"; + } + else + { + Url = url; + Body = body; + } } public string GetPendingSignatureString() From 7612eb250c09993f34ffcefccff7e0ba9ee1fff4 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 10:26:40 +0800 Subject: [PATCH 09/11] fix: Fixed the issue with incorrect signature when there are no parameters. --- .../ApiRequests/WeChatPayApiRequestModel.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs index 76ca24c..fe92e9a 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs @@ -25,16 +25,16 @@ public WeChatPayApiRequestModel(HttpMethod method, Method = method; Timestamp = timestamp; RandomString = randomString; - if (method == HttpMethod.Get) + Url = url; + Body = body; + + if (method != HttpMethod.Get) return; + + Body = null; + if (!string.IsNullOrEmpty(body)) { - Body = null; Url = $"{url}?{body}"; } - else - { - Url = url; - Body = body; - } } public string GetPendingSignatureString() From 0295c0277dcd43b950bafa6a27ea74faed3aa13f Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 10:27:28 +0800 Subject: [PATCH 10/11] fix: Fixed the issue where QueryOrderByWechatNumber was returning a 404 error. --- .../ApiRequests/DefaultWeChatPayApiRequester.cs | 7 +++++++ .../Services/BasicPayment/JSPayment/JsPaymentService.cs | 2 +- .../JSPayment/Models/QueryOrderByWechatNumberRequest.cs | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs index 1c41913..daf2476 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs @@ -1,8 +1,10 @@ +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using EasyAbp.Abp.WeChat.Common.Extensions; +using EasyAbp.Abp.WeChat.Pay.Exceptions; using EasyAbp.Abp.WeChat.Pay.Options; using EasyAbp.Abp.WeChat.Pay.Security; using Newtonsoft.Json; @@ -115,6 +117,11 @@ private string HandleRequestObject(HttpMethod method, object body) protected virtual async Task ValidateResponseAsync(HttpResponseMessage responseMessage) { + if (responseMessage.StatusCode != HttpStatusCode.OK) + { + throw new CallWeChatPayApiException("微信支付 API 调用失败,状态码为非 200。"); + } + await Task.CompletedTask; } } diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/JsPaymentService.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/JsPaymentService.cs index 0e5cba5..556e2d7 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/JsPaymentService.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/JsPaymentService.cs @@ -13,7 +13,7 @@ namespace EasyAbp.Abp.WeChat.Pay.Services.BasicPayment.JSPayment; public class JsPaymentService : WeChatPayServiceBase { public const string CreateOrderUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; - public const string QueryOrderByWechatNumberUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/{transaction_id}"; + public const string QueryOrderByWechatNumberUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id}"; public const string QueryOrderByOutTradeNumberUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}"; public const string CloseOrderUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close"; public const string RefundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByWechatNumberRequest.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByWechatNumberRequest.cs index 144b55a..0eaf358 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByWechatNumberRequest.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/JSPayment/Models/QueryOrderByWechatNumberRequest.cs @@ -27,5 +27,6 @@ public class QueryOrderByWechatNumberRequest [JsonProperty("transaction_id")] [Required] [StringLength(32, MinimumLength = 1)] + [JsonIgnore] public string TransactionId { get; set; } } \ No newline at end of file From 34dc4647d9e4a6b213d7e0981642e2a41cfd32a7 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 11:46:14 +0800 Subject: [PATCH 11/11] test: Added missing test code for JS API payment. --- .../DefaultWeChatPayApiRequester.cs | 15 +- .../BasicPayment/Models/CloseOrderRequest.cs | 1 + .../Models/QueryRefundOrderRequest.cs | 1 + .../AbpWeChatPayTestConsts.cs | 1 + .../Services/BasicPaymentServiceTests.cs | 154 +++++++++++++++++- 5 files changed, 163 insertions(+), 9 deletions(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs index daf2476..b8c5ecd 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs @@ -115,14 +115,19 @@ private string HandleRequestObject(HttpMethod method, object body) return WeChatReflectionHelper.ConvertToQueryString(body); } - protected virtual async Task ValidateResponseAsync(HttpResponseMessage responseMessage) + protected virtual Task ValidateResponseAsync(HttpResponseMessage responseMessage) { - if (responseMessage.StatusCode != HttpStatusCode.OK) + switch (responseMessage.StatusCode) { - throw new CallWeChatPayApiException("微信支付 API 调用失败,状态码为非 200。"); + case HttpStatusCode.OK: + return Task.CompletedTask; + case HttpStatusCode.Accepted: + return Task.CompletedTask; + case HttpStatusCode.NoContent: + return Task.CompletedTask; + default: + throw new CallWeChatPayApiException("微信支付 API 调用失败,状态码为非 200。"); } - - await Task.CompletedTask; } } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/CloseOrderRequest.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/CloseOrderRequest.cs index b3fc7bd..cdd970b 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/CloseOrderRequest.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/CloseOrderRequest.cs @@ -31,5 +31,6 @@ public class CloseOrderRequest [Required] [StringLength(32,MinimumLength = 6)] [JsonProperty("out_trade_no")] + [JsonIgnore] public string OutTradeNo { get; set; } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryRefundOrderRequest.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryRefundOrderRequest.cs index f762290..d22d421 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryRefundOrderRequest.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Services/BasicPayment/Models/QueryRefundOrderRequest.cs @@ -17,5 +17,6 @@ public class QueryRefundOrderRequest [Required] [StringLength(64, MinimumLength = 1)] [JsonProperty("out_refund_no")] + [JsonIgnore] public string OutRefundNo { get; set; } } \ No newline at end of file diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs index 5bab53b..1798456 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs @@ -10,5 +10,6 @@ public class AbpWeChatPayTestConsts public const string OpenId = ""; public const string NotifyUrl = ""; public const string SerialNo = ""; + public const string RefundNotifyUrl = ""; } } \ No newline at end of file diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Services/BasicPaymentServiceTests.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Services/BasicPaymentServiceTests.cs index 03a737c..9d8a2c5 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Services/BasicPaymentServiceTests.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Services/BasicPaymentServiceTests.cs @@ -24,9 +24,7 @@ public async Task CreateOrderAsync_Test() { // Arrange var service = await _weChatPayServiceFactory.CreateAsync(); - - // Act - var response = await service.CreateOrderAsync(new CreateOrderRequest + var request = new CreateOrderRequest { MchId = service.MchId, OutTradeNo = RandomStringHelper.GetRandomString(), @@ -42,10 +40,158 @@ public async Task CreateOrderAsync_Test() { OpenId = AbpWeChatPayTestConsts.OpenId // 请替换为测试用户的 OpenId,具体 Id 可以在微信公众号平台-用户管理进行查看。 } - }); + }; + + // Act + var response = await service.CreateOrderAsync(request); // Assert response.ShouldNotBeNull(); response.PrepayId.ShouldNotBeNullOrEmpty(); } + + [Fact] + public async Task QueryOrderByOutTradeNumberAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.QueryOrderByOutTradeNumberAsync(new QueryOrderByOutTradeNumberRequest + { + MchId = AbpWeChatPayTestConsts.MchId, + OutTradeNo = "5dmsi17l83n34fku5z49phcpwa9kpz" + }); + + // Assert + response.ShouldNotBeNull(); + response.TradeState.ShouldBe("SUCCESS"); + } + + [Fact] + public async Task QueryOrderByTransactionIdAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.QueryOrderByWechatNumberAsync(new QueryOrderByWechatNumberRequest + { + MchId = AbpWeChatPayTestConsts.MchId, + TransactionId = "4200002055202401099853138759" + }); + + // Assert + response.ShouldNotBeNull(); + response.TradeState.ShouldBe("SUCCESS"); + } + + [Fact] + public async Task CloseOrderAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.CloseOrderAsync(new CloseOrderRequest + { + MchId = AbpWeChatPayTestConsts.MchId, + OutTradeNo = "1ne2k7qitdr78k9zytjpz0tm7qfg8p" + }); + + // Assert + response.ShouldBeNull(); + } + + [Fact] + public async Task RefundAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + var request = new RefundOrderRequest + { + OutRefundNo = RandomStringHelper.GetRandomString(), + OutTradeNo = "kel9xerwcjib2zs8eixyazuis3qsmo", + NotifyUrl = AbpWeChatPayTestConsts.RefundNotifyUrl, + Amount = new RefundOrderRequest.AmountInfo + { + Refund = 1, + Total = 1, + Currency = "CNY" + } + }; + + // Act + var response = await service.RefundAsync(request); + + // Assert + response.ShouldNotBeNull(); + response.RefundId.ShouldNotBeNullOrEmpty(); + } + + [Fact] + public async Task QueryRefundOrderAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.QueryRefundOrderAsync(new QueryRefundOrderRequest + { + OutRefundNo = "r8z61t50kwbg9l1l5ay9s7i8qjyc89" + }); + + // Assert + response.ShouldNotBeNull(); + } + + [Fact] + public async Task GetTransactionBillAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.GetTransactionBillAsync(new GetTransactionBillRequest + { + BillDate = "2024-01-09" + }); + + // Assert + response.ShouldNotBeNull(); + } + + [Fact] + public async Task GetFundFlowBillAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + + // Act + var response = await service.GetFundFlowBillAsync(new GetFundFlowBillRequest + { + BillDate = "2024-01-09" + }); + + // Assert + response.ShouldNotBeNull(); + } + + [Fact] + public async Task DownloadBillFileAsync_Test() + { + // Arrange + var service = await _weChatPayServiceFactory.CreateAsync(); + var billResponse = await service.GetTransactionBillAsync(new GetTransactionBillRequest + { + BillDate = "2024-01-09" + }); + + // Act + var response = await service.DownloadBillFileAsync(billResponse.DownloadUrl); + + // Assert + response.ShouldNotBeNull(); + response.Length.ShouldBeGreaterThan(0); + } } \ No newline at end of file