diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs index 9d9fdc91..912b968f 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs @@ -56,7 +56,6 @@ namespace EasyAbp.EShop.Orders.Orders return order; } - // Todo: should handler the inventory rollback. [UnitOfWork] public virtual async Task CancelAsync(Order order, string cancellationReason) { diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs new file mode 100644 index 00000000..756c9eca --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.EShop.Products.Products; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace EasyAbp.EShop.Orders.Orders; + +public class InventoryReductionResultTests : OrdersDomainTestBase +{ + private Order Order1 { get; set; } + + 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 + )); + + orderRepository.GetAsync(OrderTestData.Order1Id).Returns(Task.FromResult(Order1)); + + services.AddTransient(_ => orderRepository); + } + + [Fact] + public async Task Should_Cancel_Order_If_Reduction_Failed_After_Placed() + { + typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null); + typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null); + Order1.SetReducedInventoryAfterPlacingTime(null); + Order1.SetReducedInventoryAfterPaymentTime(null); + Order1.SetOrderStatus(OrderStatus.Pending); + Order1.SetPaymentId(null); + Order1.SetPaidTime(null); + + var handler = ServiceProvider.GetRequiredService(); + + await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPlacedResultEto() + { + TenantId = null, + OrderId = OrderTestData.Order1Id, + IsSuccess = false + }); + + Order1.CanceledTime.ShouldNotBeNull(); + Order1.CancellationReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason); + Order1.ReducedInventoryAfterPlacingTime.ShouldBeNull(); + Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull(); + } + + [Fact] + public async Task Should_Not_Cancel_Order_If_Reduction_Succeeded_After_Placed() + { + typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null); + typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null); + Order1.SetReducedInventoryAfterPlacingTime(null); + Order1.SetReducedInventoryAfterPaymentTime(null); + Order1.SetOrderStatus(OrderStatus.Pending); + Order1.SetPaymentId(null); + Order1.SetPaidTime(null); + + var handler = ServiceProvider.GetRequiredService(); + + await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPlacedResultEto() + { + TenantId = null, + OrderId = OrderTestData.Order1Id, + IsSuccess = true + }); + + Order1.CanceledTime.ShouldBeNull(); + Order1.CancellationReason.ShouldBeNull(); + Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull(); + Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull(); + } + + [Fact] + public async Task Should_Cancel_Order_And_Refund_If_Reduction_Failed_After_Paid() + { + typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null); + typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null); + Order1.SetReducedInventoryAfterPlacingTime(DateTime.Now); + Order1.SetReducedInventoryAfterPaymentTime(null); + Order1.SetOrderStatus(OrderStatus.Processing); + Order1.SetPaymentId(OrderTestData.Payment1Id); + Order1.SetPaidTime(DateTime.Now); + + var handler = ServiceProvider.GetRequiredService(); + + await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPaidResultEto() + { + TenantId = null, + OrderId = OrderTestData.Order1Id, + IsSuccess = false + }); + + var eventData = TestRefundOrderEventHandler.LastEto; + TestRefundOrderEventHandler.LastEto = null; + eventData.ShouldNotBeNull(); + eventData.DisplayReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason); + eventData.StaffRemark.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason); + eventData.CustomerRemark.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason); + eventData.PaymentId.ShouldBe(OrderTestData.Payment1Id); + eventData.TenantId.ShouldBeNull(); + eventData.OrderId.ShouldBe(OrderTestData.Order1Id); + + eventData.OrderLines.Count.ShouldBe(1); + var orderLine = eventData.OrderLines[0]; + orderLine.OrderLineId.ShouldBe(OrderTestData.OrderLine1Id); + orderLine.Quantity.ShouldBe(2); + orderLine.TotalAmount.ShouldBe(1m); + + eventData.OrderExtraFees.Count.ShouldBe(1); + var orderExtraFee = eventData.OrderExtraFees[0]; + orderExtraFee.Name.ShouldBe("Name"); + orderExtraFee.Key.ShouldBe("Key"); + orderExtraFee.TotalAmount.ShouldBe(0.3m); + + Order1.CanceledTime.ShouldNotBeNull(); + Order1.CancellationReason.ShouldBe(OrdersConsts.InventoryReductionFailedAutoCancellationReason); + Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull(); + Order1.ReducedInventoryAfterPaymentTime.ShouldBeNull(); + } + + [Fact] + public async Task Should_Not_Cancel_And_Refund_Order_If_Reduction_Succeeded_After_Paid() + { + typeof(Order).GetProperty(nameof(Order.CanceledTime))!.SetValue(Order1, null); + typeof(Order).GetProperty(nameof(Order.CancellationReason))!.SetValue(Order1, null); + Order1.SetReducedInventoryAfterPlacingTime(DateTime.Now); + Order1.SetReducedInventoryAfterPaymentTime(null); + Order1.SetOrderStatus(OrderStatus.Processing); + Order1.SetPaymentId(OrderTestData.Payment1Id); + Order1.SetPaidTime(DateTime.Now); + + var handler = ServiceProvider.GetRequiredService(); + + await handler.HandleEventAsync(new ProductInventoryReductionAfterOrderPaidResultEto() + { + TenantId = null, + OrderId = OrderTestData.Order1Id, + IsSuccess = true + }); + + var eventData = TestRefundOrderEventHandler.LastEto; + TestRefundOrderEventHandler.LastEto = null; + eventData.ShouldBeNull(); + + Order1.CanceledTime.ShouldBeNull(); + Order1.CancellationReason.ShouldBeNull(); + Order1.ReducedInventoryAfterPlacingTime.ShouldNotBeNull(); + Order1.ReducedInventoryAfterPaymentTime.ShouldNotBeNull(); + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs new file mode 100644 index 00000000..52c6555b --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/TestRefundOrderEventHandler.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using EasyAbp.EShop.Payments.Refunds; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace EasyAbp.EShop.Orders.Orders; + +public class TestRefundOrderEventHandler : IDistributedEventHandler, ITransientDependency +{ + public static RefundOrderEto LastEto { get; set; } + + public Task HandleEventAsync(RefundOrderEto eventData) + { + LastEto = eventData; + + return Task.CompletedTask; + } +} \ No newline at end of file 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 2e580825..7ab2f1a6 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 @@ -47,7 +47,8 @@ namespace EasyAbp.EShop.Payments.Refunds 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(nameof(paymentItem.StoreId), PaymentsTestData.Store1); + paymentItemType.GetProperty(nameof(PaymentItem.StoreId))?.SetValue(paymentItem, PaymentsTestData.Store1); + // paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1); var payment = Activator.CreateInstance(paymentType, true) as Payment; payment.ShouldNotBeNull(); @@ -75,7 +76,9 @@ namespace EasyAbp.EShop.Payments.Refunds ?.SetValue(paymentItem, PaymentsConsts.PaymentItemType); paymentItemType.GetProperty(nameof(PaymentItem.ItemKey)) ?.SetValue(paymentItem, PaymentsTestData.Order1.ToString()); - paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1); + paymentItemType.GetProperty(nameof(PaymentItem.StoreId)) + ?.SetValue(paymentItem, PaymentsTestData.Store1); + // paymentItem.ExtraProperties.Add(nameof(paymentItem.StoreId), PaymentsTestData.Store1); var payment = Activator.CreateInstance(paymentType, true) as Payment; payment.ShouldNotBeNull(); @@ -182,10 +185,9 @@ namespace EasyAbp.EShop.Payments.Refunds // Act & Assert await _refundAppService.CreateAsync(request); - - _testRefundPaymentEventHandler.IsEventPublished.ShouldBe(true); - var eventData = _testRefundPaymentEventHandler.EventData; + var eventData = TestRefundPaymentEventHandler.LastEto; + TestRefundPaymentEventHandler.LastEto = null; eventData.ShouldNotBeNull(); eventData.CreateRefundInput.RefundItems.Count.ShouldBe(1); diff --git a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs new file mode 100644 index 00000000..e1f7b468 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using EasyAbp.EShop.Payments.Payments; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using NSubstitute.Core; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Json; +using Xunit; + +namespace EasyAbp.EShop.Payments.Refunds; + +public class RefundOrderEventHandlerTests : PaymentsDomainTestBase +{ + private readonly IJsonSerializer _jsonSerializer; + + public RefundOrderEventHandlerTests() + { + _jsonSerializer = ServiceProvider.GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + MockPaymentRepository(services); + } + + private static void MockPaymentRepository(IServiceCollection services) + { + var paymentRepository = Substitute.For(); + + Payment Payment1Returns(CallInfo _) + { + 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); + paymentItemType.GetProperty(nameof(PaymentItem.ItemType)) + ?.SetValue(paymentItem, PaymentsConsts.PaymentItemType); + paymentItemType.GetProperty(nameof(PaymentItem.ItemKey)) + ?.SetValue(paymentItem, PaymentsTestData.Order1.ToString()); + paymentItemType.GetProperty(nameof(PaymentItem.StoreId)) + ?.SetValue(paymentItem, PaymentsTestData.Store1); + + 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); + paymentType.GetProperty(nameof(Payment.PaymentItems)) + ?.SetValue(payment, new List { paymentItem }); + + return payment; + } + + paymentRepository.GetAsync(PaymentsTestData.Payment1).Returns(Payment1Returns); + paymentRepository.FindAsync(PaymentsTestData.Payment1).Returns(Payment1Returns); + + services.AddTransient(_ => paymentRepository); + } + + [Fact] + public async Task Should_Refund_Order() + { + var handler = ServiceProvider.GetRequiredService(); + + var eto = new RefundOrderEto(null, PaymentsTestData.Order1, PaymentsTestData.Store1, + PaymentsTestData.Payment1, "Test", null, null); + + eto.OrderLines.Add(new OrderLineRefundInfoModel + { + OrderLineId = PaymentsTestData.OrderLine1, + Quantity = 2, + TotalAmount = 0.4m + }); + + eto.OrderExtraFees.Add(new OrderExtraFeeRefundInfoModel + { + Name = "Name", + Key = "Key", + TotalAmount = 0.6m + }); + + await handler.HandleEventAsync(eto); + + var eventData = TestRefundPaymentEventHandler.LastEto; + TestRefundPaymentEventHandler.LastEto = null; + eventData.ShouldNotBeNull(); + eventData.CreateRefundInput.RefundItems.Count.ShouldBe(1); + + var refundItem = eventData.CreateRefundInput.RefundItems[0]; + refundItem.GetProperty(nameof(RefundItem.OrderId)).ShouldBe(PaymentsTestData.Order1); + + var orderLines = + _jsonSerializer.Deserialize>( + refundItem.GetProperty(nameof(RefundItem.OrderLines))); + + orderLines.Count.ShouldBe(1); + orderLines[0].OrderLineId.ShouldBe(PaymentsTestData.OrderLine1); + orderLines[0].Quantity.ShouldBe(2); + orderLines[0].TotalAmount.ShouldBe(0.4m); + + var orderExtraFees = + _jsonSerializer.Deserialize>( + refundItem.GetProperty(nameof(RefundItem.OrderExtraFees))); + + orderExtraFees.Count.ShouldBe(1); + orderExtraFees[0].Name.ShouldBe("Name"); + orderExtraFees[0].Key.ShouldBe("Key"); + orderExtraFees[0].TotalAmount.ShouldBe(0.6m); + } +} \ No newline at end of file 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.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs similarity index 58% rename from modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs rename to modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs index d84a9ef7..b71a2d06 100644 --- a/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs +++ b/modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/TestRefundPaymentEventHandler.cs @@ -5,17 +5,14 @@ using Volo.Abp.EventBus.Distributed; namespace EasyAbp.EShop.Payments.Refunds { - public class TestRefundPaymentEventHandler : IDistributedEventHandler, ISingletonDependency + public class TestRefundPaymentEventHandler : IDistributedEventHandler, ITransientDependency { - public bool IsEventPublished { get; protected set; } - - public RefundPaymentEto EventData { get; protected set; } - + public static RefundPaymentEto LastEto { get; set; } + public Task HandleEventAsync(RefundPaymentEto eventData) { - IsEventPublished = true; - EventData = eventData; - + LastEto = eventData; + return Task.CompletedTask; } }