diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs index 72fb028f..23f700a1 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs @@ -6,81 +6,260 @@ using System.Threading.Tasks; using BusinessLogic.Manager; using BusinessLogic.Services; + using Common; using EstateManagement.Client; + using EstateManagement.DataTransferObjects; + using EstateManagement.DataTransferObjects.Responses; using EventHandling; using MessagingService.Client; using Microsoft.Extensions.Configuration; + using Models; using Moq; using SecurityService.Client; + using SettlementAggregates; + using Shared.DomainDrivenDesign.EventSourcing; + using Shared.EventStore.Aggregate; using Shared.EventStore.EventStore; + using Shared.Exceptions; using Shared.General; using Shared.Logger; + using Shouldly; using Testing; using TransactionAggregate; using Xunit; public class TransactionDomainEventHandlerTests { - [Fact] - public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SuccessfulSale_EventIsHandled() + //[Fact] + //public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SuccessfulSale_EventIsHandled() + //{ + // Mock transactionAggregateManager = new Mock(); + // transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + // .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + // Mock> pendingSettlementAggregateRepository = + // new Mock>(); + // Mock feeCalculationManager = new Mock(); + // feeCalculationManager.Setup(f => f.CalculateFees(It.IsAny>(), It.IsAny())).Returns(TestData.CalculatedMerchantFees); + // Mock estateClient = new Mock(); + // estateClient.Setup(e => e.GetTransactionFeesForProduct(It.IsAny(), + // It.IsAny(), + // It.IsAny(), + // It.IsAny(), + // It.IsAny(), + // It.IsAny())).ReturnsAsync(TestData.ContractProductTransactionFees); + + // Mock securityServiceClient = new Mock(); + // securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + // Mock transactionReceiptBulder = new Mock(); + // Mock messagingServiceClient = new Mock(); + + // IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); + // ConfigurationReader.Initialise(configurationRoot); + // Logger.Initialise(NullLogger.Instance); + + // TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, + // feeCalculationManager.Object, + // estateClient.Object, + // securityServiceClient.Object, + // transactionReceiptBulder.Object, + // messagingServiceClient.Object, + // pendingSettlementAggregateRepository.Object); + + // await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); + //} + + //Merchant not found + // Merchant with Immediate Settlement + // Merchant with Weekly Settlement + // Merchant with Monthly Settlement + + private Mock TransactionAggregateManager; + + private Mock> PendingSettlementAggregateRepository; + + private Mock FeeCalculationManager; + + private Mock EstateClient; + + private Mock SecurityServiceClient; + + private Mock TransactionReceiptBuilder; + + private Mock MessagingServiceClient; + public TransactionDomainEventHandlerTests() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); - Mock feeCalculationManager = new Mock(); - feeCalculationManager.Setup(f => f.CalculateFees(It.IsAny>(), It.IsAny())).Returns(TestData.CalculatedMerchantFees); - Mock estateClient = new Mock(); - estateClient.Setup(e => e.GetTransactionFeesForProduct(It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())).ReturnsAsync(TestData.ContractProductTransactionFees); - - Mock securityServiceClient = new Mock(); - securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); - - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); + this.TransactionAggregateManager = new Mock(); + this.PendingSettlementAggregateRepository = new Mock>(); + this.FeeCalculationManager = new Mock(); + this.EstateClient = new Mock(); + this.SecurityServiceClient = new Mock(); + this.TransactionReceiptBuilder = new Mock(); + this.MessagingServiceClient = new Mock(); IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); + } + + [Fact] + public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SuccessfulSale_MerchantWithImmediateSettlement_EventIsHandled() + { + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + + this.FeeCalculationManager.Setup(f => f.CalculateFees(It.IsAny>(), It.IsAny())).Returns(new List + { + TestData.CalculatedFeeMerchantFee, + TestData.CalculatedFeeServiceProviderFee + }); + + this.EstateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new MerchantResponse + { + SettlementSchedule = SettlementSchedule.Immediate, + }); + this.EstateClient.Setup(e => e.GetTransactionFeesForProduct(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.ContractProductTransactionFees); + + this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); + + await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); + + this.TransactionAggregateManager.Verify(t => t.AddFee(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()),Times.Exactly(2)); + } - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + [Fact] + public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SuccessfulSale_MerchantWithWeeklySettlement_EventIsHandled() + { + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + PendingSettlementAggregate pendingSettlementAggregate = new PendingSettlementAggregate(); + this.PendingSettlementAggregateRepository.Setup(p => p.GetLatestVersion(It.IsAny(), It.IsAny())) + .ReturnsAsync(pendingSettlementAggregate); + + this.FeeCalculationManager.Setup(f => f.CalculateFees(It.IsAny>(), It.IsAny())).Returns(new List + { + TestData.CalculatedFeeMerchantFee, + TestData.CalculatedFeeServiceProviderFee + }); + var merchant = new MerchantResponse + { + SettlementSchedule = SettlementSchedule.Weekly, + NextSettlementDueDate = DateTime.Now.AddDays(7) + }; + this.EstateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(merchant); + this.EstateClient.Setup(e => e.GetTransactionFeesForProduct(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.ContractProductTransactionFees); + + this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); + + this.TransactionAggregateManager.Verify(t => t.AddFee(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); + this.PendingSettlementAggregateRepository.Verify(p => p.GetLatestVersion(It.IsAny(), It.IsAny()),Times.Once); + this.PendingSettlementAggregateRepository.Verify(p => p.SaveChanges(It.IsAny(), It.IsAny()), Times.Once); + pendingSettlementAggregate.GetNumberOfFeesPendingSettlement().ShouldBe(1); + pendingSettlementAggregate.GetNumberOfFeesSettled().ShouldBe(0); } [Fact] - public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_UnsuccessfulSale_EventIsHandled() + public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SuccessfulSale_MerchantWithMonthlySettlement_EventIsHandled() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetCompletedDeclinedSaleTransactionAggregate); - Mock feeCalculationManager = new Mock(); - Mock estateClient = new Mock(); - - Mock securityServiceClient = new Mock(); + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + PendingSettlementAggregate pendingSettlementAggregate = new PendingSettlementAggregate(); + this.PendingSettlementAggregateRepository.Setup(p => p.GetLatestVersion(It.IsAny(), It.IsAny())) + .ReturnsAsync(pendingSettlementAggregate); + + this.FeeCalculationManager.Setup(f => f.CalculateFees(It.IsAny>(), It.IsAny())).Returns(new List + { + TestData.CalculatedFeeMerchantFee, + TestData.CalculatedFeeServiceProviderFee + }); + var merchant = new MerchantResponse + { + SettlementSchedule = SettlementSchedule.Monthly, + NextSettlementDueDate = DateTime.Now.AddMonths(1) + }; + this.EstateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(merchant); + this.EstateClient.Setup(e => e.GetTransactionFeesForProduct(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.ContractProductTransactionFees); + + this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); + await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); - ConfigurationReader.Initialise(configurationRoot); - Logger.Initialise(NullLogger.Instance); + this.TransactionAggregateManager.Verify(t => t.AddFee(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); + this.PendingSettlementAggregateRepository.Verify(p => p.GetLatestVersion(It.IsAny(), It.IsAny()), Times.Once); + this.PendingSettlementAggregateRepository.Verify(p => p.SaveChanges(It.IsAny(), It.IsAny()), Times.Once); + pendingSettlementAggregate.GetNumberOfFeesPendingSettlement().ShouldBe(1); + pendingSettlementAggregate.GetNumberOfFeesSettled().ShouldBe(0); + } - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + [Fact] + public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_UnsuccessfulSale_EventIsHandled() + { + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedDeclinedSaleTransactionAggregate); + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -88,27 +267,16 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet [Fact] public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_IncompleteSale_EventIsHandled() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetIncompleteAuthorisedSaleTransactionAggregate); - Mock feeCalculationManager = new Mock(); - Mock estateClient = new Mock(); - - Mock securityServiceClient = new Mock(); - - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); - ConfigurationReader.Initialise(configurationRoot); - Logger.Initialise(NullLogger.Instance); + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetIncompleteAuthorisedSaleTransactionAggregate); - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -116,27 +284,16 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet [Fact] public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_SaleWithNoProductDetails_EventIsHandled() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetCompletedAuthorisedSaleWithNoProductDetailsTransactionAggregate); - Mock feeCalculationManager = new Mock(); - Mock estateClient = new Mock(); - - Mock securityServiceClient = new Mock(); - - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); - ConfigurationReader.Initialise(configurationRoot); - Logger.Initialise(NullLogger.Instance); + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleWithNoProductDetailsTransactionAggregate); - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -144,27 +301,16 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet [Fact] public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_AuthorisedLogon_EventIsHandled() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetCompletedAuthorisedLogonTransactionAggregate); - Mock feeCalculationManager = new Mock(); - Mock estateClient = new Mock(); - - Mock securityServiceClient = new Mock(); - - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); - ConfigurationReader.Initialise(configurationRoot); - Logger.Initialise(NullLogger.Instance); - - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedLogonTransactionAggregate); + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -172,28 +318,18 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet [Fact] public async Task TransactionDomainEventHandler_Handle_CustomerEmailReceiptRequestedEvent_EventIsHandled() { - Mock transactionAggregateManager = new Mock(); - transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); - Mock feeCalculationManager = new Mock(); - Mock estateClient = new Mock(); - - Mock securityServiceClient = new Mock(); - securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); - - Mock transactionReceiptBulder = new Mock(); - Mock messagingServiceClient = new Mock(); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); - ConfigurationReader.Initialise(configurationRoot); - Logger.Initialise(NullLogger.Instance); - - TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, - feeCalculationManager.Object, - estateClient.Object, - securityServiceClient.Object, - transactionReceiptBulder.Object, - messagingServiceClient.Object); + this.TransactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + + this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(this.TransactionAggregateManager.Object, + this.FeeCalculationManager.Object, + this.EstateClient.Object, + this.SecurityServiceClient.Object, + this.TransactionReceiptBuilder.Object, + this.MessagingServiceClient.Object, + this.PendingSettlementAggregateRepository.Object); await transactionDomainEventHandler.Handle(TestData.CustomerEmailReceiptRequestedEvent, CancellationToken.None); } diff --git a/TransactionProcessor.BusinessLogic.Tests/Extensions/ExtensionsTests.cs b/TransactionProcessor.BusinessLogic.Tests/Extensions/ExtensionsTests.cs new file mode 100644 index 00000000..7e69f060 --- /dev/null +++ b/TransactionProcessor.BusinessLogic.Tests/Extensions/ExtensionsTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionProcessor.BusinessLogic.Tests.Extensions +{ + using Common; + using Shared.Exceptions; + using Shouldly; + using Xunit; + + public class ExtensionsTests + { + [Fact] + public void GuidExtensions_ToDateTime_DateTimeIsReturned() + { + Guid guid = Guid.Parse("c150c000-7c92-08d9-0000-000000000000"); + DateTime dateTime = guid.ToDateTime(); + dateTime.ShouldBe(new DateTime(2021, 9, 21)); + } + + [Fact] + public void DateExtensions_ToGuid_GuidIsReturned() + { + DateTime dateTime = new DateTime(2021, 9, 21); + Guid guid = dateTime.ToGuid(); + guid.ShouldBe(Guid.Parse("c150c000-7c92-08d9-0000-000000000000")); + } + } +} diff --git a/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj b/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj index 1a781fe3..3d8393ba 100644 --- a/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj +++ b/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj @@ -2,7 +2,7 @@ net5.0 - Full + None false diff --git a/TransactionProcessor.BusinessLogic/Common/Extensions.cs b/TransactionProcessor.BusinessLogic/Common/Extensions.cs index fb0ad2ca..2c657fcb 100644 --- a/TransactionProcessor.BusinessLogic/Common/Extensions.cs +++ b/TransactionProcessor.BusinessLogic/Common/Extensions.cs @@ -18,7 +18,8 @@ public static class Extensions /// The additional transaction metadata. /// [ExcludeFromCodeCoverage] - public static T ExtractFieldFromMetadata(this Dictionary additionalTransactionMetadata, String fieldName) + public static T ExtractFieldFromMetadata(this Dictionary additionalTransactionMetadata, + String fieldName) { // Create a case insensitive version of the dictionary Dictionary caseInsensitiveDictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); @@ -37,5 +38,23 @@ public static T ExtractFieldFromMetadata(this Dictionary addi return default(T); } } + + public static Guid ToGuid(this DateTime dt) + { + var bytes = BitConverter.GetBytes(dt.Ticks); + + Array.Resize(ref bytes, 16); + + return new Guid(bytes); + } + + public static DateTime ToDateTime(this Guid guid) + { + var bytes = guid.ToByteArray(); + + Array.Resize(ref bytes, 8); + + return new DateTime(BitConverter.ToInt64(bytes)); + } } } diff --git a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs index d02878bb..7eb977d9 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs @@ -3,23 +3,31 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Linq; using System.Threading; using System.Threading.Tasks; + using Common; using EstateManagement.Client; + using EstateManagement.DataTransferObjects; using EstateManagement.DataTransferObjects.Responses; using Manager; using MessagingService.Client; using MessagingService.DataTransferObjects; using Models; + using Newtonsoft.Json; using SecurityService.Client; using SecurityService.DataTransferObjects.Responses; using Services; + using SettlementAggregates; using Shared.DomainDrivenDesign.EventSourcing; + using Shared.EventStore.Aggregate; using Shared.EventStore.EventHandling; using Shared.General; using Shared.Logger; using Transaction.DomainEvents; using TransactionAggregate; + using CalculationType = Models.CalculationType; + using FeeType = Models.FeeType; /// /// @@ -55,6 +63,8 @@ public class TransactionDomainEventHandler : IDomainEventHandler /// private readonly IMessagingServiceClient MessagingServiceClient; + private readonly IAggregateRepository PendingSettlementAggregateRepository; + /// /// The token response /// @@ -83,7 +93,8 @@ public TransactionDomainEventHandler(ITransactionAggregateManager transactionAgg IEstateClient estateClient, ISecurityServiceClient securityServiceClient, ITransactionReceiptBuilder transactionReceiptBuilder, - IMessagingServiceClient messagingServiceClient) + IMessagingServiceClient messagingServiceClient, + IAggregateRepository pendingSettlementAggregateRepository) { this.TransactionAggregateManager = transactionAggregateManager; this.FeeCalculationManager = feeCalculationManager; @@ -91,6 +102,7 @@ public TransactionDomainEventHandler(ITransactionAggregateManager transactionAgg this.SecurityServiceClient = securityServiceClient; this.TransactionReceiptBuilder = transactionReceiptBuilder; this.MessagingServiceClient = messagingServiceClient; + this.PendingSettlementAggregateRepository = pendingSettlementAggregateRepository; } #endregion @@ -165,6 +177,7 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do } this.TokenResponse = await this.GetToken(cancellationToken); + // Ok we should have filtered out the not applicable transactions // Get the fees to be calculated List feesForProduct = await this.EstateClient.GetTransactionFeesForProduct(this.TokenResponse.AccessToken, @@ -191,11 +204,51 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do // Do the fee calculation List resultFees = this.FeeCalculationManager.CalculateFees(feesForCalculation, transactionAggregate.TransactionAmount.Value); - foreach (CalculatedFee calculatedFee in resultFees) + // Process the non merchant fees + IEnumerable nonMerchantFees = resultFees.Where(f => f.FeeType == FeeType.ServiceProvider); + + foreach (CalculatedFee calculatedFee in nonMerchantFees) { // Add Fee to the Transaction await this.TransactionAggregateManager.AddFee(transactionAggregate.EstateId, transactionAggregate.AggregateId, calculatedFee, cancellationToken); } + + // Now deal with merchant fees + IEnumerable merchantFees = resultFees.Where(f => f.FeeType == FeeType.Merchant); + + // get the merchant now to see the settlement schedule + this.TokenResponse = await this.GetToken(cancellationToken); + MerchantResponse merchant = await this.EstateClient.GetMerchant(this.TokenResponse.AccessToken, domainEvent.EstateId, domainEvent.MerchantId, cancellationToken); + + // Add fees to transaction now if settlement is immediate + if (merchant.SettlementSchedule == SettlementSchedule.Immediate) + { + foreach (CalculatedFee calculatedFee in merchantFees) + { + // Add Fee to the Transaction + await this.TransactionAggregateManager.AddFee(transactionAggregate.EstateId, transactionAggregate.AggregateId, calculatedFee, cancellationToken); + } + } + else + { + foreach (CalculatedFee calculatedFee in merchantFees) + { + // Determine when the fee should be applied + Guid aggregateId = merchant.NextSettlementDueDate.ToGuid(); + + // We need to add the fees to a pending settlement stream (for today) + PendingSettlementAggregate aggregate = await this.PendingSettlementAggregateRepository.GetLatestVersion(aggregateId, cancellationToken); + + if (aggregate.IsCreated == false) + { + aggregate.Create(transactionAggregate.EstateId, merchant.NextSettlementDueDate); + } + + aggregate.AddFee(transactionAggregate.MerchantId, transactionAggregate.AggregateId, calculatedFee); + + await this.PendingSettlementAggregateRepository.SaveChanges(aggregate, cancellationToken); + } + } } /// diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index 009629d9..1985b8da 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -61,7 +61,7 @@ public class TransactionDomainService : ITransactionDomainService /// The reconciliation aggregate repository /// private readonly IAggregateRepository ReconciliationAggregateRepository; - + #endregion #region Constructors diff --git a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj index 0ac70085..e0e9ed35 100644 --- a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj +++ b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj @@ -5,7 +5,7 @@ - + @@ -18,6 +18,7 @@ + diff --git a/TransactionProcessor.Client/ITransactionProcessorClient.cs b/TransactionProcessor.Client/ITransactionProcessorClient.cs index bd841d71..c673f756 100644 --- a/TransactionProcessor.Client/ITransactionProcessorClient.cs +++ b/TransactionProcessor.Client/ITransactionProcessorClient.cs @@ -19,6 +19,11 @@ Task PerformTransaction(String accessToken, SerialisedMessage transactionRequest, CancellationToken cancellationToken); + Task GetPendingSettlementByDate(String accessToken, + DateTime pendingSettlementDate, + Guid estateId, + CancellationToken cancellationToken); + #endregion } } \ No newline at end of file diff --git a/TransactionProcessor.Client/TransactionProcessorClient.cs b/TransactionProcessor.Client/TransactionProcessorClient.cs index 5c707185..e41fa9d9 100644 --- a/TransactionProcessor.Client/TransactionProcessorClient.cs +++ b/TransactionProcessor.Client/TransactionProcessorClient.cs @@ -90,6 +90,40 @@ public async Task PerformTransaction(String accessToken, return response; } + public async Task GetPendingSettlementByDate(String accessToken, + DateTime pendingSettlementDate, + Guid estateId, + CancellationToken cancellationToken) + { + PendingSettlementResponse response = null; + + String requestUri = $"{this.BaseAddress}/api/settlements/{pendingSettlementDate.Date:yyyy-MM-dd}/estates/{estateId}/pending"; + + try + { + // Add the access token to the client headers + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Make the Http Call here + HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken); + + // Process the response + String content = await this.HandleResponse(httpResponse, cancellationToken); + + // call was successful so now deserialise the body to the response object + response = JsonConvert.DeserializeObject(content); + } + catch (Exception ex) + { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception("Error getting pending settlment.", ex); + + throw exception; + } + + return response; + } + #endregion } } \ No newline at end of file diff --git a/TransactionProcessor.DataTransferObjects/PendingSettlementResponse.cs b/TransactionProcessor.DataTransferObjects/PendingSettlementResponse.cs new file mode 100644 index 00000000..2a32a791 --- /dev/null +++ b/TransactionProcessor.DataTransferObjects/PendingSettlementResponse.cs @@ -0,0 +1,16 @@ +namespace TransactionProcessor.DataTransferObjects +{ + using System; + + public class PendingSettlementResponse + { + public Guid EstateId { get; set; } + + public DateTime SettlementDate { get; set; } + + public Int32 NumberOfFeesPendingSettlement { get; set; } + + public Int32 NumberOfFeesSettled { get; set; } + + } +} \ No newline at end of file diff --git a/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj b/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj index 1e4cc726..e66b32b1 100644 --- a/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj +++ b/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj @@ -9,7 +9,7 @@ - + diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs index ceb02ef7..390f1747 100644 --- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -380,58 +380,7 @@ private static EventStoreClientSettings ConfigureEventStoreSettings(Int32 eventS settings.DefaultCredentials = new UserCredentials("admin", "changeit"); return settings; } - - protected async Task CleanUpSubscriptionServiceConfiguration() - { - String connectionString = Setup.GetLocalConnectionString("SubscriptionServiceConfiguration"); - - await using (SqlConnection connection = new SqlConnection(connectionString)) - { - await connection.OpenAsync(CancellationToken.None).ConfigureAwait(false); - - // Delete the Event Store Server - await this.DeleteEventStoreServer(connection).ConfigureAwait(false); - - // Delete the Subscriptions - await this.DeleteSubscriptions(connection).ConfigureAwait(false); - - await connection.CloseAsync().ConfigureAwait(false); - } - } - - protected async Task InsertEventStoreServer(SqlConnection openConnection, String eventStoreContainerName) - { - String esConnectionString = $"ConnectTo=tcp://admin:changeit@{eventStoreContainerName}:{DockerHelper.EventStoreTcpDockerPort};VerboseLogging=true;"; - SqlCommand command = openConnection.CreateCommand(); - command.CommandText = $"INSERT INTO EventStoreServer(EventStoreServerId, ConnectionString,Name) SELECT '{this.TestId}', '{esConnectionString}', 'TestEventStore'"; - command.CommandType = CommandType.Text; - await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); - } - - protected async Task DeleteEventStoreServer(SqlConnection openConnection) - { - SqlCommand command = openConnection.CreateCommand(); - command.CommandText = $"DELETE FROM EventStoreServer WHERE EventStoreServerId = '{this.TestId}'"; - command.CommandType = CommandType.Text; - await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); - } - - protected async Task DeleteSubscriptions(SqlConnection openConnection) - { - SqlCommand command = openConnection.CreateCommand(); - command.CommandText = $"DELETE FROM Subscription WHERE EventStoreId = '{this.TestId}'"; - command.CommandType = CommandType.Text; - await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); - } - - protected async Task InsertSubscription(SqlConnection openConnection, String streamName, String groupName, String endPointUri) - { - SqlCommand command = openConnection.CreateCommand(); - command.CommandText = $"INSERT INTO subscription(SubscriptionId, EventStoreId, StreamName, GroupName, EndPointUri, StreamPosition) SELECT '{Guid.NewGuid()}', '{this.TestId}', '{streamName}', '{groupName}', '{endPointUri}', null"; - command.CommandType = CommandType.Text; - await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); - } - + private async Task RemoveEstateReadModel() { List estateIdList = this.TestingContext.GetAllEstateIds(); @@ -455,8 +404,6 @@ await Retry.For(async () => /// public override async Task StopContainersForScenarioRun() { - await CleanUpSubscriptionServiceConfiguration().ConfigureAwait(false); - await RemoveEstateReadModel().ConfigureAwait(false); if (this.Containers.Any()) diff --git a/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature b/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature new file mode 100644 index 00000000..74767038 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature @@ -0,0 +1,105 @@ +@base @shared +Feature: Settlement + +Background: + + Given I create the following api scopes + | Name | DisplayName | Description | + | estateManagement | Estate Managememt REST Scope | A scope for Estate Managememt REST | + | transactionProcessor | Transaction Processor REST Scope | A scope for Transaction Processor REST | + | voucherManagement | Voucher Management REST Scope | A scope for Voucher Management REST | + + Given the following api resources exist + | ResourceName | DisplayName | Secret | Scopes | UserClaims | + | estateManagement | Estate Managememt REST | Secret1 | estateManagement | MerchantId, EstateId, role | + | transactionProcessor | Transaction Processor REST | Secret1 | transactionProcessor | | + | voucherManagement | Voucher Management REST | Secret1 | voucherManagement | | + + Given the following clients exist + | ClientId | ClientName | Secret | AllowedScopes | AllowedGrantTypes | + | serviceClient | Service Client | Secret1 | estateManagement,transactionProcessor,voucherManagement | client_credentials | + + Given I have a token to access the estate management and transaction processor resources + | ClientId | + | serviceClient | + + Given I have created the following estates + | EstateName | + | Test Estate 1 | + + Given I have created the following operators + | EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber | + | Test Estate 1 | Safaricom | True | True | + | Test Estate 1 | Voucher | True | True | + + Given I create a contract with the following values + | EstateName | OperatorName | ContractDescription | + | Test Estate 1 | Safaricom | Safaricom Contract | + | Test Estate 1 | Voucher | Hospital 1 Contract | + + When I create the following Products + | EstateName | OperatorName | ContractDescription | ProductName | DisplayText | Value | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Custom | | + | Test Estate 1 | Voucher | Hospital 1 Contract | 10 KES | 10 KES | | + + When I add the following Transaction Fees + | EstateName | OperatorName | ContractDescription | ProductName | CalculationType | FeeDescription | Value | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Fixed | Merchant Commission | 2.50 | + +@PRTest +Scenario: Settlement Processing + Given I create the following merchants + | MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName | SettlementSchedule | + | Test Merchant 1 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | Immediate | + | Test Merchant 2 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 2 | testcontact2@merchant2.co.uk | Test Estate 1 | Weekly | + | Test Merchant 3 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 3 | testcontact3@merchant2.co.uk | Test Estate 1 | Monthly | + + Given I have assigned the following operator to the merchants + | OperatorName | MerchantName | MerchantNumber | TerminalNumber | EstateName | + | Safaricom | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + | Voucher | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + | Safaricom | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 | + | Voucher | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 | + | Safaricom | Test Merchant 3 | 00000003 | 10000003 | Test Estate 1 | + | Voucher | Test Merchant 3 | 00000003 | 10000003 | Test Estate 1 | + + Given I have assigned the following devices to the merchants + | DeviceIdentifier | MerchantName | EstateName | + | 123456780 | Test Merchant 1 | Test Estate 1 | + | 123456781 | Test Merchant 2 | Test Estate 1 | + | 123456782 | Test Merchant 3 | Test Estate 1 | + + Given I make the following manual merchant deposits + | Reference | Amount | DateTime | MerchantName | EstateName | + | Deposit1 | 210.00 | Today | Test Merchant 1 | Test Estate 1 | + | Deposit1 | 110.00 | Today | Test Merchant 2 | Test Estate 1 | + | Deposit1 | 110.00 | Today | Test Merchant 3 | Test Estate 1 | + + When I perform the following transactions + | DateTime | TransactionNumber | TransactionType | MerchantName | DeviceIdentifier | EstateName | OperatorName | TransactionAmount | CustomerAccountNumber | CustomerEmailAddress | ContractDescription | ProductName | RecipientEmail | RecipientMobile | + | Today | 1 | Sale | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | | | + | Today | 2 | Sale | Test Merchant 2 | 123456781 | Test Estate 1 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | | | + | Today | 3 | Sale | Test Merchant 3 | 123456782 | Test Estate 1 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | | | + | Today | 4 | Sale | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | + | Today | 5 | Sale | Test Merchant 1 | 123456780 | Test Estate 1 | Voucher | 10.00 | | | Hospital 1 Contract | 10 KES | test@recipient.co.uk | | + | Today | 6 | Sale | Test Merchant 2 | 123456781 | Test Estate 1 | Voucher | 10.00 | | | Hospital 1 Contract | 10 KES | | 123456789 | + | Today | 7 | Sale | Test Merchant 3 | 123456782 | Test Estate 1 | Voucher | 10.00 | | | Hospital 1 Contract | 10 KES | test@recipient.co.uk | | + | Today | 8 | Sale | Test Merchant 3 | 123456782 | Test Estate 1 | Voucher | 10.00 | | | Hospital 1 Contract | 10 KES | test@recipient.co.uk | | + + Then transaction response should contain the following information + | EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage | + | Test Estate 1 | Test Merchant 1 | 1 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 2 | 2 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 3 | 3 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 4 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 5 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 2 | 6 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 3 | 7 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 3 | 8 | 0000 | SUCCESS | + + When I get the pending settlements the following information should be returned + | NextSettlementDate | EstateName | NumberOfFees | + | NextWeek | Test Estate 1 | 1 | + | NextMonth | Test Estate 1 | 1 | + + diff --git a/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature.cs b/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature.cs new file mode 100644 index 00000000..2cdb9a57 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Settlement/Settlement.feature.cs @@ -0,0 +1,644 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.5.0.0 +// SpecFlow Generator Version:3.5.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TransactionProcessor.IntegrationTests.Settlement +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.5.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Xunit.TraitAttribute("Category", "base")] + [Xunit.TraitAttribute("Category", "shared")] + public partial class SettlementFeature : object, Xunit.IClassFixture, System.IDisposable + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private string[] _featureTags = new string[] { + "base", + "shared"}; + + private Xunit.Abstractions.ITestOutputHelper _testOutputHelper; + +#line 1 "Settlement.feature" +#line hidden + + public SettlementFeature(SettlementFeature.FixtureData fixtureData, TransactionProcessor_IntegrationTests_XUnitAssemblyFixture assemblyFixture, Xunit.Abstractions.ITestOutputHelper testOutputHelper) + { + this._testOutputHelper = testOutputHelper; + this.TestInitialize(); + } + + public static void FeatureSetup() + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Settlement", "Settlement", null, ProgrammingLanguage.CSharp, new string[] { + "base", + "shared"}); + testRunner.OnFeatureStart(featureInfo); + } + + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + public virtual void TestInitialize() + { + } + + public virtual void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testOutputHelper); + } + + public virtual void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + public virtual void FeatureBackground() + { +#line 4 +#line hidden + TechTalk.SpecFlow.Table table62 = new TechTalk.SpecFlow.Table(new string[] { + "Name", + "DisplayName", + "Description"}); + table62.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST Scope", + "A scope for Estate Managememt REST"}); + table62.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST Scope", + "A scope for Transaction Processor REST"}); + table62.AddRow(new string[] { + "voucherManagement", + "Voucher Management REST Scope", + "A scope for Voucher Management REST"}); +#line 6 + testRunner.Given("I create the following api scopes", ((string)(null)), table62, "Given "); +#line hidden + TechTalk.SpecFlow.Table table63 = new TechTalk.SpecFlow.Table(new string[] { + "ResourceName", + "DisplayName", + "Secret", + "Scopes", + "UserClaims"}); + table63.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST", + "Secret1", + "estateManagement", + "MerchantId, EstateId, role"}); + table63.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST", + "Secret1", + "transactionProcessor", + ""}); + table63.AddRow(new string[] { + "voucherManagement", + "Voucher Management REST", + "Secret1", + "voucherManagement", + ""}); +#line 12 + testRunner.Given("the following api resources exist", ((string)(null)), table63, "Given "); +#line hidden + TechTalk.SpecFlow.Table table64 = new TechTalk.SpecFlow.Table(new string[] { + "ClientId", + "ClientName", + "Secret", + "AllowedScopes", + "AllowedGrantTypes"}); + table64.AddRow(new string[] { + "serviceClient", + "Service Client", + "Secret1", + "estateManagement,transactionProcessor,voucherManagement", + "client_credentials"}); +#line 18 + testRunner.Given("the following clients exist", ((string)(null)), table64, "Given "); +#line hidden + TechTalk.SpecFlow.Table table65 = new TechTalk.SpecFlow.Table(new string[] { + "ClientId"}); + table65.AddRow(new string[] { + "serviceClient"}); +#line 22 + testRunner.Given("I have a token to access the estate management and transaction processor resource" + + "s", ((string)(null)), table65, "Given "); +#line hidden + TechTalk.SpecFlow.Table table66 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName"}); + table66.AddRow(new string[] { + "Test Estate 1"}); +#line 26 + testRunner.Given("I have created the following estates", ((string)(null)), table66, "Given "); +#line hidden + TechTalk.SpecFlow.Table table67 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName", + "OperatorName", + "RequireCustomMerchantNumber", + "RequireCustomTerminalNumber"}); + table67.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "True", + "True"}); + table67.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "True", + "True"}); +#line 30 + testRunner.Given("I have created the following operators", ((string)(null)), table67, "Given "); +#line hidden + TechTalk.SpecFlow.Table table68 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription"}); + table68.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract"}); + table68.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "Hospital 1 Contract"}); +#line 35 + testRunner.Given("I create a contract with the following values", ((string)(null)), table68, "Given "); +#line hidden + TechTalk.SpecFlow.Table table69 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "DisplayText", + "Value"}); + table69.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Custom", + ""}); + table69.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "Hospital 1 Contract", + "10 KES", + "10 KES", + ""}); +#line 40 + testRunner.When("I create the following Products", ((string)(null)), table69, "When "); +#line hidden + TechTalk.SpecFlow.Table table70 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "CalculationType", + "FeeDescription", + "Value"}); + table70.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Fixed", + "Merchant Commission", + "2.50"}); +#line 45 + testRunner.When("I add the following Transaction Fees", ((string)(null)), table70, "When "); +#line hidden + } + + void System.IDisposable.Dispose() + { + this.TestTearDown(); + } + + [Xunit.SkippableFactAttribute(DisplayName="Settlement Processing")] + [Xunit.TraitAttribute("FeatureTitle", "Settlement")] + [Xunit.TraitAttribute("Description", "Settlement Processing")] + public virtual void SettlementProcessing() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Settlement Processing", null, tagsOfScenario, argumentsOfScenario); +#line 49 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden + TechTalk.SpecFlow.Table table71 = new TechTalk.SpecFlow.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "Country", + "ContactName", + "EmailAddress", + "EstateName", + "SettlementSchedule"}); + table71.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1", + "Immediate"}); + table71.AddRow(new string[] { + "Test Merchant 2", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 2", + "testcontact2@merchant2.co.uk", + "Test Estate 1", + "Weekly"}); + table71.AddRow(new string[] { + "Test Merchant 3", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 3", + "testcontact3@merchant2.co.uk", + "Test Estate 1", + "Monthly"}); +#line 50 + testRunner.Given("I create the following merchants", ((string)(null)), table71, "Given "); +#line hidden + TechTalk.SpecFlow.Table table72 = new TechTalk.SpecFlow.Table(new string[] { + "OperatorName", + "MerchantName", + "MerchantNumber", + "TerminalNumber", + "EstateName"}); + table72.AddRow(new string[] { + "Safaricom", + "Test Merchant 1", + "00000001", + "10000001", + "Test Estate 1"}); + table72.AddRow(new string[] { + "Voucher", + "Test Merchant 1", + "00000001", + "10000001", + "Test Estate 1"}); + table72.AddRow(new string[] { + "Safaricom", + "Test Merchant 2", + "00000002", + "10000002", + "Test Estate 1"}); + table72.AddRow(new string[] { + "Voucher", + "Test Merchant 2", + "00000002", + "10000002", + "Test Estate 1"}); + table72.AddRow(new string[] { + "Safaricom", + "Test Merchant 3", + "00000003", + "10000003", + "Test Estate 1"}); + table72.AddRow(new string[] { + "Voucher", + "Test Merchant 3", + "00000003", + "10000003", + "Test Estate 1"}); +#line 56 + testRunner.Given("I have assigned the following operator to the merchants", ((string)(null)), table72, "Given "); +#line hidden + TechTalk.SpecFlow.Table table73 = new TechTalk.SpecFlow.Table(new string[] { + "DeviceIdentifier", + "MerchantName", + "EstateName"}); + table73.AddRow(new string[] { + "123456780", + "Test Merchant 1", + "Test Estate 1"}); + table73.AddRow(new string[] { + "123456781", + "Test Merchant 2", + "Test Estate 1"}); + table73.AddRow(new string[] { + "123456782", + "Test Merchant 3", + "Test Estate 1"}); +#line 65 + testRunner.Given("I have assigned the following devices to the merchants", ((string)(null)), table73, "Given "); +#line hidden + TechTalk.SpecFlow.Table table74 = new TechTalk.SpecFlow.Table(new string[] { + "Reference", + "Amount", + "DateTime", + "MerchantName", + "EstateName"}); + table74.AddRow(new string[] { + "Deposit1", + "210.00", + "Today", + "Test Merchant 1", + "Test Estate 1"}); + table74.AddRow(new string[] { + "Deposit1", + "110.00", + "Today", + "Test Merchant 2", + "Test Estate 1"}); + table74.AddRow(new string[] { + "Deposit1", + "110.00", + "Today", + "Test Merchant 3", + "Test Estate 1"}); +#line 71 + testRunner.Given("I make the following manual merchant deposits", ((string)(null)), table74, "Given "); +#line hidden + TechTalk.SpecFlow.Table table75 = new TechTalk.SpecFlow.Table(new string[] { + "DateTime", + "TransactionNumber", + "TransactionType", + "MerchantName", + "DeviceIdentifier", + "EstateName", + "OperatorName", + "TransactionAmount", + "CustomerAccountNumber", + "CustomerEmailAddress", + "ContractDescription", + "ProductName", + "RecipientEmail", + "RecipientMobile"}); + table75.AddRow(new string[] { + "Today", + "1", + "Sale", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup", + "", + ""}); + table75.AddRow(new string[] { + "Today", + "2", + "Sale", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup", + "", + ""}); + table75.AddRow(new string[] { + "Today", + "3", + "Sale", + "Test Merchant 3", + "123456782", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup", + "", + ""}); + table75.AddRow(new string[] { + "Today", + "4", + "Sale", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "testcustomer@customer.co.uk", + "Safaricom Contract", + "Variable Topup", + "", + ""}); + table75.AddRow(new string[] { + "Today", + "5", + "Sale", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Voucher", + "10.00", + "", + "", + "Hospital 1 Contract", + "10 KES", + "test@recipient.co.uk", + ""}); + table75.AddRow(new string[] { + "Today", + "6", + "Sale", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Voucher", + "10.00", + "", + "", + "Hospital 1 Contract", + "10 KES", + "", + "123456789"}); + table75.AddRow(new string[] { + "Today", + "7", + "Sale", + "Test Merchant 3", + "123456782", + "Test Estate 1", + "Voucher", + "10.00", + "", + "", + "Hospital 1 Contract", + "10 KES", + "test@recipient.co.uk", + ""}); + table75.AddRow(new string[] { + "Today", + "8", + "Sale", + "Test Merchant 3", + "123456782", + "Test Estate 1", + "Voucher", + "10.00", + "", + "", + "Hospital 1 Contract", + "10 KES", + "test@recipient.co.uk", + ""}); +#line 77 + testRunner.When("I perform the following transactions", ((string)(null)), table75, "When "); +#line hidden + TechTalk.SpecFlow.Table table76 = new TechTalk.SpecFlow.Table(new string[] { + "EstateName", + "MerchantName", + "TransactionNumber", + "ResponseCode", + "ResponseMessage"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "1", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "2", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 3", + "3", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "4", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "5", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "6", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 3", + "7", + "0000", + "SUCCESS"}); + table76.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 3", + "8", + "0000", + "SUCCESS"}); +#line 88 + testRunner.Then("transaction response should contain the following information", ((string)(null)), table76, "Then "); +#line hidden + TechTalk.SpecFlow.Table table77 = new TechTalk.SpecFlow.Table(new string[] { + "NextSettlementDate", + "EstateName", + "NumberOfFees"}); + table77.AddRow(new string[] { + "NextWeek", + "Test Estate 1", + "1"}); + table77.AddRow(new string[] { + "NextMonth", + "Test Estate 1", + "1"}); +#line 99 + testRunner.When("I get the pending settlements the following information should be returned", ((string)(null)), table77, "When "); +#line hidden + } + this.ScenarioCleanup(); + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.5.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class FixtureData : System.IDisposable + { + + public FixtureData() + { + SettlementFeature.FeatureSetup(); + } + + void System.IDisposable.Dispose() + { + SettlementFeature.FeatureTearDown(); + } + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs index 192d0a2a..20fbfdca 100644 --- a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -196,6 +196,15 @@ public async Task WhenICreateTheFollowingMerchants(Table table) } String merchantName = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantName"); + + var settlementSchedule = SpecflowTableHelper.GetStringRowValue(tableRow, "SettlementSchedule"); + + SettlementSchedule schedule = SettlementSchedule.Immediate; + if (String.IsNullOrEmpty(settlementSchedule) == false) + { + schedule = Enum.Parse(settlementSchedule); + } + CreateMerchantRequest createMerchantRequest = new CreateMerchantRequest { Name = merchantName, @@ -210,7 +219,8 @@ public async Task WhenICreateTheFollowingMerchants(Table table) Town = SpecflowTableHelper.GetStringRowValue(tableRow, "Town"), Region = SpecflowTableHelper.GetStringRowValue(tableRow, "Region"), Country = SpecflowTableHelper.GetStringRowValue(tableRow, "Country") - } + }, + SettlementSchedule = schedule }; CreateMerchantResponse response = await this.TestingContext.DockerHelper.EstateClient @@ -860,6 +870,44 @@ public void ThenReconciliationResponseShouldContainTheFollowingInformation(Table } } + [When(@"I get the pending settlements the following information should be returned")] + public async Task WhenIGetThePendingSettlementsTheFollowingInformationShouldBeReturned(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + // Get the merchant name + EstateDetails estateDetails = this.TestingContext.GetEstateDetails(tableRow); + String nextSettlementDateString = SpecflowTableHelper.GetStringRowValue(tableRow, "NextSettlementDate"); + Int32 numberOfFees = SpecflowTableHelper.GetIntValue(tableRow, "NumberOfFees"); + DateTime nextSettlementDate = this.GetNextSettlementDate(DateTime.Now, nextSettlementDateString); + + await Retry.For(async () => + { + PendingSettlementResponse pendingSettlements = + await this.TestingContext.DockerHelper.TransactionProcessorClient.GetPendingSettlementByDate(this.TestingContext.AccessToken, + nextSettlementDate, + estateDetails.EstateId, + CancellationToken.None); + + pendingSettlements.NumberOfFeesPendingSettlement.ShouldBe(numberOfFees); + }, TimeSpan.FromMinutes(3)); + } + } + + private DateTime GetNextSettlementDate(DateTime now, + String nextSettlementDate) + { + if (nextSettlementDate == "NextWeek") + { + return now.AddDays(7); + } + + if (nextSettlementDate == "NextMonth") + { + return now.AddMonths(1); + } + return now; + } } } diff --git a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj index c668dad0..e2aafeb1 100644 --- a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj +++ b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj @@ -9,7 +9,7 @@ - + @@ -73,4 +73,8 @@ + + + + diff --git a/TransactionProcessor.Settlement.DomainEvents/MerchantFeeAddedToPendingSettlementEvent.cs b/TransactionProcessor.Settlement.DomainEvents/MerchantFeeAddedToPendingSettlementEvent.cs new file mode 100644 index 00000000..e38b05fd --- /dev/null +++ b/TransactionProcessor.Settlement.DomainEvents/MerchantFeeAddedToPendingSettlementEvent.cs @@ -0,0 +1,96 @@ +namespace TransactionProcessor.Settlement.DomainEvents +{ + using System; + using Shared.DomainDrivenDesign.EventSourcing; + + public record MerchantFeeAddedToPendingSettlementEvent : DomainEventRecord.DomainEvent + { + #region Properties + + /// + /// Gets the calculated value. + /// + /// + /// The calculated value. + /// + public Decimal CalculatedValue { get; init; } + + /// + /// Gets or sets the fee calculated date time. + /// + /// + /// The fee calculated date time. + /// + public DateTime FeeCalculatedDateTime { get; init; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + public Guid EstateId { get; init; } + + /// + /// Gets the type of the fee calculation. + /// + /// + /// The type of the fee calculation. + /// + public Int32 FeeCalculationType { get; init; } + + /// + /// Gets the fee identifier. + /// + /// + /// The fee identifier. + /// + public Guid FeeId { get; init; } + + /// + /// Gets the fee value. + /// + /// + /// The fee value. + /// + public Decimal FeeValue { get; init; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + public Guid MerchantId { get; init; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + public Guid TransactionId { get; init; } + + #endregion + + public MerchantFeeAddedToPendingSettlementEvent(Guid aggregateId, + Guid estateId, + Guid merchantId, + Guid transactionId, + Decimal calculatedValue, + Int32 feeCalculationType, + Guid feeId, + Decimal feeValue, + DateTime feeCalculatedDateTime) : base(aggregateId, Guid.NewGuid()) + { + this.EstateId = estateId; + this.MerchantId = merchantId; + this.TransactionId = transactionId; + this.CalculatedValue = calculatedValue; + this.FeeCalculationType = feeCalculationType; + this.FeeId = feeId; + this.FeeValue = feeValue; + this.FeeCalculatedDateTime = feeCalculatedDateTime; + } + } +} \ No newline at end of file diff --git a/TransactionProcessor.Settlement.DomainEvents/PendingSettlementCreatedForDateEvent.cs b/TransactionProcessor.Settlement.DomainEvents/PendingSettlementCreatedForDateEvent.cs new file mode 100644 index 00000000..c1155b64 --- /dev/null +++ b/TransactionProcessor.Settlement.DomainEvents/PendingSettlementCreatedForDateEvent.cs @@ -0,0 +1,30 @@ +namespace TransactionProcessor.Settlement.DomainEvents +{ + using System; + using Shared.DomainDrivenDesign.EventSourcing; + + public record PendingSettlementCreatedForDateEvent : DomainEventRecord.DomainEvent + { + #region Properties + + public DateTime SettlementDate { get; init; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + public Guid EstateId { get; init; } + + #endregion + + public PendingSettlementCreatedForDateEvent(Guid aggregateId, + Guid estateId, + DateTime settlementDate) : base(aggregateId, Guid.NewGuid()) + { + this.EstateId = estateId; + this.SettlementDate = settlementDate; + } + } +} \ No newline at end of file diff --git a/TransactionProcessor.Settlement.DomainEvents/TransactionProcessor.Settlement.DomainEvents.csproj b/TransactionProcessor.Settlement.DomainEvents/TransactionProcessor.Settlement.DomainEvents.csproj new file mode 100644 index 00000000..0cccad20 --- /dev/null +++ b/TransactionProcessor.Settlement.DomainEvents/TransactionProcessor.Settlement.DomainEvents.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/TransactionProcessor.SettlementAggregates.Tests/PendingSettlementAggregateTests.cs b/TransactionProcessor.SettlementAggregates.Tests/PendingSettlementAggregateTests.cs new file mode 100644 index 00000000..8b5c4f03 --- /dev/null +++ b/TransactionProcessor.SettlementAggregates.Tests/PendingSettlementAggregateTests.cs @@ -0,0 +1,92 @@ +namespace TransactionProcessor.SettlementAggregates.Tests +{ + using System; + using Shouldly; + using Testing; + using TransactionAggregate; + using Xunit; + + public class PendingSettlementAggregateTests + { + [Fact] + public void PendingSettlementAggregate_CanBeCreated_IsCreated() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + + aggregate.AggregateId.ShouldBe(TestData.PendingSettlementAggregateId); + } + + [Fact] + public void PendingSettlementAggregate_Create_AggregateIsCreated() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + aggregate.EstateId.ShouldBe(TestData.EstateId); + aggregate.SettlmentDate.ShouldBe(TestData.SettlementDate.Date); + aggregate.IsCreated.ShouldBeTrue(); + } + + [Fact] + public void PendingSettlementAggregate_Create_AlreadyCreated_ErrorThrown() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + + Should.Throw(() => + { + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + }); + } + + [Fact] + public void PendingSettlementAggregate_AddFee_FeeIsAdded() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + aggregate.AddFee(TestData.MerchantId, TestData.TransactionId, TestData.CalculatedFeeMerchantFee); + + aggregate.AggregateId.ShouldBe(TestData.PendingSettlementAggregateId); + aggregate.GetNumberOfFeesPendingSettlement().ShouldBe(1); + } + + [Fact] + public void PendingSettlementAggregate_AddFee_TwoFeesAdded_FeesAreAdded() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + aggregate.AddFee(TestData.MerchantId, TestData.TransactionId, TestData.CalculatedFeeMerchantFee); + aggregate.AddFee(TestData.MerchantId, TestData.TransactionId, TestData.CalculatedFeeMerchantFee2); + + aggregate.AggregateId.ShouldBe(TestData.PendingSettlementAggregateId); + aggregate.GetNumberOfFeesPendingSettlement().ShouldBe(2); + } + + [Fact] + public void PendingSettlementAggregate_AddFee_AggregateNotCreated_ErrorThrown() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + + Should.Throw(() => + { + aggregate.AddFee(TestData.MerchantId, + TestData.TransactionId, + TestData.CalculatedFeeMerchantFee); + }); + } + + [Fact] + public void PendingSettlementAggregate_AddFee_DuplicateFee_ErrorThrown() + { + PendingSettlementAggregate aggregate = PendingSettlementAggregate.Create(TestData.PendingSettlementAggregateId); + aggregate.Create(TestData.EstateId, TestData.SettlementDate); + aggregate.AddFee(TestData.MerchantId, TestData.TransactionId, TestData.CalculatedFeeMerchantFee); + + Should.Throw(() => + { + aggregate.AddFee(TestData.MerchantId, + TestData.TransactionId, + TestData.CalculatedFeeMerchantFee); + }); + } + } +} \ No newline at end of file diff --git a/TransactionProcessor.SettlementAggregates.Tests/TransactionProcessor.SettlementAggregates.Tests.csproj b/TransactionProcessor.SettlementAggregates.Tests/TransactionProcessor.SettlementAggregates.Tests.csproj new file mode 100644 index 00000000..115026b2 --- /dev/null +++ b/TransactionProcessor.SettlementAggregates.Tests/TransactionProcessor.SettlementAggregates.Tests.csproj @@ -0,0 +1,28 @@ + + + + net5.0 + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/TransactionProcessor.SettlementAggregates/PendingSettlementAggregate.cs b/TransactionProcessor.SettlementAggregates/PendingSettlementAggregate.cs new file mode 100644 index 00000000..b264d928 --- /dev/null +++ b/TransactionProcessor.SettlementAggregates/PendingSettlementAggregate.cs @@ -0,0 +1,186 @@ +namespace TransactionProcessor.SettlementAggregates +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Models; + using Settlement.DomainEvents; + using Shared.DomainDrivenDesign.EventSourcing; + using Shared.EventStore.Aggregate; + using Shared.General; + + public class PendingSettlementAggregate : Aggregate + { + #region Fields + + /// + /// The calculated fees + /// + private readonly List<(Guid transactionId, CalculatedFee calculatedFee)> CalculatedFeesPendingSettlement; + + private readonly List<(Guid transactionId, CalculatedFee calculatedFee)> SettledCalculatedFees; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + [ExcludeFromCodeCoverage] + public PendingSettlementAggregate() + { + this.CalculatedFeesPendingSettlement = new List<(Guid transactionId, CalculatedFee calculatedFee)>(); + this.SettledCalculatedFees = new List<(Guid transactionId, CalculatedFee calculatedFee)>(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + private PendingSettlementAggregate(Guid aggregateId) + { + Guard.ThrowIfInvalidGuid(aggregateId, "Aggregate Id cannot be an Empty Guid"); + + this.AggregateId = aggregateId; + this.CalculatedFeesPendingSettlement = new List<(Guid transactionId, CalculatedFee calculatedFee)>(); + this.SettledCalculatedFees = new List<(Guid transactionId, CalculatedFee calculatedFee)>(); + } + + #endregion + + #region Properties + + public Guid EstateId { get; private set; } + + public Boolean IsCreated { get; private set; } + + public DateTime SettlmentDate { get; private set; } + + #endregion + + #region Methods + + public void AddFee(Guid merchantId, + Guid transactionId, + CalculatedFee calculatedFee) + { + if (calculatedFee == null) + { + throw new ArgumentNullException(nameof(calculatedFee)); + } + + this.CheckHasBeenCreated(); + this.CheckFeeHasNotAlreadyBeenAdded(calculatedFee); + + DomainEventRecord.DomainEvent @event = null; + if (calculatedFee.FeeType == FeeType.Merchant) + { + // This is a merchant fee + @event = new MerchantFeeAddedToPendingSettlementEvent(this.AggregateId, + this.EstateId, + merchantId, + transactionId, + calculatedFee.CalculatedValue, + (Int32)calculatedFee.FeeCalculationType, + calculatedFee.FeeId, + calculatedFee.FeeValue, + calculatedFee.FeeCalculatedDateTime); + } + else + { + throw new InvalidOperationException("Unsupported Fee Type"); + } + + this.ApplyAndAppend(@event); + } + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// + public static PendingSettlementAggregate Create(Guid aggregateId) + { + return new PendingSettlementAggregate(aggregateId); + } + + public void Create(Guid estateId, + DateTime settlementDate) + { + this.CheckHasNotAlreadyBeenCreated(); + + PendingSettlementCreatedForDateEvent pendingSettlementCreatedForDateEvent = + new PendingSettlementCreatedForDateEvent(this.AggregateId, estateId, settlementDate.Date); + + this.ApplyAndAppend(pendingSettlementCreatedForDateEvent); + } + + public Int32 GetNumberOfFeesPendingSettlement() + { + return this.CalculatedFeesPendingSettlement.Count; + } + + public Int32 GetNumberOfFeesSettled() + { + return this.SettledCalculatedFees.Count; + } + + public override void PlayEvent(IDomainEvent domainEvent) + { + this.PlayEvent((dynamic)domainEvent); + } + + protected override Object GetMetadata() + { + return null; + } + + private void CheckFeeHasNotAlreadyBeenAdded(CalculatedFee calculatedFee) + { + if (this.CalculatedFeesPendingSettlement.Any(c => c.calculatedFee.FeeId == calculatedFee.FeeId)) + { + throw new InvalidOperationException($"Fee with Id [{calculatedFee.FeeId}] has already been added to this days pending settlement"); + } + } + + private void CheckHasBeenCreated() + { + if (this.IsCreated == false) + { + throw new InvalidOperationException($"Pending Settlement not created for this date {this.SettlmentDate}"); + } + } + + private void CheckHasNotAlreadyBeenCreated() + { + if (this.IsCreated) + { + throw new InvalidOperationException($"Pending Settlement already created for this date {this.SettlmentDate}"); + } + } + + private void PlayEvent(MerchantFeeAddedToPendingSettlementEvent domainEvent) + { + this.CalculatedFeesPendingSettlement.Add(new(domainEvent.TransactionId, + new CalculatedFee + { + CalculatedValue = domainEvent.CalculatedValue, + FeeId = domainEvent.FeeId, + FeeType = FeeType.Merchant, + FeeValue = domainEvent.FeeValue, + FeeCalculationType = (CalculationType)domainEvent.FeeCalculationType + })); + } + + private void PlayEvent(PendingSettlementCreatedForDateEvent domainEvent) + { + this.EstateId = domainEvent.EstateId; + this.SettlmentDate = domainEvent.SettlementDate; + this.IsCreated = true; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.SettlementAggregates/TransactionProcessor.SettlementAggregates.csproj b/TransactionProcessor.SettlementAggregates/TransactionProcessor.SettlementAggregates.csproj new file mode 100644 index 00000000..2e5079d9 --- /dev/null +++ b/TransactionProcessor.SettlementAggregates/TransactionProcessor.SettlementAggregates.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + + + + + + + + + + + + diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index ef1fd4fd..d29fd00b 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -72,6 +72,8 @@ public class TestData public static Guid TransactionId = Guid.Parse("AE89B2F6-307B-46F4-A8E7-CEF27097D766"); + public static Guid PendingSettlementAggregateId = Guid.Parse("BAEBA232-CD7F-46F5-AE2E-3204FE69A441"); + public static String TransactionNumber = "0001"; public static TransactionType TransactionTypeLogon = TransactionType.Logon; @@ -696,6 +698,8 @@ public static TokenResponse TokenResponse() public static Guid TransactionFeeId = Guid.Parse("B83FCCCE-0D45-4FC2-8952-ED277A124BDB"); + public static Guid TransactionFeeId2 = Guid.Parse("CA2D5119-1232-41D6-B6FD-9D84B9B5460C"); + public static String TransactionFeeDescription = "Commission for Merchant"; public static Decimal TransactionFeeValue = 0.5m; @@ -722,6 +726,7 @@ public static TokenResponse TokenResponse() public static String VoucherCode = "ABCDE1234"; public static Guid VoucherId = Guid.Parse("ED744C18-1F45-47E7-A9FC-7AAC1D9E9D8A"); + public static DateTime SettlementDate = new DateTime(2021,9,22, 1,2,3); public static List ContractProductTransactionFees => new List @@ -745,6 +750,16 @@ public static TokenResponse TokenResponse() FeeType = FeeType.Merchant }; + public static CalculatedFee CalculatedFeeMerchantFee2 => + new CalculatedFee + { + CalculatedValue = TestData.CalculatedFeeValue, + FeeCalculationType = CalculationType.Fixed, + FeeId = TestData.TransactionFeeId2, + FeeValue = TestData.TransactionFeeValue, + FeeType = FeeType.Merchant + }; + public static CalculatedFee CalculatedFeeServiceProviderFee => new CalculatedFee { diff --git a/TransactionProcessor.sln b/TransactionProcessor.sln index 1057d36c..0a65323c 100644 --- a/TransactionProcessor.sln +++ b/TransactionProcessor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29509.3 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31717.71 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor", "TransactionProcessor\TransactionProcessor.csproj", "{1EBD3402-AF91-4919-A5A1-2E3728918507}" EndProject @@ -31,11 +31,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor.Integr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor.Client", "TransactionProcessor.Client\TransactionProcessor.Client.csproj", "{DAF20F25-8BD1-4FA5-ADAD-71B068CDF393}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.ReconciliationAggregate", "TransactionProcessor.ReconciliationAggregate\TransactionProcessor.ReconciliationAggregate.csproj", "{74CA5F89-A99B-49AE-B690-376900DF488E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor.ReconciliationAggregate", "TransactionProcessor.ReconciliationAggregate\TransactionProcessor.ReconciliationAggregate.csproj", "{74CA5F89-A99B-49AE-B690-376900DF488E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.Reconciliation.DomainEvents", "TransactionProcessor.Reconciliation.DomainEvents\TransactionProcessor.Reconciliation.DomainEvents.csproj", "{E079DF81-BA19-4F71-85D6-AE36463F2B7F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor.Reconciliation.DomainEvents", "TransactionProcessor.Reconciliation.DomainEvents\TransactionProcessor.Reconciliation.DomainEvents.csproj", "{E079DF81-BA19-4F71-85D6-AE36463F2B7F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.ReconciliationAggregate.Tests", "TransactionProcessor.ReconciliationAggregate.Tests\TransactionProcessor.ReconciliationAggregate.Tests.csproj", "{E71A7E6D-B320-40A1-BF86-C2857B472313}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessor.ReconciliationAggregate.Tests", "TransactionProcessor.ReconciliationAggregate.Tests\TransactionProcessor.ReconciliationAggregate.Tests.csproj", "{E71A7E6D-B320-40A1-BF86-C2857B472313}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.SettlementAggregates", "TransactionProcessor.SettlementAggregates\TransactionProcessor.SettlementAggregates.csproj", "{2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.SettlementAggregates.Tests", "TransactionProcessor.SettlementAggregates.Tests\TransactionProcessor.SettlementAggregates.Tests.csproj", "{1FF2753E-6F90-400D-9A98-E5FE3F79518E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.Settlement.DomainEvents", "TransactionProcessor.Settlement.DomainEvents\TransactionProcessor.Settlement.DomainEvents.csproj", "{1D6CF3B6-41D3-46B8-BD6B-03FD32483763}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -103,6 +109,18 @@ Global {E71A7E6D-B320-40A1-BF86-C2857B472313}.Debug|Any CPU.Build.0 = Debug|Any CPU {E71A7E6D-B320-40A1-BF86-C2857B472313}.Release|Any CPU.ActiveCfg = Release|Any CPU {E71A7E6D-B320-40A1-BF86-C2857B472313}.Release|Any CPU.Build.0 = Release|Any CPU + {2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B}.Release|Any CPU.Build.0 = Release|Any CPU + {1FF2753E-6F90-400D-9A98-E5FE3F79518E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FF2753E-6F90-400D-9A98-E5FE3F79518E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FF2753E-6F90-400D-9A98-E5FE3F79518E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FF2753E-6F90-400D-9A98-E5FE3F79518E}.Release|Any CPU.Build.0 = Release|Any CPU + {1D6CF3B6-41D3-46B8-BD6B-03FD32483763}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D6CF3B6-41D3-46B8-BD6B-03FD32483763}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D6CF3B6-41D3-46B8-BD6B-03FD32483763}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D6CF3B6-41D3-46B8-BD6B-03FD32483763}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,6 +141,9 @@ Global {74CA5F89-A99B-49AE-B690-376900DF488E} = {749ADE74-A6F0-4469-A638-8FD7E82A8667} {E079DF81-BA19-4F71-85D6-AE36463F2B7F} = {749ADE74-A6F0-4469-A638-8FD7E82A8667} {E71A7E6D-B320-40A1-BF86-C2857B472313} = {71B30DC4-AB27-4D30-8481-B4C326D074CB} + {2FF2AE7B-21F8-4EEF-9B47-3FCDAA39885B} = {749ADE74-A6F0-4469-A638-8FD7E82A8667} + {1FF2753E-6F90-400D-9A98-E5FE3F79518E} = {71B30DC4-AB27-4D30-8481-B4C326D074CB} + {1D6CF3B6-41D3-46B8-BD6B-03FD32483763} = {749ADE74-A6F0-4469-A638-8FD7E82A8667} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {193D13DE-424B-4D50-B674-01F9E4CC2CA9} diff --git a/TransactionProcessor/Common/Examples/ExampleData.cs b/TransactionProcessor/Common/Examples/ExampleData.cs index 1b3d709b..ed776509 100644 --- a/TransactionProcessor/Common/Examples/ExampleData.cs +++ b/TransactionProcessor/Common/Examples/ExampleData.cs @@ -1,10 +1,12 @@ namespace TransactionProcessor.Common.Examples { using System; + using System.Diagnostics.CodeAnalysis; /// /// /// + [ExcludeFromCodeCoverage] internal static class ExampleData { #region Fields diff --git a/TransactionProcessor/Common/Examples/TransactionRequestExample.cs b/TransactionProcessor/Common/Examples/TransactionRequestExample.cs index 4f473991..86408c14 100644 --- a/TransactionProcessor/Common/Examples/TransactionRequestExample.cs +++ b/TransactionProcessor/Common/Examples/TransactionRequestExample.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using DataTransferObjects; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Filters; @@ -10,6 +11,7 @@ /// /// /// + [ExcludeFromCodeCoverage] public class TransactionRequestExample : IMultipleExamplesProvider { #region Methods diff --git a/TransactionProcessor/Common/Examples/TransactionResponseExample.cs b/TransactionProcessor/Common/Examples/TransactionResponseExample.cs index 11768d18..22642fa0 100644 --- a/TransactionProcessor/Common/Examples/TransactionResponseExample.cs +++ b/TransactionProcessor/Common/Examples/TransactionResponseExample.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using DataTransferObjects; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Filters; @@ -10,6 +11,7 @@ /// /// /// + [ExcludeFromCodeCoverage] public class TransactionResponseExample : IMultipleExamplesProvider { /// diff --git a/TransactionProcessor/Controllers/SettlementController.cs b/TransactionProcessor/Controllers/SettlementController.cs new file mode 100644 index 00000000..0c3388c4 --- /dev/null +++ b/TransactionProcessor/Controllers/SettlementController.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace TransactionProcessor.Controllers +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Threading.Tasks; + using BusinessLogic.Common; + using DataTransferObjects; + using Microsoft.AspNetCore.Authorization; + using SettlementAggregates; + using Shared.DomainDrivenDesign.EventSourcing; + using Shared.EventStore.Aggregate; + + [ExcludeFromCodeCoverage] + [Route(SettlementController.ControllerRoute)] + [ApiController] + [Authorize] + public class SettlementController : ControllerBase + { + private readonly IAggregateRepository PendingSettlmentAggregateRepository; + + #region Others + + /// + /// The controller name + /// + public const String ControllerName = "settlements"; + + /// + /// The controller route + /// + private const String ControllerRoute = "api/" + SettlementController.ControllerName; + + public SettlementController(IAggregateRepository pendingSettlmentAggregateRepository) + { + this.PendingSettlmentAggregateRepository = pendingSettlmentAggregateRepository; + } + + [HttpGet] + [Route("{pendingSettlementDate}/estates/{estateId}/pending")] + public async Task GetPendingSettlement([FromRoute] DateTime pendingSettlementDate, + [FromRoute] Guid estateId, + CancellationToken cancellationToken) + { + // TODO: Convert to using a manager/model/factory + // Convert the date passed in to a guid + var aggregateId = pendingSettlementDate.Date.ToGuid(); + + var pendingSettlementAggregate = await this.PendingSettlmentAggregateRepository.GetLatestVersion(aggregateId, cancellationToken); + + var pendingSettlementResponse = new PendingSettlementResponse + { + EstateId = pendingSettlementAggregate.EstateId, + NumberOfFeesPendingSettlement = pendingSettlementAggregate.GetNumberOfFeesPendingSettlement(), + NumberOfFeesSettled = pendingSettlementAggregate.GetNumberOfFeesSettled(), + SettlementDate = pendingSettlementAggregate.SettlmentDate, + }; + + return this.Ok(pendingSettlementResponse); + + } + #endregion + + } +} diff --git a/TransactionProcessor/Program.cs b/TransactionProcessor/Program.cs index d34e1910..aa0a32c6 100644 --- a/TransactionProcessor/Program.cs +++ b/TransactionProcessor/Program.cs @@ -15,6 +15,7 @@ namespace TransactionProcessor using EventStore.Client; using Microsoft.Extensions.DependencyInjection; using Reconciliation.DomainEvents; + using Settlement.DomainEvents; using Shared.EventStore.Aggregate; using Shared.EventStore.EventHandling; using Shared.EventStore.Subscriptions; @@ -48,6 +49,11 @@ public static IHostBuilder CreateHostBuilder(string[] args) }) .ConfigureServices(services => { + PendingSettlementCreatedForDateEvent s = + new PendingSettlementCreatedForDateEvent(Guid.Parse("62CA5BF0-D138-4A19-9970-A4F7D52DE292"), + Guid.Parse("3E42516B-6C6F-4F86-BF08-3EF0ACDDDD55"), + DateTime.Now); + TransactionHasStartedEvent t = new TransactionHasStartedEvent(Guid.Parse("2AA2D43B-5E24-4327-8029-1135B20F35CE"), Guid.NewGuid(),Guid.NewGuid(), DateTime.Now, "","","","",null); diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs index 6bf7ecbd..50167eae 100644 --- a/TransactionProcessor/Startup.cs +++ b/TransactionProcessor/Startup.cs @@ -42,6 +42,7 @@ namespace TransactionProcessor using Newtonsoft.Json.Serialization; using NLog.Extensions.Logging; using SecurityService.Client; + using SettlementAggregates; using Shared.DomainDrivenDesign.CommandHandling; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EntityFramework.ConnectionStringConfiguration; @@ -55,6 +56,7 @@ namespace TransactionProcessor using Shared.Repositories; using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.SwaggerGen; + using TransactionAggregate; using VoucherManagement.Client; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -155,6 +157,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton, AggregateRepository>(); services.AddSingleton, AggregateRepository>(); + services.AddSingleton, AggregateRepository>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();