diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml index 96f2a3b5..08495130 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml @@ -12,40 +12,47 @@
-

Select Address

- @for (int i = 0; i < Math.Ceiling(Model.Address.Count / (double) defaultColumnSize); i++) - { - - @for (int j = 0; j < defaultColumnSize; j++) - { - - @if (i * defaultColumnSize + j < Model.Address.Count) - { - var address = Model.Address[(i * defaultColumnSize) + j]; - string isSelected = address.IsDefault ? "is-selected" : string.Empty; -
-

@address.Type

-
-

@address.ToString()

-
- -
- } -
- } -
- } +
Select Address
+ + @foreach (var address in Model.Address) + { + string isSelectedAddressClass = address.IsDefault ? "is-selected" : string.Empty; + +
+
+

@address.Type

+
+
+

@address.ToString()

+
+ +
+
+ } +
-

Select Payment Method

+
Select Payment Method
- - - paypal logo - - + @foreach (var paymentType in Model.PaymentTypes) + { + string isSelectedClass = paymentType.IsDefault ? "is-selected" : ""; + + + +

+ @paymentType.Name +

+

+ +

+
+
+
+ }
+
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/PaymentWidgetViewComponent.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/PaymentWidgetViewComponent.cs index e0335a7a..0e458afa 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/PaymentWidgetViewComponent.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/PaymentWidgetViewComponent.cs @@ -18,19 +18,25 @@ public class PaymentWidgetViewComponent : AbpViewComponent { private readonly UserBasketProvider _userBasketProvider; private readonly UserAddressProvider _userAddressProvider; + private readonly PaymentTypeProvider _paymentTypeProvider; - public PaymentWidgetViewComponent(UserBasketProvider userBasketProvider, UserAddressProvider userAddressProvider) + public PaymentWidgetViewComponent( + UserBasketProvider userBasketProvider, + UserAddressProvider userAddressProvider, + PaymentTypeProvider paymentTypeProvider) { _userBasketProvider = userBasketProvider; _userAddressProvider = userAddressProvider; + _paymentTypeProvider = paymentTypeProvider; } public async Task InvokeAsync() { - var viewModel = new PaymentViewModel() + var viewModel = new PaymentViewModel { Basket = await _userBasketProvider.GetBasketAsync(), - Address = _userAddressProvider.GetDemoAddresses() + Address = _userAddressProvider.GetDemoAddresses(), + PaymentTypes = _paymentTypeProvider.GetPaymentTypes() }; return View("~/Components/Payment/Default.cshtml", viewModel); } @@ -40,4 +46,5 @@ public class PaymentViewModel { public BasketDto Basket { get; set; } public List Address { get; set; } + public List PaymentTypes { get; set; } } \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPaymentConsts.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPaymentConsts.cs index c9a75f54..b58e38a9 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPaymentConsts.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPaymentConsts.cs @@ -3,6 +3,7 @@ public static class EShopOnAbpPaymentConsts { public const string Currency = "USD"; + public const string PaymentIdCookie = "selected_payment_id"; // Setted in payment-widget.js public static class DemoAddressTypes { diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml index 96ece09b..bdfd8c57 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml @@ -11,7 +11,6 @@ }
- @*

@L["Payment"]

*@
@await Component.InvokeAsync(typeof(PaymentWidgetViewComponent))
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml.cs index d4191c8c..9ad48597 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml.cs @@ -59,7 +59,7 @@ public class PaymentModel : AbpPageModel var placedOrder = await _orderAppService.CreateAsync(new OrderCreateDto() { - PaymentTypeId = 1, // Paypal + PaymentTypeId = model.SelectedPaymentId, Address = GetUserAddress(model.SelectedAddressId), Products = productItems }); @@ -75,9 +75,10 @@ public class PaymentModel : AbpPageModel var response = await _paymentRequestAppService.StartAsync(new PaymentRequestStartDto { + PaymentTypeId = model.SelectedPaymentId, PaymentRequestId = paymentRequest.Id, ReturnUrl = _publicWebPaymentOptions.PaymentSuccessfulCallbackUrl, - CancelUrl = _publicWebPaymentOptions.PaymentFailureCallbackUrl, + CancelUrl = _publicWebPaymentOptions.PaymentFailureCallbackUrl }); return Redirect(response.CheckoutLink); diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs index e313939d..ffaa090e 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs @@ -24,12 +24,23 @@ public class PaymentCompletedModel : AbpPageModel public async Task OnGetAsync() { - PaymentRequest = await _paymentRequestAppService.CompleteAsync(Token); + int selectedPaymentId = 0; + if (HttpContext.Request.Cookies.TryGetValue(EShopOnAbpPaymentConsts.PaymentIdCookie, + out var selectedPaymentIdString)) + { + selectedPaymentId = string.IsNullOrEmpty(selectedPaymentIdString) ? 0 : int.Parse(selectedPaymentIdString); + } + + PaymentRequest = await _paymentRequestAppService.CompleteAsync( + new PaymentRequestCompleteInputDto() {Token = Token, PaymentTypeId = selectedPaymentId}); IsSuccessful = PaymentRequest.State == PaymentRequestState.Completed; + if (IsSuccessful) { - return RedirectToPage("OrderReceived", new { orderNo = PaymentRequest.OrderNo }); + // Remove cookie so that can be set again when default payment type is set + HttpContext.Response.Cookies.Delete(EShopOnAbpPaymentConsts.PaymentIdCookie); + return RedirectToPage("OrderReceived", new {orderNo = PaymentRequest.OrderNo}); } return Page(); diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/PaymentTypeProvider.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/PaymentTypeProvider.cs new file mode 100644 index 00000000..a2b9d755 --- /dev/null +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/PaymentTypeProvider.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PublicWeb.ServiceProviders; + +public class PaymentTypeProvider : ITransientDependency +{ + public List GetPaymentTypes() + { + return new List + { + new() {Id = 0, Name = "Demo", IconCss = "fa-credit-card demo", IsDefault = true}, + new() {Id = 1, Name = "Paypal", IconCss = "fa-cc-paypal paypal"} + }; + } +} + +public class PaymentType +{ + public int Id { get; set; } + public string Name { get; set; } + public string IconCss { get; set; } + public bool IsDefault { get; set; } = false; +} \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/UserAddressProvider.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/UserAddressProvider.cs index 6e343752..9c69caa0 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/UserAddressProvider.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/UserAddressProvider.cs @@ -26,8 +26,7 @@ public class UserAddressProvider : ITransientDependency Street = "Yeşilköy Serbest Bölge Mah. E-Blok Sk. Bakırköy", City = "İstanbul", Country = "Turkey", - ZipCode = "34149", - Description = "Near Ataturk Airport" + ZipCode = "34149" } }; } @@ -38,7 +37,6 @@ public class AddressDto public int Id { get; set; } public string Type { get; set; } public bool IsDefault { get; set; } = false; - public string Description { get; set; } public string Street { get; set; } public string City { get; set; } public string Country { get; set; } diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.css b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.css index 1b38f784..90e9a095 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.css +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.css @@ -17,4 +17,16 @@ .is-selected { border: solid #bfbfe3; +} + +.payment-type-header { + font-weight: 500; +} + +.paypal { + color: #6c84fa; +} + +.demo { + color: darkgray; } \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.js b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.js index b246c5f1..0632b47c 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.js +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.js @@ -1,4 +1,8 @@ (function () { + // Write selected payment type to cookie anyways + const paymentTypeId = $(".payment-list").find(".is-selected").attr('data-payment-id'); + abp.utils.setCookieValue("selected_payment_id", paymentTypeId); + abp.widgets.PaymentWidget = function ($wrapper) { var widgetManager = $wrapper.data('abp-widget-manager'); @@ -6,12 +10,20 @@ $wrapper .find('.address-list .card') .click(function () { - var $this = $(this); - var addressId = $this.attr('data-address-id'); - abp.utils.setCookieValue("selected-address", addressId); + const $this = $(this); $this.parents(".address-list").find('.card').removeClass("is-selected"); $this.addClass("is-selected"); }); + + $wrapper + .find('.payment-list .card') + .click(el => { + const $this = $(el.currentTarget); + const paymentTypeId = $this.attr('data-payment-id'); + abp.utils.setCookieValue("selected_payment_id", paymentTypeId); + $this.parents(".payment-list").find('.card').removeClass("is-selected"); + $this.addClass("is-selected"); + }); }; return { diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs index 2e556acd..ec3a0e35 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs @@ -7,16 +7,16 @@ namespace EShopOnAbp.OrderingService.Orders; public class PaymentType : Enumeration { + public static PaymentType Demo = new PaymentType(0, nameof(Demo).ToLowerInvariant()); public static PaymentType Paypal = new PaymentType(1, nameof(Paypal).ToLowerInvariant()); - public static PaymentType Demo = new PaymentType(2, nameof(Paypal).ToLowerInvariant()); public PaymentType(int id, string name) : base(id, name) { } public static IEnumerable List() => - new[] {Paypal, Demo}; - + new[] {Demo, Paypal}; + public static PaymentType FromName(string name) { var state = List() 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..366c3413 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -9,7 +9,7 @@ namespace EShopOnAbp.PaymentService.PaymentRequests Task StartAsync(PaymentRequestStartDto input); - Task CompleteAsync(string token); + Task CompleteAsync(PaymentRequestCompleteInputDto input); Task HandleWebhookAsync(string payload); } 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..15b62860 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs @@ -1,44 +1,47 @@ -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; namespace EShopOnAbp.PaymentService.PaymentRequests { - [ExposeServices(typeof(PaymentRequestAppService))] public class PaymentRequestAppService : PaymentServiceAppService, IPaymentRequestAppService { + private readonly PaymentMethodResolver _paymentMethodResolver; + private readonly PaymentRequestDomainService _paymentRequestDomainService; protected IPaymentRequestRepository PaymentRequestRepository { get; } protected PayPalHttpClient PayPalHttpClient { get; } public PaymentRequestAppService( IPaymentRequestRepository paymentRequestRepository, - PayPalHttpClient payPalHttpClient) + PayPalHttpClient payPalHttpClient, + PaymentMethodResolver paymentMethodResolver, + PaymentRequestDomainService paymentRequestDomainService) { PaymentRequestRepository = paymentRequestRepository; PayPalHttpClient = payPalHttpClient; + _paymentMethodResolver = paymentMethodResolver; + _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 +53,18 @@ namespace EShopOnAbp.PaymentService.PaymentRequests public virtual async Task StartAsync(PaymentRequestStartDto input) { - var paymentRequest = await PaymentRequestRepository.GetAsync(input.PaymentRequestId, includeDetails: true); + PaymentRequest 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 - }; + var paymentService = _paymentMethodResolver.Resolve(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 = _paymentMethodResolver.Resolve(input.PaymentTypeId); + var paymentRequest = await paymentService.CompleteAsync(PaymentRequestRepository, input.Token); return ObjectMapper.Map(paymentRequest); } @@ -130,34 +80,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..f0b21e0f 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs @@ -7,6 +7,8 @@ using EShopOnAbp.PaymentService.PayPal; using PayPalCheckoutSdk.Core; using System; using EShopOnAbp.PaymentService.PaymentRequests; +using EShopOnAbp.PaymentService.PaymentServices; +using Microsoft.Extensions.Logging; namespace EShopOnAbp.PaymentService { @@ -15,7 +17,7 @@ namespace EShopOnAbp.PaymentService typeof(PaymentServiceApplicationContractsModule), typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule) - )] + )] public class PaymentServiceApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -40,17 +42,10 @@ 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(); - } - }); + context.Services.AddTransient(provider => new PaymentMethodResolver( + provider.GetServices(), + provider.GetRequiredService>() + )); } } -} +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoPaymentMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoPaymentMethod.cs new file mode 100644 index 00000000..46c8e24d --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoPaymentMethod.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using EShopOnAbp.PaymentService.PaymentRequests; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +[ExposeServices(typeof(IPaymentMethod), typeof(DemoPaymentMethod))] +public class DemoPaymentMethod : IPaymentMethod +{ + public int PaymentTypeId { get; } + + public DemoPaymentMethod() + { + PaymentTypeId = 0; + } + + 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/IPaymentMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentMethod.cs new file mode 100644 index 00000000..16d1ed6c --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentMethod.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using EShopOnAbp.PaymentService.PaymentRequests; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public interface IPaymentMethod : ITransientDependency +{ + public int PaymentTypeId { get; } + 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/PaymentMethodResolver.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentMethodResolver.cs new file mode 100644 index 00000000..7973da79 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentMethodResolver.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +public class PaymentMethodResolver : ITransientDependency +{ + private readonly IEnumerable _paymentMethods; + private readonly ILogger _logger; + + public PaymentMethodResolver(IEnumerable paymentMethods, ILogger logger) + { + _paymentMethods = paymentMethods; + _logger = logger; + } + + public IPaymentMethod Resolve(int paymentTypeId) + { + IPaymentMethod paymentMethod = _paymentMethods.FirstOrDefault(q => q.PaymentTypeId == paymentTypeId); + if (paymentMethod == null) + { + _logger.LogError($"Couldn't find Payment method with id:{paymentTypeId}"); + throw new ArgumentException("Payment method not found", paymentTypeId.ToString()); + } + + return paymentMethod; + } +} \ No newline at end of file diff --git a/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalMethod.cs b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalMethod.cs new file mode 100644 index 00000000..c660f112 --- /dev/null +++ b/services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalMethod.cs @@ -0,0 +1,98 @@ +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; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.PaymentService.PaymentServices; + +[ExposeServices(typeof(IPaymentMethod), typeof(PaypalMethod))] +public class PaypalMethod : IPaymentMethod +{ + private readonly PayPalHttpClient _payPalHttpClient; + private readonly PaymentRequestDomainService _paymentRequestDomainService; + public int PaymentTypeId { get; } + + public PaypalMethod(PayPalHttpClient payPalHttpClient, PaymentRequestDomainService paymentRequestDomainService) + { + _payPalHttpClient = payPalHttpClient; + _paymentRequestDomainService = paymentRequestDomainService; + PaymentTypeId = 1; + } + + 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.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs index c59692b4..5295d312 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs @@ -15,11 +15,11 @@ namespace EShopOnAbp.PaymentService.Controllers.ClientProxies [ExposeServices(typeof(IPaymentRequestAppService), typeof(PaymentRequestClientProxy))] public partial class PaymentRequestClientProxy : ClientProxyBase, IPaymentRequestAppService { - public virtual async Task CompleteAsync(string token) + public virtual async Task CompleteAsync(PaymentRequestCompleteInputDto input) { return await RequestAsync(nameof(CompleteAsync), new ClientProxyRequestTypeValue { - { typeof(string), token } + { typeof(PaymentRequestCompleteInputDto), input } }); } diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/payment-generate-proxy.json b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/payment-generate-proxy.json index 7f45f280..ad90fbc0 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/payment-generate-proxy.json +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/payment-generate-proxy.json @@ -14,33 +14,33 @@ } ], "actions": { - "CompleteAsyncByToken": { - "uniqueName": "CompleteAsyncByToken", + "CompleteAsyncByInput": { + "uniqueName": "CompleteAsyncByInput", "name": "CompleteAsync", "httpMethod": "POST", "url": "api/payment/requests/complete", "supportedVersions": [], "parametersOnMethod": [ { - "name": "token", - "typeAsString": "System.String, System.Private.CoreLib", - "type": "System.String", - "typeSimple": "string", + "name": "input", + "typeAsString": "EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestCompleteInputDto, EShopOnAbp.PaymentService.Application.Contracts", + "type": "EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestCompleteInputDto", + "typeSimple": "EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestCompleteInputDto", "isOptional": false, "defaultValue": null } ], "parameters": [ { - "nameOnMethod": "token", - "name": "token", + "nameOnMethod": "input", + "name": "input", "jsonName": null, - "type": "System.String", - "typeSimple": "string", + "type": "EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestCompleteInputDto", + "typeSimple": "EShopOnAbp.PaymentService.PaymentRequests.PaymentRequestCompleteInputDto", "isOptional": false, "defaultValue": null, "constraintTypes": null, - "bindingSourceId": "ModelBinding", + "bindingSourceId": "Body", "descriptorName": "" } ], 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"); diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs index 7325ed28..4c73bf1c 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs @@ -20,9 +20,9 @@ namespace EShopOnAbp.PaymentService.Controllers } [HttpPost("complete")] - public Task CompleteAsync(string token) + public Task CompleteAsync(PaymentRequestCompleteInputDto input) { - return PaymentRequestAppService.CompleteAsync(token); + return PaymentRequestAppService.CompleteAsync(input); } [HttpPost] @@ -47,4 +47,4 @@ namespace EShopOnAbp.PaymentService.Controllers return PaymentRequestAppService.StartAsync(input); } } -} +} \ No newline at end of file diff --git a/services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs b/services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs index 406f5e10..f21634a0 100644 --- a/services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs +++ b/services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs @@ -18,12 +18,12 @@ namespace EShopOnAbp.PaymentService.PaymentRequests public async Task Should_Insert_Payment_Request() { var id = Guid.NewGuid(); - var paymentRequest = new PaymentRequest(id, "USD"); - + var paymentRequest = new PaymentRequest(id, "123",456,"USD"); + await _paymentRequestRepository.InsertAsync(paymentRequest, autoSave: true); - + var inserted = await _paymentRequestRepository.GetAsync(id); - + inserted.Id.ShouldNotBe(Guid.Empty); } }