diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs index df729533..44ea4412 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersConsts.cs @@ -3,5 +3,6 @@ public static class OrdersConsts { public static string UnpaidAutoCancellationReason = "Order payment timed out and not paid"; + public static string InventoryReductionFailedAutoCancellationReason = "Insufficient inventory"; } } \ No newline at end of file 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/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs index 35c07fe5..2e126164 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs @@ -1,6 +1,12 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.EShop.Payments.Refunds; using EasyAbp.EShop.Products.Products; +using EasyAbp.PaymentService.Refunds; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; using Volo.Abp.Uow; @@ -11,18 +17,24 @@ namespace EasyAbp.EShop.Orders.Orders { private readonly IClock _clock; private readonly ICurrentTenant _currentTenant; + private readonly IOrderManager _orderManager; private readonly IOrderRepository _orderRepository; + private readonly IDistributedEventBus _distributedEventBus; public ProductInventoryReductionEventHandler( IClock clock, ICurrentTenant currentTenant, - IOrderRepository orderRepository) + IOrderManager orderManager, + IOrderRepository orderRepository, + IDistributedEventBus distributedEventBus) { _clock = clock; _currentTenant = currentTenant; + _orderManager = orderManager; _orderRepository = orderRepository; + _distributedEventBus = distributedEventBus; } - + [UnitOfWork(true)] public virtual async Task HandleEventAsync(ProductInventoryReductionAfterOrderPlacedResultEto eventData) { @@ -37,10 +49,11 @@ namespace EasyAbp.EShop.Orders.Orders if (!eventData.IsSuccess) { - // Todo: Cancel order. + await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason); + return; } - + order.SetReducedInventoryAfterPlacingTime(_clock.Now); await _orderRepository.UpdateAsync(order, true); @@ -61,15 +74,58 @@ namespace EasyAbp.EShop.Orders.Orders if (!eventData.IsSuccess) { - // Todo: Refund. - // Todo: Cancel order. + var refundOrderEto = CreateRefundOrderEto(order); + + await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason); + + await RefundOrderAsync(refundOrderEto); + return; } - + order.SetReducedInventoryAfterPaymentTime(_clock.Now); await _orderRepository.UpdateAsync(order, true); } } + + [UnitOfWork(true)] + protected virtual async Task RefundOrderAsync(RefundOrderEto refundOrderEto) + { + await _distributedEventBus.PublishAsync(refundOrderEto); + } + + protected virtual RefundOrderEto CreateRefundOrderEto(Order order) + { + if (!order.PaymentId.HasValue) + { + throw new OrderIsInWrongStageException(order.Id); + } + + var eto = new RefundOrderEto( + order.TenantId, + order.Id, + order.StoreId, + order.PaymentId.Value, + OrdersConsts.InventoryReductionFailedAutoCancellationReason, + OrdersConsts.InventoryReductionFailedAutoCancellationReason, + OrdersConsts.InventoryReductionFailedAutoCancellationReason); + + eto.OrderLines.AddRange(order.OrderLines.Select(x => new OrderLineRefundInfoModel + { + OrderLineId = x.Id, + Quantity = x.Quantity - x.RefundedQuantity, + TotalAmount = x.ActualTotalPrice - x.RefundAmount + })); + + eto.OrderExtraFees.AddRange(order.OrderExtraFees.Select(x => new OrderExtraFeeRefundInfoModel + { + Name = x.Name, + Key = x.Key, + TotalAmount = x.Fee - x.RefundAmount + })); + + return eto; + } } } \ No newline at end of file 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/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 0eb0c99b..2d211c63 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 @@ -1,23 +1,9 @@ using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Volo.Abp.ObjectExtending; namespace EasyAbp.EShop.Payments.Refunds.Dtos { [Serializable] - public class CreateEShopRefundItemInput : ExtensibleObject + public class CreateEShopRefundItemInput : CreateEShopRefundItemInfoModel { - public Guid OrderId { get; set; } - - [CanBeNull] - public string CustomerRemark { get; set; } - - [CanBeNull] - public string StaffRemark { get; set; } - - 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.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs new file mode 100644 index 00000000..03176bf7 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/CreateEShopRefundItemInfoModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.ObjectExtending; + +namespace EasyAbp.EShop.Payments.Refunds; + +[Serializable] +public class CreateEShopRefundItemInfoModel : ExtensibleObject +{ + public Guid OrderId { get; set; } + + [CanBeNull] + public string CustomerRemark { get; set; } + + [CanBeNull] + public string StaffRemark { get; set; } + + 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.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs new file mode 100644 index 00000000..a594a7c3 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundOrderEto.cs @@ -0,0 +1,34 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.MultiTenancy; + +namespace EasyAbp.EShop.Payments.Refunds; + +[Serializable] +public class RefundOrderEto : CreateEShopRefundItemInfoModel, IMultiTenant +{ + public Guid? TenantId { get; set; } + + public Guid StoreId { get; set; } + + public Guid PaymentId { get; set; } + + [CanBeNull] + public string DisplayReason { get; set; } + + protected RefundOrderEto() + { + } + + public RefundOrderEto(Guid? tenantId, Guid orderId, Guid storeId, Guid paymentId, [CanBeNull] string displayReason, + [CanBeNull] string customerRemark, [CanBeNull] string staffRemark) + { + TenantId = tenantId; + OrderId = orderId; + StoreId = storeId; + PaymentId = paymentId; + DisplayReason = displayReason; + CustomerRemark = customerRemark; + StaffRemark = staffRemark; + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs new file mode 100644 index 00000000..76b9d6a1 --- /dev/null +++ b/modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundOrderEventHandler.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.EShop.Payments.Payments; +using EasyAbp.PaymentService.Payments; +using EasyAbp.PaymentService.Refunds; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Json; + +namespace EasyAbp.EShop.Payments.Refunds; + +public class RefundOrderEventHandler : IDistributedEventHandler, ITransientDependency +{ + private readonly IJsonSerializer _jsonSerializer; + private readonly IPaymentRepository _paymentRepository; + private readonly IDistributedEventBus _distributedEventBus; + + public RefundOrderEventHandler( + IJsonSerializer jsonSerializer, + IPaymentRepository paymentRepository, + IDistributedEventBus distributedEventBus) + { + _jsonSerializer = jsonSerializer; + _paymentRepository = paymentRepository; + _distributedEventBus = distributedEventBus; + } + + public virtual async Task HandleEventAsync(RefundOrderEto eventData) + { + var refundAmount = eventData.OrderLines.Sum(x => x.TotalAmount) + + eventData.OrderExtraFees.Sum(x => x.TotalAmount); + + var payment = await _paymentRepository.GetAsync(eventData.PaymentId); + + var paymentItem = payment.PaymentItems.Single(x => x.ItemKey == eventData.OrderId.ToString()); + + var createRefundItemInput = new CreateRefundItemInput + { + PaymentItemId = paymentItem.Id, + RefundAmount = refundAmount, + CustomerRemark = eventData.CustomerRemark, + StaffRemark = eventData.StaffRemark + }; + + createRefundItemInput.SetProperty(nameof(RefundItem.StoreId), eventData.StoreId); + createRefundItemInput.SetProperty(nameof(RefundItem.OrderId), eventData.OrderId); + createRefundItemInput.SetProperty(nameof(RefundItem.OrderLines), + _jsonSerializer.Serialize(eventData.OrderLines)); + createRefundItemInput.SetProperty(nameof(RefundItem.OrderExtraFees), + _jsonSerializer.Serialize(eventData.OrderExtraFees)); + + var eto = new RefundPaymentEto(eventData.TenantId, new CreateRefundInput + { + PaymentId = eventData.PaymentId, + DisplayReason = eventData.DisplayReason, + CustomerRemark = eventData.CustomerRemark, + StaffRemark = eventData.StaffRemark, + RefundItems = new List { createRefundItemInput } + }); + + await _distributedEventBus.PublishAsync(eto); + } +} \ 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; } }