From 96010fea3e380f6a96a5bc3f8c141d47fbeeaa58 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 17 Jan 2022 15:13:54 +0300 Subject: [PATCH] Move handling webhooks to PaymentMethod --- .../PaymentMethods/PaymentTypeIds.cs | 12 +++++++ .../PaymentMethods/PaymentTypes.cs | 12 +++++++ .../IPaymentRequestAppService.cs | 6 ++-- .../PaymentMethods/DemoPaymentMethod.cs | 13 +++++--- .../PaymentMethods/IPaymentMethod.cs | 8 ++++- .../PaymentMethods/PaymentMethodResolver.cs | 13 ++++++++ .../PaymentMethods/PaypalMethod.cs | 22 +++++++++++-- .../PaymentRequestAppService.cs | 32 +++++-------------- .../Controllers/PaymentRequestController.cs | 14 ++++---- 9 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypeIds.cs create mode 100644 services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypes.cs diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypeIds.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypeIds.cs new file mode 100644 index 00000000..ef23e5fb --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypeIds.cs @@ -0,0 +1,12 @@ +namespace EShopOnAbp.PaymentService.PaymentMethods; + +public static class PaymentTypeIds +{ + /* + * PaymentTypeIds must be unique. + */ + + public const int Demo = 0; + + public const int PayPal = 1; +} diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypes.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypes.cs new file mode 100644 index 00000000..8e3a351c --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentMethods/PaymentTypes.cs @@ -0,0 +1,12 @@ +namespace EShopOnAbp.PaymentService.PaymentMethods; + +public static class PaymentTypes +{ + /* + * PaymentTypes must be unique. + * It's identifier of a PaymentMethod. + */ + public const string Demo = "demo"; + + public const string PayPal = "paypal"; +} diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs index 366c3413..6b6f1c0f 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -7,10 +7,10 @@ namespace EShopOnAbp.PaymentService.PaymentRequests { Task CreateAsync(PaymentRequestCreationDto input); - Task StartAsync(PaymentRequestStartDto input); + Task StartAsync(string paymentType, PaymentRequestStartDto input); - Task CompleteAsync(PaymentRequestCompleteInputDto input); + Task CompleteAsync(string paymentType, PaymentRequestCompleteInputDto input); - Task HandleWebhookAsync(string payload); + Task HandleWebhookAsync(string paymentType, string payload); } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/DemoPaymentMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/DemoPaymentMethod.cs index 131e1f9c..9d0fb8ea 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/DemoPaymentMethod.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/DemoPaymentMethod.cs @@ -8,12 +8,10 @@ namespace EShopOnAbp.PaymentService.PaymentMethods; [ExposeServices(typeof(IPaymentMethod), typeof(DemoPaymentMethod))] public class DemoPaymentMethod : IPaymentMethod { - public int PaymentTypeId { get; } + public int PaymentTypeId => PaymentTypeIds.Demo; + + public string PaymentType => PaymentTypes.Demo; - public DemoPaymentMethod() - { - PaymentTypeId = 0; - } public Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input) { @@ -31,4 +29,9 @@ public class DemoPaymentMethod : IPaymentMethod return await paymentRequestRepository.UpdateAsync(paymentRequest); } + + public Task HandleWebhookAsync(string payload) + { + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/IPaymentMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/IPaymentMethod.cs index d38285ef..f6a14905 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/IPaymentMethod.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/IPaymentMethod.cs @@ -6,7 +6,13 @@ namespace EShopOnAbp.PaymentService.PaymentMethods; public interface IPaymentMethod : ITransientDependency { - public int PaymentTypeId { get; } + int PaymentTypeId { get; } + + string PaymentType { get; } + public Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input); + public Task CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token); + + public Task HandleWebhookAsync(string payload); } \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaymentMethodResolver.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaymentMethodResolver.cs index 8ec3b072..164435f3 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaymentMethodResolver.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaymentMethodResolver.cs @@ -28,4 +28,17 @@ public class PaymentMethodResolver : ITransientDependency return paymentMethod; } + + public IPaymentMethod Resolve(string paymentType) + { + var paymentMethod = _paymentMethods.FirstOrDefault(q => q.PaymentType == paymentType); + if (paymentMethod == null) + { + _logger.LogError($"Couldn't find Payment method with type:{paymentType}"); + throw new ArgumentException("Payment method not found", paymentType); + } + + return paymentMethod; + } + } \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaypalMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaypalMethod.cs index c584c872..24240839 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaypalMethod.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentMethods/PaypalMethod.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using EShopOnAbp.PaymentService.PaymentRequests; +using Newtonsoft.Json.Linq; using PayPalCheckoutSdk.Core; using PayPalCheckoutSdk.Orders; using Volo.Abp.DependencyInjection; @@ -15,13 +16,13 @@ public class PaypalMethod : IPaymentMethod { private readonly PayPalHttpClient _payPalHttpClient; private readonly PaymentRequestDomainService _paymentRequestDomainService; - public int PaymentTypeId { get; } + public int PaymentTypeId => PaymentTypeIds.PayPal; + public string PaymentType => PaymentTypes.PayPal; public PaypalMethod(PayPalHttpClient payPalHttpClient, PaymentRequestDomainService paymentRequestDomainService) { _payPalHttpClient = payPalHttpClient; _paymentRequestDomainService = paymentRequestDomainService; - PaymentTypeId = 1; } public async Task StartAsync(PaymentRequest paymentRequest, @@ -95,4 +96,21 @@ public class PaypalMethod : IPaymentMethod return await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status, order.Id); } + + public async Task HandleWebhookAsync(string payload) + { + // TODO: Find better way to parse. + var jObject = JObject.Parse(payload); + + var order = jObject["resource"].ToObject(); + + var request = new OrdersGetRequest(order.Id); + + // Ensure order object comes from PayPal + var response = await _payPalHttpClient.Execute(request); + order = response.Result(); + + var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); + await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status, order.Id); + } } \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs index 59058e3c..f4b44cbc 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,10 +1,6 @@ -using Newtonsoft.Json.Linq; -using PayPalCheckoutSdk.Core; -using PayPalCheckoutSdk.Orders; -using System; +using EShopOnAbp.PaymentService.PaymentMethods; using System.Linq; using System.Threading.Tasks; -using EShopOnAbp.PaymentService.PaymentMethods; namespace EShopOnAbp.PaymentService.PaymentRequests { @@ -13,16 +9,13 @@ namespace EShopOnAbp.PaymentService.PaymentRequests private readonly PaymentMethodResolver _paymentMethodResolver; private readonly PaymentRequestDomainService _paymentRequestDomainService; protected IPaymentRequestRepository PaymentRequestRepository { get; } - protected PayPalHttpClient PayPalHttpClient { get; } public PaymentRequestAppService( IPaymentRequestRepository paymentRequestRepository, - PayPalHttpClient payPalHttpClient, PaymentMethodResolver paymentMethodResolver, PaymentRequestDomainService paymentRequestDomainService) { PaymentRequestRepository = paymentRequestRepository; - PayPalHttpClient = payPalHttpClient; _paymentMethodResolver = paymentMethodResolver; _paymentRequestDomainService = paymentRequestDomainService; } @@ -51,37 +44,28 @@ namespace EShopOnAbp.PaymentService.PaymentRequests return ObjectMapper.Map(paymentRequest); } - public virtual async Task StartAsync(PaymentRequestStartDto input) + public virtual async Task StartAsync(string paymentType, PaymentRequestStartDto input) { PaymentRequest paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId, includeDetails: true); - var paymentService = _paymentMethodResolver.Resolve(input.PaymentTypeId); + var paymentService = _paymentMethodResolver.Resolve(paymentType); return await paymentService.StartAsync(paymentRequest, input); } - public virtual async Task CompleteAsync(PaymentRequestCompleteInputDto input) + public virtual async Task CompleteAsync(string paymentType, PaymentRequestCompleteInputDto input) { - var paymentService = _paymentMethodResolver.Resolve(input.PaymentTypeId); + var paymentService = _paymentMethodResolver.Resolve(paymentType); var paymentRequest = await paymentService.CompleteAsync(PaymentRequestRepository, input.Token); return ObjectMapper.Map(paymentRequest); } - public virtual async Task HandleWebhookAsync(string payload) + public virtual async Task HandleWebhookAsync(string paymentType, string payload) { - var jObject = JObject.Parse(payload); + var paymentService = _paymentMethodResolver.Resolve(paymentType); - var order = jObject["resource"].ToObject(); - - var request = new OrdersGetRequest(order.Id); - - // Ensure order object comes from PayPal - var response = await PayPalHttpClient.Execute(request); - order = response.Result(); - - var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); - await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status, order.Id); + await paymentService.HandleWebhookAsync(payload); // PayPal doesn't accept Http 204 (NoContent) result and tries to execute webhook again. // So with following value, API returns Http 200 (OK) result. diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs index 4c73bf1c..477d9187 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs @@ -20,9 +20,9 @@ namespace EShopOnAbp.PaymentService.Controllers } [HttpPost("complete")] - public Task CompleteAsync(PaymentRequestCompleteInputDto input) + public Task CompleteAsync(string paymentType, PaymentRequestCompleteInputDto input) { - return PaymentRequestAppService.CompleteAsync(input); + return PaymentRequestAppService.CompleteAsync(paymentType, input); } [HttpPost] @@ -32,19 +32,19 @@ namespace EShopOnAbp.PaymentService.Controllers } [HttpPost] - [Route("webhook")] - public async Task HandleWebhookAsync(string payload) + [Route("{paymentType}/webhook")] + public async Task HandleWebhookAsync(string paymentType, string payload) { var bytes = await Request.Body.GetAllBytesAsync(); payload = Encoding.UTF8.GetString(bytes); - return await PaymentRequestAppService.HandleWebhookAsync(payload); + return await PaymentRequestAppService.HandleWebhookAsync(paymentType, payload); } [HttpPost("start")] - public Task StartAsync(PaymentRequestStartDto input) + public Task StartAsync(string paymentType, PaymentRequestStartDto input) { - return PaymentRequestAppService.StartAsync(input); + return PaymentRequestAppService.StartAsync(paymentType, input); } } } \ No newline at end of file