diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs index e5385653..663c02d0 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs @@ -154,7 +154,7 @@ namespace EasyAbp.EShop.Orders.Orders return PaidTime.HasValue; } - public void Refund(Guid orderLineId, int quantity, decimal amount) + public void RefundOrderLine(Guid orderLineId, int quantity, decimal amount) { if (amount <= decimal.Zero) { @@ -163,12 +163,21 @@ namespace EasyAbp.EShop.Orders.Orders var orderLine = OrderLines.Single(x => x.Id == orderLineId); - if (orderLine.RefundedQuantity + quantity > orderLine.Quantity) + orderLine.Refund(quantity, amount); + + RefundAmount += amount; + } + + public void RefundOrderExtraFee([NotNull] string extraFeeName, [CanBeNull] string extraFeeKey, decimal amount) + { + if (amount <= decimal.Zero) { - throw new InvalidRefundQuantityException(quantity); + throw new InvalidRefundAmountException(amount); } - orderLine.Refund(quantity, amount); + var extraFee = OrderExtraFees.Single(x => x.Name == extraFeeName && x.Key == extraFeeKey); + + extraFee.Refund(amount); RefundAmount += amount; } diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs index 01c72d3a..1d2f85bd 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs @@ -15,6 +15,8 @@ namespace EasyAbp.EShop.Orders.Orders public virtual string Key { get; protected set; } public virtual decimal Fee { get; protected set; } + + public virtual decimal RefundAmount { get; protected set; } protected OrderExtraFee() { @@ -32,6 +34,11 @@ namespace EasyAbp.EShop.Orders.Orders Fee = fee; } + internal void Refund(decimal amount) + { + RefundAmount += amount; + } + public override object[] GetKeys() { return new object[] {OrderId, Name, Key}; diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs index dfcfbe8f..9967f0d2 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs @@ -115,6 +115,11 @@ namespace EasyAbp.EShop.Orders.Orders internal void Refund(int quantity, decimal amount) { + if (RefundedQuantity + quantity > Quantity) + { + throw new InvalidRefundQuantityException(quantity); + } + RefundedQuantity += quantity; RefundAmount += amount; } diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/RefundCompletedEventHandler.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/RefundCompletedEventHandler.cs index 6420926a..bd22e387 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/RefundCompletedEventHandler.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/RefundCompletedEventHandler.cs @@ -40,7 +40,12 @@ namespace EasyAbp.EShop.Orders.Orders foreach (var eto in refundItem.RefundItemOrderLines) { - order.Refund(eto.OrderLineId, eto.RefundedQuantity, eto.RefundAmount); + order.RefundOrderLine(eto.OrderLineId, eto.RefundedQuantity, eto.RefundAmount); + } + + foreach (var eto in refundItem.RefundItemOrderExtraFees) + { + order.RefundOrderExtraFee(eto.Name, eto.Key, eto.RefundAmount); } await _orderRepository.UpdateAsync(order, true); diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj index b1396ae3..d35e9448 100644 --- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj @@ -2,7 +2,7 @@ net6.0 - + EasyAbp.EShop.Orders diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs index 3aca6c0e..28aa602d 100644 --- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs @@ -1,4 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using EasyAbp.EShop.Payments.Refunds; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; using Shouldly; using Xunit; @@ -6,18 +12,200 @@ namespace EasyAbp.EShop.Orders.Orders { public class OrderDomainTests : OrdersDomainTestBase { + private Order Order1 { get; set; } + private readonly IOrderRepository _orderRepository; + public OrderDomainTests() { + _orderRepository = ServiceProvider.GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + var orderRepository = Substitute.For(); + Order1 = new Order( + OrderTestData.Order1Id, + null, + OrderTestData.Store1Id, + Guid.NewGuid(), + "CNY", + 1m, + 0m, + 1.5m, + 1.5m, + null, + null); + Order1.OrderLines.Add(new OrderLine( + OrderTestData.OrderLine1Id, + OrderTestData.Product1Id, + OrderTestData.ProductSku1Id, + null, + DateTime.Now, + null, + "Default", + "Default", + null, + "Product 1", + null, + null, + null, + "CNY", + 0.5m, + 1m, + 0m, + 1m, + 2 + )); + Order1.OrderExtraFees.Add(new OrderExtraFee( + OrderTestData.Order1Id, + "Name", + "Key", + 0.3m + )); + Order1.SetPaymentId(OrderTestData.Payment1Id); + Order1.SetPaidTime(DateTime.Now); + + orderRepository.GetAsync(OrderTestData.Order1Id).Returns(Task.FromResult(Order1)); + + services.AddTransient(_ => orderRepository); } [Fact] - public async Task Test1() + public async Task Should_Record_Refund() { - // Arrange + var handler = ServiceProvider.GetRequiredService(); + + await handler.HandleEventAsync(new EShopRefundCompletedEto + { + Refund = new EShopRefundEto + { + Id = Guid.NewGuid(), + TenantId = null, + PaymentId = OrderTestData.Payment1Id, + Currency = "CNY", + RefundAmount = 0.3m, + RefundItems = new List + { + new() + { + Id = Guid.NewGuid(), + PaymentItemId = Guid.NewGuid(), + RefundAmount = 0.3m, + StoreId = OrderTestData.Store1Id, + OrderId = OrderTestData.Order1Id, + RefundItemOrderLines = new List + { + new() + { + OrderLineId = OrderTestData.OrderLine1Id, + RefundedQuantity = 1, + RefundAmount = 0.2m + } + }, + RefundItemOrderExtraFees = new List + { + new() + { + Name = "Name", + Key = "Key", + RefundAmount = 0.1m + } + } + } + } + } + }); + + Order1.RefundAmount.ShouldBe(0.3m); + + var orderLine1 = Order1.OrderLines.Single(x => x.Id == OrderTestData.OrderLine1Id); + orderLine1.RefundAmount.ShouldBe(0.2m); + orderLine1.RefundedQuantity.ShouldBe(1); + + var extraFee = Order1.OrderExtraFees.Single(x => x.Name == "Name" && x.Key == "Key"); + extraFee.RefundAmount.ShouldBe(0.1m); + } - // Assert + [Fact] + public async Task Should_Avoid_Non_Positive_Refund_Amount() + { + var handler = ServiceProvider.GetRequiredService(); + + await Should.ThrowAsync(async () => + { + await handler.HandleEventAsync(new EShopRefundCompletedEto + { + Refund = new EShopRefundEto + { + Id = Guid.NewGuid(), + TenantId = null, + PaymentId = OrderTestData.Payment1Id, + Currency = "CNY", + RefundAmount = -1m, + RefundItems = new List + { + new() + { + Id = Guid.NewGuid(), + PaymentItemId = Guid.NewGuid(), + RefundAmount = -1m, + StoreId = OrderTestData.Store1Id, + OrderId = OrderTestData.Order1Id, + RefundItemOrderLines = new List + { + new() + { + OrderLineId = OrderTestData.OrderLine1Id, + RefundedQuantity = 1, + RefundAmount = -1m + } + } + } + } + } + }); + }); + } + + [Fact] + public async Task Should_Avoid_Over_Quantity_Refund() + { + var handler = ServiceProvider.GetRequiredService(); - // Assert + await Should.ThrowAsync(async () => + { + await handler.HandleEventAsync(new EShopRefundCompletedEto + { + Refund = new EShopRefundEto + { + Id = Guid.NewGuid(), + TenantId = null, + PaymentId = OrderTestData.Payment1Id, + Currency = "CNY", + RefundAmount = 0.3m, + RefundItems = new List + { + new() + { + Id = Guid.NewGuid(), + PaymentItemId = Guid.NewGuid(), + RefundAmount = 0.3m, + StoreId = OrderTestData.Store1Id, + OrderId = OrderTestData.Order1Id, + RefundItemOrderLines = new List + { + new() + { + OrderLineId = OrderTestData.OrderLine1Id, + RefundedQuantity = 3, + RefundAmount = 0.2m + } + } + } + } + } + }); + }); } } } diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs index b15a517a..a12c181d 100644 --- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs @@ -5,6 +5,12 @@ namespace EasyAbp.EShop.Orders { public class OrderTestData { + public static Guid Order1Id { get; } = Guid.NewGuid(); + + public static Guid OrderLine1Id { get; } = Guid.NewGuid(); + + public static Guid Payment1Id { get; } = Guid.NewGuid(); + public static Guid Store1Id { get; } = Guid.NewGuid(); public static Guid Product1Id { get; } = Guid.NewGuid(); diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundInput.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundInput.cs index 371ad60a..8007ddb8 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundInput.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundInput.cs @@ -35,11 +35,36 @@ namespace EasyAbp.EShop.Payments.Refunds.Dtos ); } - if (RefundItems.Any(x => x.OrderLines.IsNullOrEmpty())) + if (RefundItems.Any(x => x.OrderLines.IsNullOrEmpty() && x.OrderExtraFees.IsNullOrEmpty())) { yield return new ValidationResult( - "RefundItem.OrderLines should not be empty!", - new[] { nameof(CreateEShopRefundItemInput.OrderLines) } + "RefundItem.OrderLines and RefundItem.OrderExtraFees should not both be empty!", + new[] + { + nameof(CreateEShopRefundItemInput.OrderLines), nameof(CreateEShopRefundItemInput.OrderExtraFees) + } + ); + } + + if (RefundItems.SelectMany(x => x.OrderLines).Any(x => x.TotalAmount <= decimal.Zero)) + { + yield return new ValidationResult( + "RefundAmount should be greater than 0.", + new[] + { + nameof(OrderLineRefundInfoModel.TotalAmount) + } + ); + } + + if (RefundItems.SelectMany(x => x.OrderExtraFees).Any(x => x.TotalAmount <= decimal.Zero)) + { + yield return new ValidationResult( + "RefundAmount should be greater than 0.", + new[] + { + nameof(OrderExtraFeeRefundInfoModel.TotalAmount) + } ); } } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs index 89513772..0eb0c99b 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs @@ -16,6 +16,8 @@ namespace EasyAbp.EShop.Payments.Refunds.Dtos [CanBeNull] public string StaffRemark { get; set; } - public List OrderLines { get; set; } = new List(); + public List OrderLines { get; set; } = new(); + + public List OrderExtraFees { get; set; } = new(); } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/AnotherRefundTaskIsOnGoingException.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/AnotherRefundTaskIsOnGoingException.cs new file mode 100644 index 00000000..787a1ae4 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/AnotherRefundTaskIsOnGoingException.cs @@ -0,0 +1,13 @@ +using System; +using Volo.Abp; + +namespace EasyAbp.EShop.Payments.Refunds +{ + public class AnotherRefundTaskIsOnGoingException : BusinessException + { + public AnotherRefundTaskIsOnGoingException(Guid id) : base(PaymentsErrorCodes.AnotherRefundTaskIsOnGoing) + { + WithData(nameof(id), id); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/InvalidRefundAmountException.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/InvalidRefundAmountException.cs new file mode 100644 index 00000000..f8e2575e --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/InvalidRefundAmountException.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp; + +namespace EasyAbp.EShop.Payments.Refunds +{ + public class InvalidRefundAmountException : BusinessException + { + public InvalidRefundAmountException(Guid paymentId, Guid paymentItemId, decimal refundAmount) : base( + PaymentsErrorCodes.InvalidRefundAmount) + { + WithData(nameof(paymentId), paymentId); + WithData(nameof(paymentItemId), paymentItemId); + WithData(nameof(refundAmount), refundAmount); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs index 919fa562..4df5f9bd 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs @@ -85,6 +85,11 @@ namespace EasyAbp.EShop.Payments.Refunds var payment = await _paymentRepository.GetAsync(input.PaymentId); + if (payment.PendingRefundAmount != decimal.Zero) + { + throw new AnotherRefundTaskIsOnGoingException(payment.Id); + } + var createRefundInput = new CreateRefundInput { PaymentId = input.PaymentId, @@ -103,24 +108,32 @@ namespace EasyAbp.EShop.Payments.Refunds { throw new OrderIsNotInSpecifiedPaymentException(order.Id, payment.Id); } - + await AuthorizationService.CheckMultiStorePolicyAsync(paymentItem.StoreId, PaymentsPermissions.Refunds.Manage, PaymentsPermissions.Refunds.CrossStore); - foreach (var orderLineRefundInfoModel in refundItem.OrderLines) + var refundAmount = refundItem.OrderLines.Sum(x => x.TotalAmount) + + refundItem.OrderExtraFees.Sum(x => x.TotalAmount); + + if (refundAmount + paymentItem.RefundAmount > paymentItem.ActualPaymentAmount) + { + throw new InvalidRefundAmountException(payment.Id, paymentItem.Id, refundAmount); + } + + foreach (var model in refundItem.OrderLines) { - var orderLine = order.OrderLines.Single(x => x.Id == orderLineRefundInfoModel.OrderLineId); + var orderLine = order.OrderLines.Single(x => x.Id == model.OrderLineId); - if (orderLine.RefundedQuantity + orderLineRefundInfoModel.Quantity > orderLine.Quantity) + if (orderLine.RefundedQuantity + model.Quantity > orderLine.Quantity) { - throw new InvalidRefundQuantityException(orderLineRefundInfoModel.Quantity); + throw new InvalidRefundQuantityException(model.Quantity); } } var eto = new CreateRefundItemInput { PaymentItemId = paymentItem.Id, - RefundAmount = refundItem.OrderLines.Sum(x => x.TotalAmount), + RefundAmount = refundAmount, CustomerRemark = refundItem.CustomerRemark, StaffRemark = refundItem.StaffRemark }; @@ -128,6 +141,7 @@ namespace EasyAbp.EShop.Payments.Refunds eto.SetProperty("StoreId", order.StoreId.ToString()); eto.SetProperty("OrderId", order.Id.ToString()); eto.SetProperty("OrderLines", _jsonSerializer.Serialize(refundItem.OrderLines)); + eto.SetProperty("OrderExtraFees", _jsonSerializer.Serialize(refundItem.OrderExtraFees)); createRefundInput.RefundItems.Add(eto); } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json index a6968e9b..f595f307 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json @@ -10,6 +10,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json index 1bc21dbe..3afdca8e 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json @@ -11,6 +11,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json index 283301ae..02ac89d7 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json @@ -10,6 +10,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json index bef301d1..03a75e63 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json @@ -10,6 +10,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json index a447e1c6..fabd6175 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json @@ -11,6 +11,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json index ec1c0532..89722fa6 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json @@ -11,6 +11,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json index fdc51ba2..acb0e5ad 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json @@ -10,6 +10,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "Should create payments for each store.", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "The order ({orderId}) is not in the specified payment ({paymentId}).", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "Payment ({id}) has another ongoing refund task.", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "Refund amount ({refundAmount}) is invalid for the payment (id: {paymentId}, item id: {paymentItemId}).", "EasyAbp.EShop.Payments:OrderIdNotFound": "Cannot get valid OrderId from ExtraProperties.", "EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties." } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json index 51aea86e..ab595298 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json @@ -11,6 +11,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "应该为每个商店创建支付", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "退款数量({quantity})无效", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "订单({orderId})不在指定的支付({paymentId})中", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "支付({id})存在进行中的退款任务", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "退款金额({refundAmount})对于支付(id: {paymentId}, item id: {paymentItemId})不正确", "EasyAbp.EShop.Payments:OrderIdNotFound": "无法从ExtraProperties获得有效的OrderId", "EasyAbp.EShop.Payments:StoreIdNotFound": "无法从ExtraProperties获得有效的StoreId" } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json index a2f2eef6..3c3e2e7b 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json @@ -11,6 +11,8 @@ "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported": "應該為每個商店創建支付", "EasyAbp.EShop.Payments:InvalidRefundQuantity": "退款數量({quantity})無效", "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment": "訂單({orderId})不在指定的支付({paymentId})中", + "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing": "支付({id})存在進行中的退款任務", + "EasyAbp.EShop.Payments:InvalidRefundAmount": "退款金額({refundAmount})對於支付(id: {paymentId}, item id: {paymentItemId})不正確", "EasyAbp.EShop.Payments:OrderIdNotFound": "無法從ExtraProperties獲得有效的OrderId", "EasyAbp.EShop.Payments:StoreIdNotFound": "無法從ExtraProperties獲得有效的StoreId" } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs index 6a07234d..e4d7ce4b 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs @@ -5,6 +5,8 @@ public const string MultiStorePaymentNotSupported = "EasyAbp.EShop.Payments:MultiStorePaymentNotSupported"; public const string InvalidRefundQuantity = "EasyAbp.EShop.Payments:InvalidRefundQuantity"; public const string OrderIsNotInSpecifiedPayment = "EasyAbp.EShop.Payments:OrderIsNotInSpecifiedPayment"; + public const string AnotherRefundTaskIsOnGoing = "EasyAbp.EShop.Payments:AnotherRefundTaskIsOnGoing"; + public const string InvalidRefundAmount = "EasyAbp.EShop.Payments:InvalidRefundAmount"; public const string OrderIdNotFound = "EasyAbp.EShop.Payments:OrderIdNotFound"; public const string StoreIdNotFound = "EasyAbp.EShop.Payments:StoreIdNotFound"; } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/EShopRefundItemEto.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/EShopRefundItemEto.cs index 9d5118bb..2205196c 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/EShopRefundItemEto.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/EShopRefundItemEto.cs @@ -26,6 +26,8 @@ namespace EasyAbp.EShop.Payments.Refunds public Guid OrderId { get; set; } - public List RefundItemOrderLines { get; set; } = new List(); + public List RefundItemOrderLines { get; set; } = new(); + + public List RefundItemOrderExtraFees { get; set; } = new(); } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs new file mode 100644 index 00000000..e0062f51 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs @@ -0,0 +1,14 @@ +using System; + +namespace EasyAbp.EShop.Payments.Refunds +{ + [Serializable] + public class OrderExtraFeeRefundInfoModel + { + public string Name { get; set; } + + public string Key { get; set; } + + public decimal TotalAmount { get; set; } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs new file mode 100644 index 00000000..4aa7d73c --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs @@ -0,0 +1,14 @@ +using System; + +namespace EasyAbp.EShop.Payments.Refunds +{ + [Serializable] + public class RefundItemOrderExtraFeeEto + { + public string Name { get; set; } + + public string Key { get; set; } + + public decimal RefundAmount { get; set; } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs index 91f63bde..e4ea8985 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs @@ -31,6 +31,7 @@ namespace EasyAbp.EShop.Payments CreateMap(); CreateMap(); CreateMap(); + CreateMap(); } } } diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItem.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItem.cs index c80fe852..0bd17432 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItem.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItem.cs @@ -30,10 +30,13 @@ namespace EasyAbp.EShop.Payments.Refunds public virtual Guid OrderId { get; protected set; } public virtual List RefundItemOrderLines { get; protected set; } + + public virtual List RefundItemOrderExtraFees { get; protected set; } protected RefundItem() { RefundItemOrderLines = new List(); + RefundItemOrderExtraFees = new List(); ExtraProperties = new ExtraPropertyDictionary(); diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs new file mode 100644 index 00000000..ac11ebdc --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs @@ -0,0 +1,31 @@ +using System; +using AutoMapper; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace EasyAbp.EShop.Payments.Refunds +{ + [AutoMap(typeof(RefundItemOrderExtraFeeEto))] + public class RefundItemOrderExtraFee : Entity + { + [NotNull] + public virtual string Name { get; protected set; } + + [CanBeNull] + public virtual string Key { get; protected set; } + + public virtual decimal RefundAmount { get; protected set; } + + protected RefundItemOrderExtraFee() + { + } + + public RefundItemOrderExtraFee(Guid id, [NotNull] string name, [CanBeNull] string key, + decimal refundAmount) : base(id) + { + Name = name; + Key = key; + RefundAmount = refundAmount; + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs index 6b449163..32712b8f 100644 --- a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs @@ -80,7 +80,8 @@ namespace EasyAbp.EShop.Payments.Refunds }); FillRefundItemOrderLines(refund); - + FillRefundItemOrderExtraFees(refund); + await _refundRepository.InsertAsync(refund, true); if (refund.CompletedTime.HasValue) @@ -132,6 +133,7 @@ namespace EasyAbp.EShop.Payments.Refunds refund.RefundItems.RemoveAll(i => !etoRefundItemIds.Contains(i.Id)); FillRefundItemOrderLines(refund); + FillRefundItemOrderExtraFees(refund); await _refundRepository.UpdateAsync(refund, true); @@ -174,6 +176,38 @@ namespace EasyAbp.EShop.Payments.Refunds } } + protected virtual void FillRefundItemOrderExtraFees(Refund refund) + { + foreach (var refundItem in refund.RefundItems) + { + var orderExtraFeeInfoModels = + _jsonSerializer.Deserialize>( + refundItem.GetProperty("OrderExtraFees")); + + foreach (var orderExtraFeeInfoModel in orderExtraFeeInfoModels) + { + var refundItemOrderExtraFeeEntity = + refundItem.RefundItemOrderExtraFees.FirstOrDefault(x => + x.Name == orderExtraFeeInfoModel.Name && + x.Key == orderExtraFeeInfoModel.Key); + + if (refundItemOrderExtraFeeEntity == null) + { + refundItemOrderExtraFeeEntity = new RefundItemOrderExtraFee(_guidGenerator.Create(), + orderExtraFeeInfoModel.Name, orderExtraFeeInfoModel.Key, + orderExtraFeeInfoModel.TotalAmount); + + refundItem.RefundItemOrderExtraFees.Add(refundItemOrderExtraFeeEntity); + } + + var orderExtraFeeIds = orderExtraFeeInfoModels.Select(i => new { i.Name, i.Key }).ToList(); + + refundItem.RefundItemOrderExtraFees.RemoveAll( + i => !orderExtraFeeIds.Contains(new { i.Name, i.Key })); + } + } + } + protected virtual void FillRefundItemStoreId(RefundItem item) { if (!Guid.TryParse(item.GetProperty("StoreId"), out var storeId)) diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs index 02063e80..81cc70c3 100644 --- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs +++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs @@ -8,12 +8,16 @@ using EasyAbp.EShop.Payments.Refunds.Dtos; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Json; +using Volo.Abp.Validation; using Xunit; namespace EasyAbp.EShop.Payments.Refunds { public class RefundAppServiceTests : PaymentsApplicationTestBase { + private readonly IJsonSerializer _jsonSerializer; private readonly IRefundAppService _refundAppService; private readonly TestRefundPaymentEventHandler _testRefundPaymentEventHandler; @@ -26,6 +30,7 @@ namespace EasyAbp.EShop.Payments.Refunds private void MockPaymentRepository(IServiceCollection services) { var paymentRepository = Substitute.For(); + paymentRepository.GetAsync(PaymentsTestData.Payment1).Returns(x => { var paymentType = typeof(Payment); @@ -34,7 +39,7 @@ namespace EasyAbp.EShop.Payments.Refunds var paymentItem = Activator.CreateInstance(paymentItemType, true) as PaymentItem; paymentItem.ShouldNotBeNull(); paymentItemType.GetProperty(nameof(PaymentItem.Id))?.SetValue(paymentItem, PaymentsTestData.PaymentItem1); - paymentItemType.GetProperty(nameof(PaymentItem.ActualPaymentAmount))?.SetValue(paymentItem, 0m); + paymentItemType.GetProperty(nameof(PaymentItem.ActualPaymentAmount))?.SetValue(paymentItem, 1m); paymentItemType.GetProperty(nameof(PaymentItem.ItemType))?.SetValue(paymentItem, PaymentsConsts.PaymentItemType); paymentItemType.GetProperty(nameof(PaymentItem.ItemKey))?.SetValue(paymentItem, PaymentsTestData.Order1.ToString()); paymentItem.ExtraProperties.Add("StoreId", PaymentsTestData.Store1.ToString()); @@ -43,7 +48,34 @@ namespace EasyAbp.EShop.Payments.Refunds payment.ShouldNotBeNull(); paymentType.GetProperty(nameof(Payment.Id))?.SetValue(payment, PaymentsTestData.Payment1); paymentType.GetProperty(nameof(Payment.Currency))?.SetValue(payment, "CNY"); - paymentType.GetProperty(nameof(Payment.ActualPaymentAmount))?.SetValue(payment, 0m); + paymentType.GetProperty(nameof(Payment.ActualPaymentAmount))?.SetValue(payment, 1m); + paymentType.GetProperty(nameof(Payment.PaymentItems))?.SetValue(payment, new List {paymentItem}); + + return payment; + }); + + paymentRepository.GetAsync(PaymentsTestData.Payment2).Returns(x => + { + var paymentType = typeof(Payment); + var paymentItemType = typeof(PaymentItem); + + var paymentItem = Activator.CreateInstance(paymentItemType, true) as PaymentItem; + paymentItem.ShouldNotBeNull(); + paymentItemType.GetProperty(nameof(PaymentItem.Id))?.SetValue(paymentItem, PaymentsTestData.PaymentItem1); + paymentItemType.GetProperty(nameof(PaymentItem.ActualPaymentAmount))?.SetValue(paymentItem, 1m); + // pending refund amount + paymentItemType.GetProperty(nameof(PaymentItem.PendingRefundAmount))?.SetValue(paymentItem, 1m); + paymentItemType.GetProperty(nameof(PaymentItem.ItemType))?.SetValue(paymentItem, PaymentsConsts.PaymentItemType); + paymentItemType.GetProperty(nameof(PaymentItem.ItemKey))?.SetValue(paymentItem, PaymentsTestData.Order1.ToString()); + paymentItem.ExtraProperties.Add("StoreId", PaymentsTestData.Store1.ToString()); + + var payment = Activator.CreateInstance(paymentType, true) as Payment; + payment.ShouldNotBeNull(); + paymentType.GetProperty(nameof(Payment.Id))?.SetValue(payment, PaymentsTestData.Payment1); + paymentType.GetProperty(nameof(Payment.Currency))?.SetValue(payment, "CNY"); + paymentType.GetProperty(nameof(Payment.ActualPaymentAmount))?.SetValue(payment, 1m); + // pending refund amount + paymentType.GetProperty(nameof(Payment.PendingRefundAmount))?.SetValue(payment, 1m); paymentType.GetProperty(nameof(Payment.PaymentItems))?.SetValue(payment, new List {paymentItem}); return payment; @@ -67,7 +99,7 @@ namespace EasyAbp.EShop.Payments.Refunds { Id = PaymentsTestData.OrderLine1, Currency = "CNY", - ActualTotalPrice = 0, + ActualTotalPrice = 1m, Quantity = 1 } }, @@ -79,6 +111,7 @@ namespace EasyAbp.EShop.Payments.Refunds public RefundAppServiceTests() { + _jsonSerializer = GetRequiredService(); _refundAppService = GetRequiredService(); _testRefundPaymentEventHandler = GetRequiredService(); } @@ -96,18 +129,27 @@ namespace EasyAbp.EShop.Payments.Refunds StaffRemark = "StaffRemark", RefundItems = new List { - new CreateEShopRefundItemInput + new() { CustomerRemark = "CustomerRemark", OrderId = PaymentsTestData.Order1, StaffRemark = "StaffRemark", OrderLines = new List { - new OrderLineRefundInfoModel + new() { OrderLineId = PaymentsTestData.OrderLine1, Quantity = 1, - TotalAmount = 0 + TotalAmount = 0.4m + } + }, + OrderExtraFees = new List + { + new() + { + Name = "Name", + Key = "Key", + TotalAmount = 0.6m } } } @@ -118,6 +160,181 @@ namespace EasyAbp.EShop.Payments.Refunds await _refundAppService.CreateAsync(request); _testRefundPaymentEventHandler.IsEventPublished.ShouldBe(true); + + var eventData = _testRefundPaymentEventHandler.EventData; + eventData.ShouldNotBeNull(); + eventData.CreateRefundInput.RefundItems.Count.ShouldBe(1); + + var refundItem = eventData.CreateRefundInput.RefundItems[0]; + refundItem.GetProperty("OrderId").ShouldBe(PaymentsTestData.Order1); + + var orderLines = + _jsonSerializer.Deserialize>( + refundItem.GetProperty("OrderLines")); + + orderLines.Count.ShouldBe(1); + orderLines[0].OrderLineId.ShouldBe(PaymentsTestData.OrderLine1); + orderLines[0].Quantity.ShouldBe(1); + orderLines[0].TotalAmount.ShouldBe(0.4m); + + var orderExtraFees = + _jsonSerializer.Deserialize>( + refundItem.GetProperty("OrderExtraFees")); + + orderExtraFees.Count.ShouldBe(1); + orderExtraFees[0].Name.ShouldBe("Name"); + orderExtraFees[0].Key.ShouldBe("Key"); + orderExtraFees[0].TotalAmount.ShouldBe(0.6m); + } + + [Fact] + public async Task Should_Avoid_Empty_Refund() + { + // Arrange + var request = new CreateEShopRefundInput + { + DisplayReason = "Reason", + CustomerRemark = "Customer Remark", + PaymentId = PaymentsTestData.Payment1, + StaffRemark = "StaffRemark", + RefundItems = new List + { + new CreateEShopRefundItemInput + { + CustomerRemark = "CustomerRemark", + OrderId = PaymentsTestData.Order1, + StaffRemark = "StaffRemark", + OrderLines = new List(), // empty + OrderExtraFees = new List() // empty + } + } + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _refundAppService.CreateAsync(request); + }, "RefundItem.OrderLines and RefundItem.OrderExtraFees should not both be empty!"); + } + + [Fact] + public async Task Should_Avoid_Over_Refund() + { + // Arrange + var request = new CreateEShopRefundInput + { + DisplayReason = "Reason", + CustomerRemark = "Customer Remark", + PaymentId = PaymentsTestData.Payment1, + StaffRemark = "StaffRemark", + RefundItems = new List + { + new() + { + CustomerRemark = "CustomerRemark", + OrderId = PaymentsTestData.Order1, + StaffRemark = "StaffRemark", + OrderLines = new List + { + new() + { + OrderLineId = PaymentsTestData.OrderLine1, + Quantity = 1, + TotalAmount = 1m + } + }, + OrderExtraFees = new List + { + new() + { + Name = "Name", + Key = "Key", + TotalAmount = 0.1m + } + } + } + } + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _refundAppService.CreateAsync(request); + }); + } + + [Fact] + public async Task Should_Avoid_Concurrent_Refund() + { + // Arrange + var request = new CreateEShopRefundInput + { + DisplayReason = "Reason", + CustomerRemark = "Customer Remark", + PaymentId = PaymentsTestData.Payment2, + StaffRemark = "StaffRemark", + RefundItems = new List + { + new CreateEShopRefundItemInput + { + CustomerRemark = "CustomerRemark", + OrderId = PaymentsTestData.Order1, + StaffRemark = "StaffRemark", + OrderLines = new List + { + new OrderLineRefundInfoModel + { + OrderLineId = PaymentsTestData.OrderLine1, + Quantity = 1, + TotalAmount = 1m + } + } + } + } + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _refundAppService.CreateAsync(request); + }); + } + + [Fact] + public async Task Should_Avoid_Non_Positive_Refund_Amount() + { + // Arrange + var request = new CreateEShopRefundInput + { + DisplayReason = "Reason", + CustomerRemark = "Customer Remark", + PaymentId = PaymentsTestData.Payment1, + StaffRemark = "StaffRemark", + RefundItems = new List + { + new CreateEShopRefundItemInput + { + CustomerRemark = "CustomerRemark", + OrderId = PaymentsTestData.Order1, + StaffRemark = "StaffRemark", + OrderLines = new List + { + new OrderLineRefundInfoModel + { + OrderLineId = PaymentsTestData.OrderLine1, + Quantity = 1, + TotalAmount = -1m + } + } + } + } + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _refundAppService.CreateAsync(request); + }, "RefundAmount should be greater than 0."); } } } diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs index 316d84e8..d84a9ef7 100644 --- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs +++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs @@ -9,9 +9,12 @@ namespace EasyAbp.EShop.Payments.Refunds { public bool IsEventPublished { get; protected set; } + public RefundPaymentEto EventData { get; protected set; } + public Task HandleEventAsync(RefundPaymentEto eventData) { IsEventPublished = true; + EventData = eventData; return Task.CompletedTask; } diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.TestBase/PaymentsTestData.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.TestBase/PaymentsTestData.cs index b564f4c0..301ecfe1 100644 --- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.TestBase/PaymentsTestData.cs +++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.TestBase/PaymentsTestData.cs @@ -14,6 +14,8 @@ namespace EasyAbp.EShop.Payments public static Guid Payment1 { get; } = Guid.NewGuid(); + public static Guid Payment2 { get; } = Guid.NewGuid(); + public static Guid PaymentItem1 { get; } = Guid.NewGuid(); } } \ No newline at end of file