Browse Source

Merge pull request #146 from EasyAbp/extra-fee-refund

Support OrderExtraFee refund
pull/148/head
Super 4 years ago
committed by GitHub
parent
commit
4e74396313
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDto.cs
  2. 13
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderExtraFeeDto.cs
  3. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/OrdersApplicationAutoMapperProfile.cs
  4. 19
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs
  5. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs
  6. 5
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs
  7. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/RefundCompletedEventHandler.cs
  8. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContextModelCreatingExtensions.cs
  9. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj
  10. 196
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs
  11. 6
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs
  12. 31
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundInput.cs
  13. 4
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application.Contracts/EasyAbp/EShop/Payments/Refunds/Dtos/CreateEShopRefundItemInput.cs
  14. 2
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp.EShop.Payments.Application.csproj
  15. 2
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/EShopPaymentsApplicationModule.cs
  16. 13
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/AnotherRefundTaskIsOnGoingException.cs
  17. 16
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/InvalidRefundAmountException.cs
  18. 17
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeNotFoundException.cs
  19. 14
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/OrderLineNotFoundException.cs
  20. 45
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs
  21. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json
  22. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json
  23. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json
  24. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json
  25. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json
  26. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json
  27. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json
  28. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json
  29. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json
  30. 4
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs
  31. 4
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/EShopRefundItemEto.cs
  32. 14
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs
  33. 14
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs
  34. 1
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs
  35. 3
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItem.cs
  36. 31
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs
  37. 36
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs
  38. 8
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.EntityFrameworkCore/EasyAbp/EShop/Payments/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs
  39. 308
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/RefundAppServiceTests.cs
  40. 3
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Application.Tests/Refunds/TestRefundPaymentEventHandler.cs
  41. 2
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.TestBase/PaymentsTestData.cs
  42. 5171
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220420080940_OrderExtraFeesRefund.Designer.cs
  43. 55
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220420080940_OrderExtraFeesRefund.cs
  44. 39
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDto.cs

@ -48,5 +48,7 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
public DateTime? PaymentExpiration { get; set; }
public List<OrderLineDto> OrderLines { get; set; }
public List<OrderExtraFeeDto> OrderExtraFees { get; set; }
}
}

13
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderExtraFeeDto.cs

@ -0,0 +1,13 @@
using System;
namespace EasyAbp.EShop.Orders.Orders.Dtos
{
public class OrderExtraFeeDto
{
public string Name { get; set; }
public string Key { get; set; }
public decimal Fee { get; set; }
}
}

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/OrdersApplicationAutoMapperProfile.cs

@ -14,6 +14,7 @@ namespace EasyAbp.EShop.Orders
* into multiple profile classes for a better organization. */
CreateMap<Order, OrderDto>();
CreateMap<OrderLine, OrderLineDto>();
CreateMap<OrderExtraFee, OrderExtraFeeDto>(MemberList.Destination);
}
}
}

19
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs

@ -11,8 +11,6 @@ namespace EasyAbp.EShop.Orders.Orders
{
public class Order : FullAuditedAggregateRoot<Guid>, IOrder, IMultiTenant
{
public const string ExtraFeeListPropertyName = "ExtraFeeList";
public virtual Guid? TenantId { get; protected set; }
public virtual Guid StoreId { get; protected set; }
@ -156,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)
{
@ -165,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;
}

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

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

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

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContextModelCreatingExtensions.cs

@ -72,6 +72,7 @@ namespace EasyAbp.EShop.Orders.EntityFrameworkCore
b.ConfigureByConvention();
/* Configure more properties here */
b.Property(x => x.Fee).HasColumnType("decimal(20,8)");
b.Property(x => x.RefundAmount).HasColumnType("decimal(20,8)");
b.HasKey(x => new {x.OrderId, x.Name, x.Key});
});
}

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/EasyAbp.EShop.Orders.Domain.Tests.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
<RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup>
<ItemGroup>

196
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<IOrderRepository>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
var orderRepository = Substitute.For<IOrderRepository>();
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<RefundCompletedEventHandler>();
await handler.HandleEventAsync(new EShopRefundCompletedEto
{
Refund = new EShopRefundEto
{
Id = Guid.NewGuid(),
TenantId = null,
PaymentId = OrderTestData.Payment1Id,
Currency = "CNY",
RefundAmount = 0.3m,
RefundItems = new List<EShopRefundItemEto>
{
new()
{
Id = Guid.NewGuid(),
PaymentItemId = Guid.NewGuid(),
RefundAmount = 0.3m,
StoreId = OrderTestData.Store1Id,
OrderId = OrderTestData.Order1Id,
RefundItemOrderLines = new List<RefundItemOrderLineEto>
{
new()
{
OrderLineId = OrderTestData.OrderLine1Id,
RefundedQuantity = 1,
RefundAmount = 0.2m
}
},
RefundItemOrderExtraFees = new List<RefundItemOrderExtraFeeEto>
{
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<RefundCompletedEventHandler>();
await Should.ThrowAsync<InvalidRefundAmountException>(async () =>
{
await handler.HandleEventAsync(new EShopRefundCompletedEto
{
Refund = new EShopRefundEto
{
Id = Guid.NewGuid(),
TenantId = null,
PaymentId = OrderTestData.Payment1Id,
Currency = "CNY",
RefundAmount = -1m,
RefundItems = new List<EShopRefundItemEto>
{
new()
{
Id = Guid.NewGuid(),
PaymentItemId = Guid.NewGuid(),
RefundAmount = -1m,
StoreId = OrderTestData.Store1Id,
OrderId = OrderTestData.Order1Id,
RefundItemOrderLines = new List<RefundItemOrderLineEto>
{
new()
{
OrderLineId = OrderTestData.OrderLine1Id,
RefundedQuantity = 1,
RefundAmount = -1m
}
}
}
}
}
});
});
}
[Fact]
public async Task Should_Avoid_Over_Quantity_Refund()
{
var handler = ServiceProvider.GetRequiredService<RefundCompletedEventHandler>();
// Assert
await Should.ThrowAsync<InvalidRefundQuantityException>(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<EShopRefundItemEto>
{
new()
{
Id = Guid.NewGuid(),
PaymentItemId = Guid.NewGuid(),
RefundAmount = 0.3m,
StoreId = OrderTestData.Store1Id,
OrderId = OrderTestData.Order1Id,
RefundItemOrderLines = new List<RefundItemOrderLineEto>
{
new()
{
OrderLineId = OrderTestData.OrderLine1Id,
RefundedQuantity = 3,
RefundAmount = 0.2m
}
}
}
}
}
});
});
}
}
}

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

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

4
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<OrderLineRefundInfoModel> OrderLines { get; set; } = new List<OrderLineRefundInfoModel>();
public List<OrderLineRefundInfoModel> OrderLines { get; set; } = new();
public List<OrderExtraFeeRefundInfoModel> OrderExtraFees { get; set; } = new();
}
}

2
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp.EShop.Payments.Application.csproj

@ -11,7 +11,7 @@
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(AbpVersion)" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Orders\src\EasyAbp.EShop.Orders.Application.Contracts\EasyAbp.EShop.Orders.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Contracts\EasyAbp.EShop.Stores.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Shared\EasyAbp.EShop.Stores.Application.Shared.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Payments.Application.Contracts\EasyAbp.EShop.Payments.Application.Contracts.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Payments.Domain\EasyAbp.EShop.Payments.Domain.csproj" />
</ItemGroup>

2
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/EShopPaymentsApplicationModule.cs

@ -1,4 +1,5 @@
using EasyAbp.EShop.Payments.Payments;
using EasyAbp.EShop.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
@ -10,6 +11,7 @@ namespace EasyAbp.EShop.Payments
[DependsOn(
typeof(EShopPaymentsDomainModule),
typeof(EShopPaymentsApplicationContractsModule),
typeof(EShopStoresApplicationSharedModule),
typeof(AbpDddApplicationModule),
typeof(AbpAutoMapperModule)
)]

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

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

17
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeNotFoundException.cs

@ -0,0 +1,17 @@
using System;
using JetBrains.Annotations;
using Volo.Abp;
namespace EasyAbp.EShop.Payments.Refunds
{
public class OrderExtraFeeNotFoundException : BusinessException
{
public OrderExtraFeeNotFoundException(Guid orderId, [NotNull] string name, [CanBeNull] string key) : base(
PaymentsErrorCodes.OrderExtraFeeNotFound)
{
WithData(nameof(orderId), orderId);
WithData(nameof(name), name);
WithData(nameof(key), key);
}
}
}

14
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/OrderLineNotFoundException.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp;
namespace EasyAbp.EShop.Payments.Refunds
{
public class OrderLineNotFoundException : BusinessException
{
public OrderLineNotFoundException(Guid orderId, Guid orderLineId) : base(PaymentsErrorCodes.OrderLineNotFound)
{
WithData(nameof(orderId), orderId);
WithData(nameof(orderLineId), orderLineId);
}
}
}

45
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Application/EasyAbp/EShop/Payments/Refunds/RefundAppService.cs

@ -1,15 +1,14 @@
using EasyAbp.EShop.Payments.Authorization;
using EasyAbp.EShop.Payments.Payments;
using EasyAbp.EShop.Payments.Refunds.Dtos;
using EasyAbp.EShop.Stores.Permissions;
using EasyAbp.EShop.Orders.Orders;
using EasyAbp.PaymentService.Payments;
using EasyAbp.PaymentService.Refunds;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Stores.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Data;
@ -86,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,
@ -104,23 +108,47 @@ namespace EasyAbp.EShop.Payments.Refunds
{
throw new OrderIsNotInSpecifiedPaymentException(order.Id, payment.Id);
}
await AuthorizationService.CheckMultiStorePolicyAsync(paymentItem.StoreId,
PaymentsPermissions.Refunds.Manage, PaymentsPermissions.Refunds.CrossStore);
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);
}
// Todo: Check if current user is an admin of the store.
foreach (var model in refundItem.OrderLines)
{
var orderLine = order.OrderLines.Find(x => x.Id == model.OrderLineId);
if (orderLine is null)
{
throw new OrderLineNotFoundException(order.Id, model.OrderLineId);
}
if (orderLine.RefundedQuantity + model.Quantity > orderLine.Quantity)
{
throw new InvalidRefundQuantityException(model.Quantity);
}
}
foreach (var orderLineRefundInfoModel in refundItem.OrderLines)
foreach (var model in refundItem.OrderExtraFees)
{
var orderLine = order.OrderLines.Single(x => x.Id == orderLineRefundInfoModel.OrderLineId);
var orderExtraFee = order.OrderExtraFees.Find(x => x.Name == model.Name && x.Key == model.Key);
if (orderLine.RefundedQuantity + orderLineRefundInfoModel.Quantity > orderLine.Quantity)
if (orderExtraFee is null)
{
throw new InvalidRefundQuantityException(orderLineRefundInfoModel.Quantity);
throw new OrderExtraFeeNotFoundException(order.Id, model.Name, model.Key);
}
}
var eto = new CreateRefundItemInput
{
PaymentItemId = paymentItem.Id,
RefundAmount = refundItem.OrderLines.Sum(x => x.TotalAmount),
RefundAmount = refundAmount,
CustomerRemark = refundItem.CustomerRemark,
StaffRemark = refundItem.StaffRemark
};
@ -128,6 +156,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);
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/cs.json

@ -10,7 +10,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/en.json

@ -11,7 +11,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pl.json

@ -10,7 +10,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/pt-BR.json

@ -10,7 +10,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/sl.json

@ -11,7 +11,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/tr.json

@ -11,7 +11,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/vi.json

@ -10,7 +10,11 @@
"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."
"EasyAbp.EShop.Payments:StoreIdNotFound": "Cannot get valid StoreId from ExtraProperties.",
"EasyAbp.EShop.Payments:OrderLineNotFound": "There is no such an order line. (order ID: {orderId}, order line ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "There is no such an order extra fee. (order ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hans.json

@ -11,7 +11,11 @@
"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"
"EasyAbp.EShop.Payments:StoreIdNotFound": "无法从ExtraProperties获得有效的StoreId",
"EasyAbp.EShop.Payments:OrderLineNotFound": "不存在的订单项 (订单ID: {orderId}, 订单项ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "不存在的订单项 (订单ID: {orderId}, name: {name}, key: {key})"
}
}

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Localization/Payments/zh-Hant.json

@ -11,7 +11,11 @@
"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"
"EasyAbp.EShop.Payments:StoreIdNotFound": "無法從ExtraProperties獲得有效的StoreId",
"EasyAbp.EShop.Payments:OrderLineNotFound": "不存在的訂單項 (訂單ID: {orderId}, 訂單項ID: {orderLineId})",
"EasyAbp.EShop.Payments:OrderExtraFeeNotFound": "不存在的訂單項 (訂單ID: {orderId}, name: {name}, key: {key})"
}
}

4
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/PaymentsErrorCodes.cs

@ -5,7 +5,11 @@
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";
public const string OrderLineNotFound = "EasyAbp.EShop.Payments:OrderLineNotFound";
public const string OrderExtraFeeNotFound = "EasyAbp.EShop.Payments:OrderExtraFeeNotFound";
}
}

4
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<RefundItemOrderLineEto> RefundItemOrderLines { get; set; } = new List<RefundItemOrderLineEto>();
public List<RefundItemOrderLineEto> RefundItemOrderLines { get; set; } = new();
public List<RefundItemOrderExtraFeeEto> RefundItemOrderExtraFees { get; set; } = new();
}
}

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

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

1
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/PaymentsDomainAutoMapperProfile.cs

@ -31,6 +31,7 @@ namespace EasyAbp.EShop.Payments
CreateMap<Refund, EShopRefundEto>();
CreateMap<RefundItem, EShopRefundItemEto>();
CreateMap<RefundItemOrderLine, RefundItemOrderLineEto>();
CreateMap<RefundItemOrderExtraFee, RefundItemOrderExtraFeeEto>();
}
}
}

3
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<RefundItemOrderLine> RefundItemOrderLines { get; protected set; }
public virtual List<RefundItemOrderExtraFee> RefundItemOrderExtraFees { get; protected set; }
protected RefundItem()
{
RefundItemOrderLines = new List<RefundItemOrderLine>();
RefundItemOrderExtraFees = new List<RefundItemOrderExtraFee>();
ExtraProperties = new ExtraPropertyDictionary();

31
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<Guid>
{
[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;
}
}
}

36
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<List<OrderExtraFeeRefundInfoModel>>(
refundItem.GetProperty<string>("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<string>("StoreId"), out var storeId))

8
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.EntityFrameworkCore/EasyAbp/EShop/Payments/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs

@ -89,6 +89,14 @@ namespace EasyAbp.EShop.Payments.EntityFrameworkCore
/* Configure more properties here */
b.Property(x => x.RefundAmount).HasColumnType("decimal(20,8)");
});
builder.Entity<RefundItemOrderExtraFee>(b =>
{
b.ToTable(options.TablePrefix + "RefundItemOrderExtraFees", options.Schema);
b.ConfigureByConvention();
/* Configure more properties here */
b.Property(x => x.RefundAmount).HasColumnType("decimal(20,8)");
});
}
}
}

308
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<IPaymentRepository>();
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> {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> {paymentItem});
return payment;
@ -63,14 +95,23 @@ namespace EasyAbp.EShop.Payments.Refunds
StoreId = PaymentsTestData.Store1,
OrderLines = new List<OrderLineDto>
{
new OrderLineDto
new()
{
Id = PaymentsTestData.OrderLine1,
Currency = "CNY",
ActualTotalPrice = 0,
ActualTotalPrice = 1m,
Quantity = 1
}
},
OrderExtraFees = new List<OrderExtraFeeDto>
{
new()
{
Name = "Name",
Key = "Key",
Fee = 5m
}
},
PaymentId = PaymentsTestData.Payment1
}));
@ -79,6 +120,7 @@ namespace EasyAbp.EShop.Payments.Refunds
public RefundAppServiceTests()
{
_jsonSerializer = GetRequiredService<IJsonSerializer>();
_refundAppService = GetRequiredService<IRefundAppService>();
_testRefundPaymentEventHandler = GetRequiredService<TestRefundPaymentEventHandler>();
}
@ -96,18 +138,27 @@ namespace EasyAbp.EShop.Payments.Refunds
StaffRemark = "StaffRemark",
RefundItems = new List<CreateEShopRefundItemInput>
{
new CreateEShopRefundItemInput
new()
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>
{
new OrderLineRefundInfoModel
new()
{
OrderLineId = PaymentsTestData.OrderLine1,
Quantity = 1,
TotalAmount = 0
TotalAmount = 0.4m
}
},
OrderExtraFees = new List<OrderExtraFeeRefundInfoModel>
{
new()
{
Name = "Name",
Key = "Key",
TotalAmount = 0.6m
}
}
}
@ -118,6 +169,249 @@ 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<Guid>("OrderId").ShouldBe(PaymentsTestData.Order1);
var orderLines =
_jsonSerializer.Deserialize<List<OrderLineRefundInfoModel>>(
refundItem.GetProperty<string>("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<List<OrderExtraFeeRefundInfoModel>>(
refundItem.GetProperty<string>("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<CreateEShopRefundItemInput>
{
new CreateEShopRefundItemInput
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>(), // empty
OrderExtraFees = new List<OrderExtraFeeRefundInfoModel>() // empty
}
}
};
// Act & Assert
await Should.ThrowAsync<AbpValidationException>(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<CreateEShopRefundItemInput>
{
new()
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>
{
new()
{
OrderLineId = PaymentsTestData.OrderLine1,
Quantity = 1,
TotalAmount = 1m
}
},
OrderExtraFees = new List<OrderExtraFeeRefundInfoModel>
{
new()
{
Name = "Name",
Key = "Key",
TotalAmount = 0.1m
}
}
}
}
};
// Act & Assert
await Should.ThrowAsync<InvalidRefundAmountException>(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<CreateEShopRefundItemInput>
{
new CreateEShopRefundItemInput
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>
{
new OrderLineRefundInfoModel
{
OrderLineId = PaymentsTestData.OrderLine1,
Quantity = 1,
TotalAmount = 1m
}
}
}
}
};
// Act & Assert
await Should.ThrowAsync<AnotherRefundTaskIsOnGoingException>(async () =>
{
await _refundAppService.CreateAsync(request);
});
}
[Fact]
public async Task Should_Check_OrderLines_Exist_When_Refunding()
{
// Arrange
var request = new CreateEShopRefundInput
{
DisplayReason = "Reason",
CustomerRemark = "Customer Remark",
PaymentId = PaymentsTestData.Payment1,
StaffRemark = "StaffRemark",
RefundItems = new List<CreateEShopRefundItemInput>
{
new()
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>
{
new()
{
OrderLineId = Guid.NewGuid(),
Quantity = 1,
TotalAmount = 1m
}
}
}
}
};
}
[Fact]
public async Task Should_Check_OrderExtraFees_Exist_When_Refunding()
{
// Arrange
var request = new CreateEShopRefundInput
{
DisplayReason = "Reason",
CustomerRemark = "Customer Remark",
PaymentId = PaymentsTestData.Payment1,
StaffRemark = "StaffRemark",
RefundItems = new List<CreateEShopRefundItemInput>
{
new()
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderExtraFees = new List<OrderExtraFeeRefundInfoModel>
{
new()
{
Name = "FakeName",
Key = "FakeKey",
TotalAmount = 0.6m
}
}
}
}
};
// Act & Assert
await Should.ThrowAsync<OrderExtraFeeNotFoundException>(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<CreateEShopRefundItemInput>
{
new CreateEShopRefundItemInput
{
CustomerRemark = "CustomerRemark",
OrderId = PaymentsTestData.Order1,
StaffRemark = "StaffRemark",
OrderLines = new List<OrderLineRefundInfoModel>
{
new OrderLineRefundInfoModel
{
OrderLineId = PaymentsTestData.OrderLine1,
Quantity = 1,
TotalAmount = -1m
}
}
}
}
};
// Act & Assert
await Should.ThrowAsync<AbpValidationException>(async () =>
{
await _refundAppService.CreateAsync(request);
}, "RefundAmount should be greater than 0.");
}
}
}

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

2
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();
}
}

5171
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220420080940_OrderExtraFeesRefund.Designer.cs

File diff suppressed because it is too large

55
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220420080940_OrderExtraFeesRefund.cs

@ -0,0 +1,55 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
public partial class OrderExtraFeesRefund : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "RefundAmount",
table: "EasyAbpEShopOrdersOrderExtraFees",
type: "decimal(20,8)",
nullable: false,
defaultValue: 0m);
migrationBuilder.CreateTable(
name: "EasyAbpEShopPaymentsRefundItemOrderExtraFees",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Key = table.Column<string>(type: "nvarchar(max)", nullable: true),
RefundAmount = table.Column<decimal>(type: "decimal(20,8)", nullable: false),
RefundItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_EasyAbpEShopPaymentsRefundItemOrderExtraFees", x => x.Id);
table.ForeignKey(
name: "FK_EasyAbpEShopPaymentsRefundItemOrderExtraFees_EasyAbpEShopPaymentsRefundItems_RefundItemId",
column: x => x.RefundItemId,
principalTable: "EasyAbpEShopPaymentsRefundItems",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_EasyAbpEShopPaymentsRefundItemOrderExtraFees_RefundItemId",
table: "EasyAbpEShopPaymentsRefundItemOrderExtraFees",
column: "RefundItemId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EasyAbpEShopPaymentsRefundItemOrderExtraFees");
migrationBuilder.DropColumn(
name: "RefundAmount",
table: "EasyAbpEShopOrdersOrderExtraFees");
}
}
}

39
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

@ -19,7 +19,7 @@ namespace EShopSample.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("ProductVersion", "6.0.3")
.HasAnnotation("ProductVersion", "6.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
@ -157,6 +157,9 @@ namespace EShopSample.Migrations
b.Property<decimal>("Fee")
.HasColumnType("decimal(20,8)");
b.Property<decimal>("RefundAmount")
.HasColumnType("decimal(20,8)");
b.HasKey("OrderId", "Name", "Key");
b.ToTable("EasyAbpEShopOrdersOrderExtraFees", (string)null);
@ -590,6 +593,31 @@ namespace EShopSample.Migrations
b.ToTable("EasyAbpEShopPaymentsRefundItems", (string)null);
});
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderExtraFee", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Key")
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("RefundAmount")
.HasColumnType("decimal(20,8)");
b.Property<Guid?>("RefundItemId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RefundItemId");
b.ToTable("EasyAbpEShopPaymentsRefundItemOrderExtraFees", (string)null);
});
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderLine", b =>
{
b.Property<Guid>("Id")
@ -4652,6 +4680,13 @@ namespace EShopSample.Migrations
.HasForeignKey("RefundId");
});
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderExtraFee", b =>
{
b.HasOne("EasyAbp.EShop.Payments.Refunds.RefundItem", null)
.WithMany("RefundItemOrderExtraFees")
.HasForeignKey("RefundItemId");
});
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderLine", b =>
{
b.HasOne("EasyAbp.EShop.Payments.Refunds.RefundItem", null)
@ -5006,6 +5041,8 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItem", b =>
{
b.Navigation("RefundItemOrderExtraFees");
b.Navigation("RefundItemOrderLines");
});

Loading…
Cancel
Save