Browse Source

Add WebHook handling to payment module

pull/69/head
enisn 5 years ago
parent
commit
40159fd074
  1. 2
      modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs
  2. 37
      modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs
  3. 37
      modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json
  4. 8
      modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs
  5. 14
      modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs

2
modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs

@ -13,5 +13,7 @@ namespace Payment.PaymentRequests
Task<StartPaymentResultDto> StartPaymentAsync(StartPaymentDto input);
Task<PaymentRequestDto> CompleteAsync(string token);
Task<bool> HandleWebhookAsync(string payload);
}
}

37
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<PaymentRequestDto> GetAsync(Guid id)
{
var paymentRequest = await _paymentRequestRepository.GetAsync(id);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
@ -114,11 +115,38 @@ namespace Payment.PaymentRequests
var order = (await _payPalHttpClient.Execute(request)).Result<Order>();
var paymentRequest = await UpdatePaymentRequestStateAsync(order);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
public async Task<bool> 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<Order>();
var request = new OrdersGetRequest(order.Id);
// Ensure order object comes from PayPal
var response = await _payPalHttpClient.Execute(request);
order = response.Result<Order>();
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<PaymentRequest> 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, PaymentRequestDto>(paymentRequest);
return paymentRequest;
}
}
}

37
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"
}
}
}

8
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<bool> HandleWebhookAsync(string payload)
{
return await RequestAsync<bool>(nameof(HandleWebhookAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), payload }
});
}
}
}

14
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<bool> HandleWebhookAsync(string payload)
{
var bytes = await Request.Body.GetAllBytesAsync();
payload = Encoding.UTF8.GetString(bytes);
return await PaymentRequestAppService.HandleWebhookAsync(payload);
}
}
}
Loading…
Cancel
Save