From 40159fd074c3f89e79890a479d99be8bf61a7304 Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 21 Oct 2021 13:35:59 +0300 Subject: [PATCH] Add WebHook handling to payment module --- .../IPaymentRequestAppService.cs | 2 + .../PaymentRequestAppService.cs | 37 +++++++++++++++++-- .../ClientProxies/Payment-generate-proxy.json | 37 +++++++++++++++++++ .../PaymentRequestClientProxy.Generated.cs | 8 ++++ .../PaymentRequestController.cs | 14 +++++++ 5 files changed, 94 insertions(+), 4 deletions(-) diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs index 4cf5295..05fd627 100644 --- a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -13,5 +13,7 @@ namespace Payment.PaymentRequests Task StartPaymentAsync(StartPaymentDto input); Task CompleteAsync(string token); + + Task HandleWebhookAsync(string payload); } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs b/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs index 8b89f55..0c0c89e 100644 --- a/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; using Payment.PayPal; using PayPalCheckoutSdk.Core; using PayPalCheckoutSdk.Orders; @@ -28,7 +29,7 @@ namespace Payment.PaymentRequests public async Task GetAsync(Guid id) { var paymentRequest = await _paymentRequestRepository.GetAsync(id); - + return ObjectMapper.Map(paymentRequest); } @@ -114,11 +115,38 @@ namespace Payment.PaymentRequests var order = (await _payPalHttpClient.Execute(request)).Result(); + var paymentRequest = await UpdatePaymentRequestStateAsync(order); + + return ObjectMapper.Map(paymentRequest); + } + + public async Task HandleWebhookAsync(string payload) + { + var jObject = JObject.Parse(payload); + + //// TODO: Find way to parse with System.Text.Json instead of Newtonsoft + 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(); + + await UpdatePaymentRequestStateAsync(order); + + // PayPal doesn't accept Http 204 (NoContent) result and tries to execute webhook again. + // So with following value, API returns Http 200 (OK) result. + return true; + } + + private async Task UpdatePaymentRequestStateAsync(Order order) + { var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestId); - if (order.Status == PayPalConsts.OrderStatus.Approved || order.Status == PayPalConsts.OrderStatus.Completed) + if (order.Status == PayPalConsts.OrderStatus.Completed || order.Status == PayPalConsts.OrderStatus.Approved) { paymentRequest.SetAsCompleted(); } @@ -127,11 +155,12 @@ namespace Payment.PaymentRequests paymentRequest.SetAsFailed(order.Status); } - paymentRequest.ExtraProperties.Add(PayPalConsts.OrderIdPropertyName, order.Id); + paymentRequest.ExtraProperties[PayPalConsts.OrderIdPropertyName] = order.Id; + paymentRequest.ExtraProperties[nameof(order.Status)] = order.Status; await _paymentRequestRepository.UpdateAsync(paymentRequest); - return ObjectMapper.Map(paymentRequest); + return paymentRequest; } } } \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json index 6681ec8..b95c9be 100644 --- a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json @@ -161,6 +161,43 @@ }, "allowAnonymous": null, "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + }, + "HandleWebhookAsyncByPayload": { + "uniqueName": "HandleWebhookAsyncByPayload", + "name": "HandleWebhookAsync", + "httpMethod": "POST", + "url": "api/payment/requests/webhook", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "payload", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "payload", + "name": "payload", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "System.Boolean", + "typeSimple": "boolean" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" } } } diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs index 0b336a6..ea9f8fa 100644 --- a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs @@ -46,5 +46,13 @@ namespace Payment.PaymentRequests.ClientProxies { typeof(StartPaymentDto), input } }); } + + public virtual async Task HandleWebhookAsync(string payload) + { + return await RequestAsync(nameof(HandleWebhookAsync), new ClientProxyRequestTypeValue + { + { typeof(string), payload } + }); + } } } diff --git a/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs b/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs index 3ba9d76..06e9972 100644 --- a/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs +++ b/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs @@ -1,5 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; using System; +using System.IO; +using System.Text; using System.Threading.Tasks; using Volo.Abp; @@ -43,5 +46,16 @@ namespace Payment.PaymentRequests { return PaymentRequestAppService.StartPaymentAsync(input); } + + [ApiExplorerSettings(IgnoreApi = true)] + [HttpPost] + [Route("webhook")] + public async Task HandleWebhookAsync(string payload) + { + var bytes = await Request.Body.GetAllBytesAsync(); + payload = Encoding.UTF8.GetString(bytes); + + return await PaymentRequestAppService.HandleWebhookAsync(payload); + } } } \ No newline at end of file