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.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/NotifyInputDto.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyInputDto.cs new file mode 100644 index 0000000..282dc09 --- /dev/null +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/NotifyInputDto.cs @@ -0,0 +1,16 @@ +using System; +using JetBrains.Annotations; + +namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; + +[Serializable] +public class NotifyInputDto +{ + [CanBeNull] public string MchId { get; set; } + + public string RequestBodyString { 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/PaidNotifyInput.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs deleted file mode 100644 index 96ca8df..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay.Abstractions/RequestHandling/Dtos/PaidNotifyInput.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace EasyAbp.Abp.WeChat.Pay.RequestHandling.Dtos; - -[Serializable] -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; } -} \ 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 0fef758..a7a2f9c 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; @@ -35,33 +35,15 @@ public class WeChatPayController : AbpControllerBase /// [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!)); - 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( + BuildNotifyInputDto(await GetPostDataAsync(), mchId)); + + return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } /// @@ -70,7 +52,7 @@ public class WeChatPayController : AbpControllerBase /// [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); } @@ -81,7 +63,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); } @@ -92,7 +74,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); } @@ -102,22 +84,13 @@ 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 result = await _eventRequestHandlingService.RefundNotifyAsync(new RefundNotifyInput - { - MchId = mchId, - Xml = await GetPostDataAsync() - }); + var result = await _eventRequestHandlingService.RefundNotifyAsync(BuildNotifyInputDto(await GetPostDataAsync(), mchId)); - if (!result.Success) - { - return BadRequest(BuildFailedXml(result.FailureReason)); - } - - return Ok(BuildSuccessXml()); + return !result.Success ? NotifyFailure(result.FailureReason) : NotifySuccess(); } /// @@ -126,7 +99,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); } @@ -137,7 +110,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); } @@ -148,7 +121,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); } @@ -161,8 +134,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 @@ -182,20 +155,37 @@ public virtual Task RefundNotify4Async([CanBeNull] string tenantId }); } - private string BuildSuccessXml() + #region > Utilities methods < + + private IActionResult NotifySuccess() { - return @" - - - "; + return Ok(new WeChatPayNotificationOutput + { + Code = "SUCCESS" + }); } - private string BuildFailedXml(string failedReason) + private IActionResult NotifyFailure(string message) { - return $@" - - - "; + 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() @@ -210,5 +200,7 @@ protected virtual async Task GetPostDataAsync() return postData; } + + #endregion } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs index 1c41913..b8c5ecd 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; @@ -113,9 +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) { - await Task.CompletedTask; + switch (responseMessage.StatusCode) + { + case HttpStatusCode.OK: + return Task.CompletedTask; + case HttpStatusCode.Accepted: + return Task.CompletedTask; + case HttpStatusCode.NoContent: + return Task.CompletedTask; + default: + throw new CallWeChatPayApiException("微信支付 API 调用失败,状态码为非 200。"); + } } } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/WeChatPayApiRequestModel.cs index 8359054..fe92e9a 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 class WeChatPayApiRequestModel string randomString) { Method = method; - Url = url; - Body = body; Timestamp = timestamp; RandomString = randomString; + Url = url; + Body = body; + + if (method != HttpMethod.Get) return; + + Body = null; + if (!string.IsNullOrEmpty(body)) + { + Url = $"{url}?{body}"; + } } public string GetPendingSignatureString() 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..944c6e9 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; @@ -25,21 +28,24 @@ public class WeChatPayEventRequestHandlingService : IWeChatPayEventRequestHandli _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 model = new WeChatPayEventModel + var handlers = LazyServiceProvider.LazyGetService>() + .Where(h => h.Type == WeChatHandlerType.Paid); + + 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)) @@ -55,26 +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); - // var (decryptingResult, decryptedXmlDocument) = await _xmlDecrypter.TryDecryptAsync(xmlDocument, options); - - // 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); @@ -87,13 +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.SerialNumber); + var certificate = await _platformCertificateManager.GetPlatformCertificateAsync(options.MchId, inputDto.HttpHeader.SerialNumber); var sb = new StringBuilder(); - sb.Append(input.Timestamp).Append("\n") - .Append(input.Nonce).Append("\n") - .Append(input.RequestBodyString).Append("\n"); - return certificate.VerifySignature(sb.ToString(), input.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(NotifyInputDto inputDto, AbpWeChatPayOptions options) + { + 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/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) 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/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 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 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/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/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 f03717f..1798456 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/AbpWeChatPayTestConsts.cs @@ -9,5 +9,7 @@ public class AbpWeChatPayTestConsts public const string AppId = ""; 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/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 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 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