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..27673aab 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Volo.Abp.Application.Services; namespace EShopOnAbp.PaymentService.PaymentRequests @@ -12,5 +13,7 @@ namespace EShopOnAbp.PaymentService.PaymentRequests Task CompleteAsync(string token); Task HandleWebhookAsync(string payload); + + Task CreateMockTokenAsync(Guid paymentRequestId, bool successfulResult = true); } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs index 4c3d872b..f542c6cf 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalOptions.cs @@ -4,6 +4,8 @@ namespace EShopOnAbp.PaymentService.PayPal { public class PayPalOptions { + public bool IsEnabled { get; set; } = true; + public string ClientId { get; set; } public string Secret { 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 4e4cfc9d..d4d77cae 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,4 +1,5 @@ using EShopOnAbp.PaymentService.PayPal; +using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; using PayPalCheckoutSdk.Core; using PayPalCheckoutSdk.Orders; @@ -6,20 +7,26 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Volo.Abp; namespace EShopOnAbp.PaymentService.PaymentRequests { public class PaymentRequestAppService : PaymentServiceAppService, IPaymentRequestAppService { + private const string MockFailureToken = "55F97E93"; + private const string MockSuccessToken = "A8195146CA5A"; protected IPaymentRequestRepository PaymentRequestRepository { get; } protected PayPalHttpClient PayPalHttpClient { get; } + public PaymentOptions Options { get; } public PaymentRequestAppService( IPaymentRequestRepository paymentRequestRepository, - PayPalHttpClient payPalHttpClient) + PayPalHttpClient payPalHttpClient, + IOptions options) { PaymentRequestRepository = paymentRequestRepository; PayPalHttpClient = payPalHttpClient; + Options = options.Value; } public async Task CreateAsync(PaymentRequestCreationDto input) @@ -43,6 +50,14 @@ namespace EShopOnAbp.PaymentService.PaymentRequests public async Task StartAsync(PaymentRequestStartDto input) { + if (Options.UseMock) + { + return new PaymentRequestStartResultDto + { + CheckoutLink = String.Empty + }; + } + var paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId); var totalCheckoutPrice = paymentRequest.Products.Sum(s => s.TotalPrice); @@ -101,6 +116,11 @@ namespace EShopOnAbp.PaymentService.PaymentRequests public async Task CompleteAsync(string token) { + if (Options.UseMock) + { + return ObjectMapper.Map(await HandleMockTokenAsync(token)); + } + var request = new OrdersCaptureRequest(token); request.RequestBody(new OrderActionRequest()); @@ -130,6 +150,23 @@ namespace EShopOnAbp.PaymentService.PaymentRequests return true; } + public Task CreateMockTokenAsync(Guid paymentRequestId, bool successfulResult = true) + { + if (Options.UseMock) + { + throw new InvalidOperationException("Mock token can't be genetared. UseMock has to be set as true in PaymentOptions."); + } + + if (successfulResult) + { + return Task.FromResult(MockSuccessToken + "_" + paymentRequestId); + } + else + { + return Task.FromResult(MockFailureToken + "-" + paymentRequestId); + } + } + private async Task UpdatePaymentRequestStateAsync(Order order) { var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); @@ -152,5 +189,28 @@ namespace EShopOnAbp.PaymentService.PaymentRequests return paymentRequest; } + + private async Task HandleMockTokenAsync(string token) + { + var isPaymentSuccessful = token.Contains($"{MockSuccessToken}_") ? true : token.Contains($"{MockFailureToken}_") ? false : throw new UserFriendlyException("Token is invalid!"); + + PaymentRequest paymentRequest = null; + if (isPaymentSuccessful) + { + paymentRequest = await PaymentRequestRepository.GetAsync( + Guid.Parse(token.Replace($"{MockSuccessToken}_", string.Empty))); + + paymentRequest.SetAsCompleted(); + } + else + { + paymentRequest = await PaymentRequestRepository.GetAsync( + Guid.Parse(token.Replace($"{MockFailureToken}_", string.Empty))); + + paymentRequest.SetAsFailed("This payment has been mocked to be failed"); + } + + return paymentRequest; + } } } diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs index 024b3ba5..c6cc7479 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs @@ -19,12 +19,16 @@ namespace EShopOnAbp.PaymentService { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + context.Services.AddAutoMapperObjectMapper(); Configure(options => { options.AddMaps(validate: true); }); + Configure(configuration.GetSection("Payment:PayPal")); + context.Services.AddTransient(provider => { var options = provider.GetService>().Value; diff --git a/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentOptions.cs b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentOptions.cs new file mode 100644 index 00000000..7f066b97 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentOptions.cs @@ -0,0 +1,7 @@ +namespace EShopOnAbp.PaymentService +{ + public class PaymentOptions + { + public bool UseMock { get; set; } = true; + } +} diff --git a/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentServiceDomainSharedModule.cs b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentServiceDomainSharedModule.cs index 1ab5de4d..cf55d29c 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentServiceDomainSharedModule.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PaymentServiceDomainSharedModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Validation; using Volo.Abp.Validation.Localization; using Volo.Abp.VirtualFileSystem; +using Microsoft.Extensions.DependencyInjection; namespace EShopOnAbp.PaymentService { @@ -15,6 +16,10 @@ namespace EShopOnAbp.PaymentService { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration.GetSection("Payment")); + Configure(options => { options.FileSets.AddEmbedded(); diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml new file mode 100644 index 00000000..90e4b34b --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml @@ -0,0 +1,11 @@ +@page + +@model EShopOnAbp.PaymentService.Pages.MockCheckoutModel + + +
+@* + TODO: + Discuss about that*@ + +
\ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml.cs new file mode 100644 index 00000000..a974a71a --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/Pages/MockCheckout.cshtml.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace EShopOnAbp.PaymentService.Pages +{ + public class MockCheckoutModel : PageModel + { + + } +} diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi/PaymentRequests/PaymentRequestController.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/PaymentRequests/PaymentRequestController.cs new file mode 100644 index 00000000..85b556b5 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/PaymentRequests/PaymentRequestController.cs @@ -0,0 +1,80 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; + +namespace EShopOnAbp.PaymentService.PaymentRequests +{ + [RemoteService(Name = PaymentServiceRemoteServiceConsts.RemoteServiceName)] + [Area("payment")] + [Route("api/payment/requests")] + + public class PaymentRequestController : PaymentServiceController, IPaymentRequestAppService + { + protected IPaymentRequestAppService PaymentRequestAppService { get; } + public PaymentOptions Options { get; } + public IConfiguration Configuration { get; } + + public PaymentRequestController( + IPaymentRequestAppService paymentRequestAppService, + IOptions options, + IConfiguration configuration) + { + PaymentRequestAppService = paymentRequestAppService; + Options = options.Value; + Configuration = configuration; + } + + [HttpPost] + [Route("complete")] + public Task CompleteAsync(string token) + { + return PaymentRequestAppService.CompleteAsync(token); + } + + [HttpPost] + public Task CreateAsync(PaymentRequestCreationDto input) + { + return PaymentRequestAppService.CreateAsync(input); + } + + [HttpPost] + [Route("mock-token")] + public Task CreateMockTokenAsync(Guid paymentRequestId, bool successfulResult = true) + { + return PaymentRequestAppService.CreateMockTokenAsync(paymentRequestId, successfulResult); + } + + [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); + } + + [HttpPost] + [Route("start")] + public async Task StartAsync(PaymentRequestStartDto input) + { + if (Options.UseMock) + { + var token = await CreateMockTokenAsync(input.PaymentRequestId, true); + var uri = new Uri(input.ReturnUrl); + + return new PaymentRequestStartResultDto + { + CheckoutLink = .EndsWith("/") + $"?token={token}&returnUrl={input.ReturnUrl}" + }; + } + + return await PaymentRequestAppService.StartAsync(input); + } + } +}