Browse Source

Merge pull request #57 from abpframework/gterdem/payment_ui

eShopOnAbp: Design Payment UI
pull/61/head
Enis Necipoglu 4 years ago
committed by GitHub
parent
commit
7b2bcb147c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 65
      apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml
  2. 13
      apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/PaymentWidgetViewComponent.cs
  3. 1
      apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPaymentConsts.cs
  4. 1
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml
  5. 5
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml.cs
  6. 15
      apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs
  7. 24
      apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/PaymentTypeProvider.cs
  8. 4
      apps/public-web/src/EShopOnAbp.PublicWeb/ServiceProviders/UserAddressProvider.cs
  9. 12
      apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.css
  10. 18
      apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/payment/payment-widget.js
  11. 6
      services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs
  12. 2
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs
  13. 10
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestCompleteInputDto.cs
  14. 1
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/PaymentRequestStartDto.cs
  15. 1
      services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs
  16. 132
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestAppService.cs
  17. 38
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestByPassAppService.cs
  18. 21
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServiceApplicationModule.cs
  19. 34
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/DemoPaymentMethod.cs
  20. 12
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/IPaymentMethod.cs
  21. 31
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaymentMethodResolver.cs
  22. 98
      services/payment/src/EShopOnAbp.PaymentService.Application/PaymentServices/PaypalMethod.cs
  23. 2
      services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PayPalConsts.cs
  24. 39
      services/payment/src/EShopOnAbp.PaymentService.Domain/PaymentRequests/PaymentRequestDomainService.cs
  25. 4
      services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs
  26. 22
      services/payment/src/EShopOnAbp.PaymentService.HttpApi.Client/ClientProxies/payment-generate-proxy.json
  27. 6
      services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/PaymentServiceHttpApiHostModule.cs
  28. 6
      services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs
  29. 8
      services/payment/test/EShopOnAbp.PaymentService.EntityFrameworkCore.Tests/PaymentRequests/PaymentRequestRepository_Tests.cs

65
apps/public-web/src/EShopOnAbp.PublicWeb/Components/Payment/Default.cshtml

@ -12,40 +12,47 @@
<div class="row">
<div class="col-lg-9">
<div class="address-list p-5">
<h3>Select Address</h3>
@for (int i = 0; i < Math.Ceiling(Model.Address.Count / (double) defaultColumnSize); i++)
{
<abp-row>
@for (int j = 0; j < defaultColumnSize; j++)
{
<abp-column size-lg="_3" size-md="_4" size-sm="_6">
@if (i * defaultColumnSize + j < Model.Address.Count)
{
var address = Model.Address[(i * defaultColumnSize) + j];
string isSelected = address.IsDefault ? "is-selected" : string.Empty;
<div class="card @isSelected" data-address-id="@address.Id">
<div class="card-header"><h4>@address.Type</h4></div>
<div class="card-body">
<p class="card-text">@address.ToString()</p>
</div>
<div class="card-footer">@address.Description</div>
</div>
}
</abp-column>
}
</abp-row>
}
<h5>Select Address</h5>
<abp-row>
@foreach (var address in Model.Address)
{
string isSelectedAddressClass = address.IsDefault ? "is-selected" : string.Empty;
<abp-column size="_3">
<div class="card @isSelectedAddressClass" data-address-id="@address.Id">
<div class="card-header">
<h4>@address.Type</h4>
</div>
<div class="card-body">
<p class="card-text">@address.ToString()</p>
</div>
<div class="card-footer">@address.Type</div>
</div>
</abp-column>
}
</abp-row>
</div>
<div class="payment-list p-5">
<h3>Select Payment Method</h3>
<h5>Select Payment Method</h5>
<abp-row>
<abp-column size-lg="_3" size-md="_4" size-sm="_6">
<abp-card class="is-selected" data-payment-id="1">
<img class="" src="https://upload.wikimedia.org/wikipedia/commons/b/b5/PayPal.svg" alt="paypal logo">
</abp-card>
</abp-column>
@foreach (var paymentType in Model.PaymentTypes)
{
string isSelectedClass = paymentType.IsDefault ? "is-selected" : "";
<abp-column size="_2">
<abp-card class="@isSelectedClass" data-payment-id="@paymentType.Id">
<abp-card-body>
<p class="card-title payment-type-header" style="text-align: center">
@paymentType.Name
</p>
<p class="card-text">
<i class="fa fa-5x @paymentType.IconCss"></i>
</p>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
</div>
</div>
<div class="col-lg-3">

13
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<IViewComponentResult> 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<AddressDto> Address { get; set; }
public List<PaymentType> PaymentTypes { get; set; }
}

1
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
{

1
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Payment.cshtml

@ -11,7 +11,6 @@
}
<div class="row">
@* <h3 class="pt-5 pb-4 text-center">@L["Payment"]</h3> *@
<div id="PaymentArea" class="pt-5">
@await Component.InvokeAsync(typeof(PaymentWidgetViewComponent))
</div>

5
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);

15
apps/public-web/src/EShopOnAbp.PublicWeb/Pages/PaymentCompleted.cshtml.cs

@ -24,12 +24,23 @@ public class PaymentCompletedModel : AbpPageModel
public async Task<IActionResult> 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();

24
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<PaymentType> GetPaymentTypes()
{
return new List<PaymentType>
{
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;
}

4
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; }

12
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;
}

18
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 {

6
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<PaymentType> List() =>
new[] {Paypal, Demo};
new[] {Demo, Paypal};
public static PaymentType FromName(string name)
{
var state = List()

2
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs

@ -9,7 +9,7 @@ namespace EShopOnAbp.PaymentService.PaymentRequests
Task<PaymentRequestStartResultDto> StartAsync(PaymentRequestStartDto input);
Task<PaymentRequestDto> CompleteAsync(string token);
Task<PaymentRequestDto> CompleteAsync(PaymentRequestCompleteInputDto input);
Task<bool> HandleWebhookAsync(string payload);
}

10
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; }
}

1
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]

1
services/payment/src/EShopOnAbp.PaymentService.Application.Contracts/PaymentServiceConsts.cs

@ -2,6 +2,5 @@
{
public static class PaymentServiceConsts
{
public static bool ByPassPaymentProvider { get; set; }
}
}

132
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<PaymentRequestDto> 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<PaymentRequestStartResultDto> 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<PurchaseUnitRequest>
{
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<Order>();
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<PaymentRequestDto> CompleteAsync(string token)
public virtual async Task<PaymentRequestDto> CompleteAsync(PaymentRequestCompleteInputDto input)
{
var request = new OrdersCaptureRequest(token);
request.RequestBody(new OrderActionRequest());
var order = (await PayPalHttpClient.Execute(request)).Result<Order>();
var paymentRequest = await UpdatePaymentRequestStateAsync(order);
var paymentService = _paymentMethodResolver.Resolve(input.PaymentTypeId);
var paymentRequest = await paymentService.CompleteAsync(PaymentRequestRepository, input.Token);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
@ -130,34 +80,12 @@ namespace EShopOnAbp.PaymentService.PaymentRequests
var response = await PayPalHttpClient.Execute(request);
order = response.Result<Order>();
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<PaymentRequest> 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;
}
}
}
}

38
services/payment/src/EShopOnAbp.PaymentService.Application/PaymentRequests/PaymentRequestByPassAppService.cs

@ -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<PaymentRequestStartResultDto> StartAsync(PaymentRequestStartDto input)
{
return Task.FromResult(new PaymentRequestStartResultDto
{
CheckoutLink = input.ReturnUrl + "?token=" + input.PaymentRequestId
});
}
public override async Task<PaymentRequestDto> CompleteAsync(string token)
{
var paymentRequest = await PaymentRequestRepository.GetAsync(Guid.Parse(token));
paymentRequest.SetAsCompleted();
await PaymentRequestRepository.UpdateAsync(paymentRequest);
return ObjectMapper.Map<PaymentRequest, PaymentRequestDto>(paymentRequest);
}
}
}

21
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<IPaymentRequestAppService>(provider =>
{
if (PaymentServiceConsts.ByPassPaymentProvider)
{
return provider.GetRequiredService<PaymentRequestByPassAppService>();
}
else
{
return provider.GetRequiredService<PaymentRequestAppService>();
}
});
context.Services.AddTransient<PaymentMethodResolver>(provider => new PaymentMethodResolver(
provider.GetServices<IPaymentMethod>(),
provider.GetRequiredService<ILogger<PaymentMethodResolver>>()
));
}
}
}
}

34
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<PaymentRequestStartResultDto> StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input)
{
return Task.FromResult(new PaymentRequestStartResultDto
{
CheckoutLink = input.ReturnUrl + "?token=" + input.PaymentRequestId
});
}
public async Task<PaymentRequest> CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token)
{
var paymentRequest = await paymentRequestRepository.GetAsync(Guid.Parse(token));
paymentRequest.SetAsCompleted();
return await paymentRequestRepository.UpdateAsync(paymentRequest);
}
}

12
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<PaymentRequestStartResultDto> StartAsync(PaymentRequest paymentRequest, PaymentRequestStartDto input);
public Task<PaymentRequest> CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token);
}

31
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<IPaymentMethod> _paymentMethods;
private readonly ILogger<PaymentMethodResolver> _logger;
public PaymentMethodResolver(IEnumerable<IPaymentMethod> paymentMethods, ILogger<PaymentMethodResolver> 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;
}
}

98
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<PaymentRequestStartResultDto> 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<PurchaseUnitRequest>
{
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<Order>();
return new PaymentRequestStartResultDto
{
CheckoutLink = result.Links.First(x => x.Rel == "approve").Href
};
}
public async Task<PaymentRequest> CompleteAsync(IPaymentRequestRepository paymentRequestRepository, string token)
{
var request = new OrdersCaptureRequest(token);
request.RequestBody(new OrderActionRequest());
var order = (await _payPalHttpClient.Execute(request)).Result<Order>();
var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId);
return await _paymentRequestDomainService.UpdatePaymentRequestStateAsync(paymentRequestId, order.Status,
order.Id);
}
}

2
services/payment/src/EShopOnAbp.PaymentService.Application/PayPal/PayPalConsts.cs → services/payment/src/EShopOnAbp.PaymentService.Domain.Shared/PayPalConsts.cs

@ -1,4 +1,4 @@
namespace EShopOnAbp.PaymentService.PayPal
namespace EShopOnAbp.PaymentService
{
public static class PayPalConsts
{

39
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<PaymentRequest> 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;
}
}

4
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>, IPaymentRequestAppService
{
public virtual async Task<PaymentRequestDto> CompleteAsync(string token)
public virtual async Task<PaymentRequestDto> CompleteAsync(PaymentRequestCompleteInputDto input)
{
return await RequestAsync<PaymentRequestDto>(nameof(CompleteAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), token }
{ typeof(PaymentRequestCompleteInputDto), input }
});
}

22
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": ""
}
],

6
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");

6
services/payment/src/EShopOnAbp.PaymentService.HttpApi/Controllers/PaymentRequestController.cs

@ -20,9 +20,9 @@ namespace EShopOnAbp.PaymentService.Controllers
}
[HttpPost("complete")]
public Task<PaymentRequestDto> CompleteAsync(string token)
public Task<PaymentRequestDto> CompleteAsync(PaymentRequestCompleteInputDto input)
{
return PaymentRequestAppService.CompleteAsync(token);
return PaymentRequestAppService.CompleteAsync(input);
}
[HttpPost]
@ -47,4 +47,4 @@ namespace EShopOnAbp.PaymentService.Controllers
return PaymentRequestAppService.StartAsync(input);
}
}
}
}

8
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);
}
}

Loading…
Cancel
Save