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 cf3e8f40..c41feb31 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -9,8 +9,15 @@ namespace EShopOnAbp.PaymentService.PaymentRequests Task StartAsync(PaymentRequestStartDto input); - Task CompleteAsync(string token); + Task CompleteAsync(PaymentRequestCompleteInputDto input); Task HandleWebhookAsync(string payload); + // /// + // /// 0 - Demo + // /// 1 - Paypal + // /// + // /// + // /// + // Task SetPaymentServiceAsync(int paymentTypeId); } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCompleteInputDto.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCompleteInputDto.cs new file mode 100644 index 00000000..2c154610 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCompleteInputDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace EShopOnAbp.PaymentService.PaymentRequests; + +[Serializable] +public class PaymentRequestCompleteInputDto +{ + public string Token { get; set; } + public int PaymentTypeId { get; set; } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs index 44b39fdb..d5b06af4 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs @@ -6,6 +6,7 @@ namespace EShopOnAbp.PaymentService.PaymentRequests [Serializable] public class PaymentRequestStartDto { + public int PaymentTypeId { get; set; } public Guid PaymentRequestId { get; set; } [Required] diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs index 1b805927..6bfa585d 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs @@ -2,6 +2,5 @@ { public static class PaymentServiceConsts { - public static bool ByPassPaymentProvider { get; set; } } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs index 28bac790..82e582f2 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,44 +1,49 @@ -using EShopOnAbp.PaymentService.PayPal; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using PayPalCheckoutSdk.Core; using PayPalCheckoutSdk.Orders; using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; +using EShopOnAbp.PaymentService.PaymentServices; +using Microsoft.Extensions.Logging; namespace EShopOnAbp.PaymentService.PaymentRequests { - [ExposeServices(typeof(PaymentRequestAppService))] public class PaymentRequestAppService : PaymentServiceAppService, IPaymentRequestAppService { + private readonly PaymentServiceFactory _paymentServiceFactory; + private readonly PaymentRequestDomainService _paymentRequestDomainService; protected IPaymentRequestRepository PaymentRequestRepository { get; } protected PayPalHttpClient PayPalHttpClient { get; } + public PaymentRequestAppService( IPaymentRequestRepository paymentRequestRepository, - PayPalHttpClient payPalHttpClient) + PayPalHttpClient payPalHttpClient, + PaymentServiceFactory paymentServiceFactory, + PaymentRequestDomainService paymentRequestDomainService) { PaymentRequestRepository = paymentRequestRepository; PayPalHttpClient = payPalHttpClient; + _paymentServiceFactory = paymentServiceFactory; + _paymentRequestDomainService = paymentRequestDomainService; } public virtual async Task CreateAsync(PaymentRequestCreationDto input) { - var paymentRequest = new PaymentRequest(id: GuidGenerator.Create(), orderId: input.OrderId, orderNo:input.OrderNo, currency: input.Currency, buyerId: input.BuyerId); + var paymentRequest = new PaymentRequest(id: GuidGenerator.Create(), orderId: input.OrderId, + orderNo: input.OrderNo, currency: input.Currency, buyerId: input.BuyerId); foreach (var paymentRequestProduct in input.Products - .Select(s => new PaymentRequestProduct( - GuidGenerator.Create(), - paymentRequestId: paymentRequest.Id, - code: s.Code, - name: s.Name, - unitPrice: s.UnitPrice, - quantity: s.Quantity, - totalPrice: s.TotalPrice, - referenceId: s.ReferenceId))) + .Select(s => new PaymentRequestProduct( + GuidGenerator.Create(), + paymentRequestId: paymentRequest.Id, + code: s.Code, + name: s.Name, + unitPrice: s.UnitPrice, + quantity: s.Quantity, + totalPrice: s.TotalPrice, + referenceId: s.ReferenceId))) { paymentRequest.Products.Add(paymentRequestProduct); } @@ -50,71 +55,17 @@ namespace EShopOnAbp.PaymentService.PaymentRequests public virtual async Task StartAsync(PaymentRequestStartDto input) { - var paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId, includeDetails: true); - - var totalCheckoutPrice = paymentRequest.Products.Sum(s => s.TotalPrice); - - var order = new OrderRequest - { - CheckoutPaymentIntent = "CAPTURE", - ApplicationContext = new ApplicationContext - { - ReturnUrl = input.ReturnUrl, - CancelUrl = input.CancelUrl, - }, - PurchaseUnits = new List - { - new PurchaseUnitRequest - { - AmountWithBreakdown = new AmountWithBreakdown - { - AmountBreakdown = new AmountBreakdown - { - ItemTotal = new Money - { - CurrencyCode = paymentRequest.Currency, - Value = totalCheckoutPrice.ToString($"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00") - } - }, - CurrencyCode = paymentRequest.Currency, - Value = totalCheckoutPrice.ToString($"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00"), - }, - Items = paymentRequest.Products.Select(p => new Item - { - Quantity = p.Quantity.ToString(), - Name = p.Name, - UnitAmount = new Money - { - CurrencyCode = paymentRequest.Currency, - Value = p.UnitPrice.ToString($"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00") - } - }).ToList(), - ReferenceId = paymentRequest.Id.ToString() - } - } - }; - - var request = new OrdersCreateRequest(); - request.Prefer("return=representation"); - request.RequestBody(order); - - var result = (await PayPalHttpClient.Execute(request)).Result(); - - return new PaymentRequestStartResultDto - { - CheckoutLink = result.Links.First(x => x.Rel == "approve").Href - }; + PaymentRequest paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId, includeDetails: true); + + var paymentService = _paymentServiceFactory.Create(input.PaymentTypeId); + return await paymentService.StartAsync(paymentRequest, input); } - public virtual async Task CompleteAsync(string token) + public virtual async Task CompleteAsync(PaymentRequestCompleteInputDto input) { - var request = new OrdersCaptureRequest(token); - request.RequestBody(new OrderActionRequest()); - - var order = (await PayPalHttpClient.Execute(request)).Result(); - - var paymentRequest = await UpdatePaymentRequestStateAsync(order); - + var paymentService = _paymentServiceFactory.Create(input.PaymentTypeId); + + var paymentRequest = await paymentService.CompleteAsync(PaymentRequestRepository, input.Token); return ObjectMapper.Map(paymentRequest); } @@ -130,34 +81,12 @@ namespace EShopOnAbp.PaymentService.PaymentRequests var response = await PayPalHttpClient.Execute(request); order = response.Result(); - await UpdatePaymentRequestStateAsync(order); + var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); + await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status, order.Id); // 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; } - - protected async Task UpdatePaymentRequestStateAsync(Order order) - { - var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); - - var paymentRequest = await PaymentRequestRepository.GetAsync(paymentRequestId); - - if (order.Status == PayPalConsts.OrderStatus.Completed || order.Status == PayPalConsts.OrderStatus.Approved) - { - paymentRequest.SetAsCompleted(); - } - else - { - paymentRequest.SetAsFailed(order.Status); - } - - paymentRequest.ExtraProperties[PayPalConsts.OrderIdPropertyName] = order.Id; - paymentRequest.ExtraProperties[nameof(order.Status)] = order.Status; - - await PaymentRequestRepository.UpdateAsync(paymentRequest); - - return paymentRequest; - } } -} +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestByPassAppService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestByPassAppService.cs deleted file mode 100644 index 1c032844..00000000 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestByPassAppService.cs +++ /dev/null @@ -1,38 +0,0 @@ -using PayPalCheckoutSdk.Core; -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.PaymentService.PaymentRequests -{ - [ExposeServices(typeof(PaymentRequestByPassAppService))] - public class PaymentRequestByPassAppService : PaymentRequestAppService - { - public PaymentRequestByPassAppService( - IPaymentRequestRepository paymentRequestRepository, - PayPalHttpClient payPalHttpClient) - : base(paymentRequestRepository, - payPalHttpClient) - { - } - - public override Task StartAsync(PaymentRequestStartDto input) - { - return Task.FromResult(new PaymentRequestStartResultDto - { - CheckoutLink = input.ReturnUrl + "?token=" + input.PaymentRequestId - }); - } - - public override async Task CompleteAsync(string token) - { - var paymentRequest = await PaymentRequestRepository.GetAsync(Guid.Parse(token)); - - paymentRequest.SetAsCompleted(); - - await PaymentRequestRepository.UpdateAsync(paymentRequest); - - return ObjectMapper.Map(paymentRequest); - } - } -} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs index 158e6fb1..e57c90b7 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs @@ -39,18 +39,6 @@ namespace EShopOnAbp.PaymentService return new PayPalHttpClient(new LiveEnvironment(options.ClientId, options.Secret)); }); - - context.Services.AddTransient(provider => - { - if (PaymentServiceConsts.ByPassPaymentProvider) - { - return provider.GetRequiredService(); - } - else - { - return provider.GetRequiredService(); - } - }); } } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoService.cs new file mode 100644 index 00000000..da54bb5d --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoService.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using EShopOnAbp.PaymentService.PaymentRequests; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public class DemoService : IPaymentStrategy +{ + public Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input) + { + return Task.FromResult(new PaymentRequestStartResultDto + { + CheckoutLink = input.ReturnUrl + "?token=" + input.PaymentRequestId + }); + } + + public async Task CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token) + { + var paymentRequest = await paymentRequestRepository.GetAsync(Guid.Parse(token)); + + paymentRequest.SetAsCompleted(); + + return await paymentRequestRepository.UpdateAsync(paymentRequest); + } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentStrategy.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentStrategy.cs new file mode 100644 index 00000000..aafab8bd --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentStrategy.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using EShopOnAbp.PaymentService.PaymentRequests; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public interface IPaymentStrategy +{ + public Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input); + public Task CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token); +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentServiceFactory.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentServiceFactory.cs new file mode 100644 index 00000000..719ea7da --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentServiceFactory.cs @@ -0,0 +1,41 @@ +using System; +using EShopOnAbp.PaymentService.PaymentRequests; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PayPalCheckoutSdk.Core; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public class PaymentServiceFactory : ITransientDependency +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public PaymentServiceFactory(IServiceProvider serviceProvider, ILogger logger) + { + logger.LogInformation("== Payment SErvice Factory == "); + _serviceProvider = serviceProvider; + _logger = logger; + } + + public IPaymentStrategy Create(int paymentTypeId) + { + if (paymentTypeId == 0) + { + return new DemoService(); + } + + if (paymentTypeId == 1) + { + var paypalService = _serviceProvider.GetRequiredService(); + _logger.LogInformation($"=== Resolved PaypalService {paypalService} ==="); + var requestDomainService = _serviceProvider.GetRequiredService(); + _logger.LogInformation($"=== Resolved PaymentRequestDomainService {requestDomainService} ==="); + + return new PaypalService(paypalService, requestDomainService); + } + + return new DemoService(); + } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalService.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalService.cs new file mode 100644 index 00000000..d0dce7c8 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalService.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using EShopOnAbp.PaymentService.PaymentRequests; +using PayPalCheckoutSdk.Core; +using PayPalCheckoutSdk.Orders; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public class PaypalService : IPaymentStrategy +{ + private readonly PayPalHttpClient _payPalHttpClient; + private readonly PaymentRequestDomainService _paymentRequestDomainService; + + public PaypalService(PayPalHttpClient payPalHttpClient, PaymentRequestDomainService paymentRequestDomainService) + { + _payPalHttpClient = payPalHttpClient; + _paymentRequestDomainService = paymentRequestDomainService; + } + + public async Task StartAsync(PaymentRequest paymentRequest, + PaymentRequestStartDto input) + { + var totalCheckoutPrice = paymentRequest.Products.Sum(s => s.TotalPrice); + + var order = new OrderRequest + { + CheckoutPaymentIntent = "CAPTURE", + ApplicationContext = new ApplicationContext + { + ReturnUrl = input.ReturnUrl, + CancelUrl = input.CancelUrl, + }, + PurchaseUnits = new List + { + new PurchaseUnitRequest + { + AmountWithBreakdown = new AmountWithBreakdown + { + AmountBreakdown = new AmountBreakdown + { + ItemTotal = new Money + { + CurrencyCode = paymentRequest.Currency, + Value = totalCheckoutPrice.ToString( + $"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00") + } + }, + CurrencyCode = paymentRequest.Currency, + Value = totalCheckoutPrice.ToString( + $"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00"), + }, + Items = paymentRequest.Products.Select(p => new Item + { + Quantity = p.Quantity.ToString(), + Name = p.Name, + UnitAmount = new Money + { + CurrencyCode = paymentRequest.Currency, + Value = p.UnitPrice.ToString( + $"{CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator}00") + } + }).ToList(), + ReferenceId = paymentRequest.Id.ToString() + } + } + }; + + var request = new OrdersCreateRequest(); + request.Prefer("return=representation"); + request.RequestBody(order); + + Order result = (await _payPalHttpClient.Execute(request)).Result(); + + return new PaymentRequestStartResultDto + { + CheckoutLink = result.Links.First(x => x.Rel == "approve").Href + }; + } + + public async Task CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token) + { + var request = new OrdersCaptureRequest(token); + request.RequestBody(new OrderActionRequest()); + + var order = (await _payPalHttpClient.Execute(request)).Result(); + + var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); + return await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status, + order.Id); + } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PayPalConsts.cs similarity index 97% rename from services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs rename to services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PayPalConsts.cs index 0e83292c..d13ad8c0 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PayPalConsts.cs @@ -1,4 +1,4 @@ -namespace EShopOnAbp.PaymentService.PayPal +namespace EShopOnAbp.PaymentService { public static class PayPalConsts { diff --git a/services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestDomainService.cs b/services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestDomainService.cs new file mode 100644 index 00000000..6ea3542a --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestDomainService.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; + +namespace EShopOnAbp.PaymentService.PaymentRequests; + +public class PaymentRequestDomainService : DomainService +{ + private readonly IPaymentRequestRepository _paymentRequestRepository; + + public PaymentRequestDomainService(IPaymentRequestRepository paymentRequestRepository) + { + _paymentRequestRepository = paymentRequestRepository; + } + + public async Task UpdatePaymentRequestStateAsync( + Guid paymentRequestId, + string orderStatus, + string orderId) + { + var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestId); + + if (orderStatus == PayPalConsts.OrderStatus.Completed || orderStatus == PayPalConsts.OrderStatus.Approved) + { + paymentRequest.SetAsCompleted(); + } + else + { + paymentRequest.SetAsFailed(orderStatus); + } + + paymentRequest.ExtraProperties[PayPalConsts.OrderIdPropertyName] = orderId; + paymentRequest.ExtraProperties[nameof(orderStatus)] = orderStatus; + + await _paymentRequestRepository.UpdateAsync(paymentRequest); + + return paymentRequest; + } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/PaymentServiceHttpApiHostModule.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/PaymentServiceHttpApiHostModule.cs index 23af9ecd..9eed0e02 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/PaymentServiceHttpApiHostModule.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/PaymentServiceHttpApiHostModule.cs @@ -30,11 +30,7 @@ namespace EShopOnAbp.PaymentService var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - - /// Enable bypassing payment provider via uncommenting code line below. - /// If bypassing is enabled, all payments will be completed immediately. - // PaymentServiceConsts.ByPassPaymentProvider = true; - + JwtBearerConfigurationHelper.Configure(context, "PaymentService"); // SwaggerConfigurationHelper.Configure(context, "Payment Service API");