From cc9be5dc7b9ad3550d1e01721bf5b93e72ab0d4f Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Oct 2021 15:22:46 +0300 Subject: [PATCH] Creating Payment Checkout scenario --- .../IPaymentRequestAppService.cs | 2 +- .../PaymentRequestCreationDto.cs | 17 +++-- .../PaymentRequests/PaymentRequestDto.cs | 12 ++-- .../PaymentRequestAppService.cs | 14 ++-- .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 +++++++++ .../src/Payment.Domain/PayPal/PayPalConsts.cs | 18 ++++++ .../Payment.Domain/PayPal/PayPalOptions.cs | 22 +++++++ .../src/Payment.Domain/PaymentDomainModule.cs | 10 ++- .../src/Payment.Domain/PaymentOptions.cs | 7 ++ .../PaymentRequests/PaymentRequest.cs | 18 ++++-- .../Payment.Web/Pages/Payment/Checkout.cshtml | 6 +- .../Pages/Payment/Checkout.cshtml.cs | 64 ++++++++++++++++++- .../Pages/Payment/CheckoutCallback.cshtml | 8 +++ .../Pages/Payment/CheckoutCallback.cshtml.cs | 6 ++ .../src/Payment.Web/Pages/PaymentPageBase.cs | 20 ++++++ .../src/Payment.Web/Payment.Web.csproj | 1 + .../PaymentRequest/IPaymentUrlBuilder.cs | 2 + .../PaymentRequest/PaymentUrlBuilder.cs | 5 ++ .../PaymentRequestAppService_Tests.cs | 4 +- src/EventHub.Web/appsettings.json | 8 +++ 21 files changed, 247 insertions(+), 30 deletions(-) create mode 100644 modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml create mode 100644 modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd create mode 100644 modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs create mode 100644 modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs create mode 100644 modules/payment/src/Payment.Domain/PaymentOptions.cs create mode 100644 modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml create mode 100644 modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs create mode 100644 modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs index b2ddaac..85fc4a0 100644 --- a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -7,7 +7,7 @@ namespace Payment.PaymentRequests public interface IPaymentRequestAppService : IApplicationService { Task GetAsync(Guid id); - + Task CreateAsync(PaymentRequestCreationDto input); } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs index 78baab2..5f043ff 100644 --- a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using System.ComponentModel.DataAnnotations; using Volo.Abp.ObjectExtending; @@ -6,12 +7,20 @@ namespace Payment.PaymentRequests public class PaymentRequestCreationDto : ExtensibleObject { public string CustomerId { get; set; } - + public string ProductId { get; set; } - + [Required] public string ProductName { get; set; } - - public decimal Amount { get; set; } + + public decimal Price { get; set; } + + /// + /// 3 Letter Language Code in ISO 4217 Standards. + /// For example: USD, EUR. + /// + [StringLength(3)] + [CanBeNull] + public string Currency { get; set; } } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs index 528a6f1..8e453fc 100644 --- a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs @@ -6,13 +6,15 @@ namespace Payment.PaymentRequests public class PaymentRequestDto : CreationAuditedEntityDto { public string CustomerId { get; set; } - + public string ProductId { get; set; } - + public string ProductName { get; set; } - - public decimal Amount { get; set; } - + + public decimal Price { get; set; } + + public string Currency { get; set; } + public PaymentRequestState State { get; set; } } } \ 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 9f4cc6e..8bf1759 100644 --- a/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Options; +using System; using System.Threading.Tasks; namespace Payment.PaymentRequests @@ -6,16 +7,20 @@ namespace Payment.PaymentRequests public class PaymentRequestAppService : PaymentAppService, IPaymentRequestAppService { private readonly IPaymentRequestRepository _paymentRequestRepository; + private readonly PaymentOptions _paymentOptions; - public PaymentRequestAppService(IPaymentRequestRepository paymentRequestRepository) + public PaymentRequestAppService( + IPaymentRequestRepository paymentRequestRepository, + IOptions paymentOptions) { _paymentRequestRepository = paymentRequestRepository; + _paymentOptions = paymentOptions.Value; } public async Task GetAsync(Guid id) { var paymentRequest = await _paymentRequestRepository.GetAsync(id); - + return ObjectMapper.Map(paymentRequest); } @@ -26,7 +31,8 @@ namespace Payment.PaymentRequests input.CustomerId, input.ProductId, input.ProductName, - input.Amount + input.Price, + input.Currency ?? _paymentOptions.DefaultCurrency ); await _paymentRequestRepository.InsertAsync(paymentRequest); diff --git a/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml new file mode 100644 index 0000000..00e1d9a --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs b/modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs new file mode 100644 index 0000000..0544336 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PayPal/PayPalConsts.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Payment.PayPal +{ + public static class PayPalConsts + { + public static class Environment + { + public const string Sandbox = "Sandbox"; + + public const string Live = "Live"; + } + } +} diff --git a/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs b/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs new file mode 100644 index 0000000..1511ba0 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Payment.PayPal +{ + public class PayPalOptions + { + public string ClientId { get; set; } + + public string Secret { get; set; } + + public string Locale { get; set; } + + /// + /// "Sandbox" or "Live". Default value is "Sandbox" + /// + public string Environment { get; set; } = PayPalConsts.Environment.Sandbox; + + public bool Recommended { get; set; } + + public List ExtraInfos { get; set; } = new(); + } +} diff --git a/modules/payment/src/Payment.Domain/PaymentDomainModule.cs b/modules/payment/src/Payment.Domain/PaymentDomainModule.cs index b5a2a41..4b76dba 100644 --- a/modules/payment/src/Payment.Domain/PaymentDomainModule.cs +++ b/modules/payment/src/Payment.Domain/PaymentDomainModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Domain; +using Microsoft.Extensions.DependencyInjection; +using Payment.PayPal; +using Volo.Abp.Domain; using Volo.Abp.Modularity; namespace Payment @@ -9,6 +11,12 @@ namespace Payment )] public class PaymentDomainModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + Configure(configuration.GetSection("Payment:PayPal")); + Configure(configuration.GetSection("Payment")); + } } } diff --git a/modules/payment/src/Payment.Domain/PaymentOptions.cs b/modules/payment/src/Payment.Domain/PaymentOptions.cs new file mode 100644 index 0000000..bf069b0 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentOptions.cs @@ -0,0 +1,7 @@ +namespace Payment +{ + public class PaymentOptions + { + public string DefaultCurrency { get; set; } + } +} diff --git a/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs b/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs index 5d47f26..dbbcc81 100644 --- a/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs +++ b/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs @@ -9,14 +9,16 @@ namespace Payment.PaymentRequests { [CanBeNull] public string CustomerId { get; private set; } - + [CanBeNull] public string ProductId { get; private set; } - + [NotNull] public string ProductName { get; private set; } - - public decimal Amount { get; private set; } + + public decimal Price { get; private set; } + + public string Currency { get; private set; } public PaymentRequestState State { get; set; } @@ -24,7 +26,7 @@ namespace Payment.PaymentRequests private PaymentRequest() { - + } public PaymentRequest( @@ -32,13 +34,15 @@ namespace Payment.PaymentRequests [CanBeNull] string customerId, [CanBeNull] string productId, [NotNull] string productName, - decimal amount) + decimal price, + [NotNull] string currency) : base(id) { CustomerId = customerId; ProductId = productId; ProductName = Check.NotNullOrWhiteSpace(productName, nameof(productName)); - Amount = amount; + Price = price; + Currency = currency; } } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml b/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml index f1f30e4..2823565 100644 --- a/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml +++ b/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml @@ -1,9 +1,7 @@ @page -@using Microsoft.Extensions.Localization -@using Payment.Localization @using Payment.Web.Pages.Payment +@inherits Payment.Web.Pages.PaymentPageBase @model Payment.Web.Pages.Payment.CheckoutPageModel -@inject IStringLocalizer L @section scripts { @@ -20,7 +18,7 @@
  • Product name: @Model.PaymentRequest.ProductName
  • -
  • Amount: @Model.PaymentRequest.Amount
  • +
  • Amount: @Model.PaymentRequest.Price
diff --git a/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs b/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs index 9d8385e..0468b07 100644 --- a/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs +++ b/modules/payment/src/Payment.Web/Pages/Payment/Checkout.cshtml.cs @@ -1,7 +1,14 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using NUglify.Helpers; using Payment.PaymentRequests; +using Payment.Web.PaymentRequest; +using PayPalCheckoutSdk.Core; +using PayPalCheckoutSdk.Orders; namespace Payment.Web.Pages.Payment { @@ -13,10 +20,17 @@ namespace Payment.Web.Pages.Payment public PaymentRequestDto PaymentRequest { get; set; } private readonly IPaymentRequestAppService _paymentRequestAppService; + private readonly IPaymentUrlBuilder _paymentUrlBuilder; + private readonly PayPalHttpClient _payPalHttpClient; - public CheckoutPageModel(IPaymentRequestAppService paymentRequestAppService) + public CheckoutPageModel( + IPaymentRequestAppService paymentRequestAppService, + IPaymentUrlBuilder paymentUrlBuilder, + PayPalHttpClient payPalHttpClient) { _paymentRequestAppService = paymentRequestAppService; + this._paymentUrlBuilder = paymentUrlBuilder; + this._payPalHttpClient = payPalHttpClient; } public async Task OnGetAsync() @@ -28,10 +42,56 @@ namespace Payment.Web.Pages.Payment { PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId); - // TODO: Redirect from here. + var order = new OrderRequest + { + CheckoutPaymentIntent = "CAPTURE", + ApplicationContext = new ApplicationContext + { + ReturnUrl = _paymentUrlBuilder.BuildReturnUrl(PaymentRequest.Id).AbsoluteUri, + CancelUrl = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequest.Id).AbsoluteUri, + }, + PurchaseUnits = new List + { + new PurchaseUnitRequest + { + AmountWithBreakdown = new AmountWithBreakdown + { + AmountBreakdown = new AmountBreakdown + { + ItemTotal = new Money + { + CurrencyCode = PaymentRequest.Currency, + Value = PaymentRequest.Price.ToString(".00") + } + }, + CurrencyCode = PaymentRequest.Currency, + Value = PaymentRequest.Price.ToString(".00"), + }, + Items = new List + { + new Item + { + Quantity = 1.ToString(), + Name = PaymentRequest.ProductName, + UnitAmount = new Money + { + CurrencyCode = PaymentRequest.Currency, + Value = PaymentRequest.Price.ToString(".00") + } + } + }, + ReferenceId = PaymentRequest.Id.ToString() + } + } + }; + var request = new OrdersCreateRequest(); + request.Prefer("return=representation"); + request.RequestBody(order); + var result = (await _payPalHttpClient.Execute(request)).Result(); + Response.Redirect(result.Links.First(x => x.Rel == "approve").Href); } } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml b/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml new file mode 100644 index 0000000..cec29af --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml @@ -0,0 +1,8 @@ +@page +@using Payment.Web.Pages.Payment +@inherits Payment.Web.Pages.PaymentPageBase +@model Payment.Web.Pages.Payment.CheckoutCallbackModel +@{ + PageLayout.Content.Title = L["Redirecting"]; +} +

@L["Redirecting"]

\ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs b/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs new file mode 100644 index 0000000..a5d2b66 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/CheckoutCallback.cshtml.cs @@ -0,0 +1,6 @@ +namespace Payment.Web.Pages.Payment +{ + public class CheckoutCallbackModel : PaymentPageModel + { + } +} diff --git a/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs b/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs new file mode 100644 index 0000000..8e2dc9e --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.Localization; +using Payment.Localization; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.Layout; + +namespace Payment.Web.Pages +{ + public class PaymentPageBase : Microsoft.AspNetCore.Mvc.RazorPages.Page + { + [RazorInject] public IStringLocalizer L { get; set; } + + [RazorInject] public IPageLayout PageLayout { get; set; } + + public override Task ExecuteAsync() + { + return Task.CompletedTask; // Will be overriden by razor pages. (.cshtml) + } + } +} diff --git a/modules/payment/src/Payment.Web/Payment.Web.csproj b/modules/payment/src/Payment.Web/Payment.Web.csproj index 8989689..e136494 100644 --- a/modules/payment/src/Payment.Web/Payment.Web.csproj +++ b/modules/payment/src/Payment.Web/Payment.Web.csproj @@ -12,6 +12,7 @@ + diff --git a/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs b/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs index b71880e..31e6649 100644 --- a/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs +++ b/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs @@ -5,5 +5,7 @@ namespace Payment.Web.PaymentRequest public interface IPaymentUrlBuilder { Uri BuildCheckoutUrl(Guid paymentRequestId); + + Uri BuildReturnUrl(Guid paymentRequestId); } } \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs b/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs index 3398911..ebc7e20 100644 --- a/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs +++ b/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs @@ -18,5 +18,10 @@ namespace Payment.Web.PaymentRequest { return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/Checkout?paymentRequestId=" + paymentRequestId); } + + public Uri BuildReturnUrl(Guid paymentRequestId) + { + return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/CheckoutCallback?paymentRequestId=" + paymentRequestId); + } } } diff --git a/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs b/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs index 0a5bb3e..361716f 100644 --- a/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs +++ b/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs @@ -21,12 +21,12 @@ namespace Payment.PaymentRequests new PaymentRequestCreationDto { ProductName = "My product 1", - Amount = 99.99m + Price = 99.99m } ); result.Id.ShouldNotBe(Guid.Empty); - result.Amount.ShouldBe(99.99m); + result.Price.ShouldBe(99.99m); result.ProductName.ShouldBe("My product 1"); } } diff --git a/src/EventHub.Web/appsettings.json b/src/EventHub.Web/appsettings.json index 2c4a520..ad787a5 100644 --- a/src/EventHub.Web/appsettings.json +++ b/src/EventHub.Web/appsettings.json @@ -7,5 +7,13 @@ "RequireHttpsMetadata": "true", "ClientId": "EventHub_Web", "ClientSecret": "1q2w3e*" + }, + "Payment": { + "DefaultCurrency": "USD", + "PayPal": { + "ClientId": "{ClientId}", + "Secret": "{Secret}", + "Environment": "Sandbox" + } } }