From dc2d7f31df19583edb4034a4854e7c7a07dd5d57 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Tue, 24 Mar 2020 12:09:28 +0000 Subject: [PATCH] Operator Respose now recorded in aggregate --- .../TransactionAggregateManagerTests.cs | 227 +++++++++ .../Services/TransactionDomainServiceTests.cs | 350 ++++++-------- .../OperatorInterfaces/OperatorResponse.cs | 23 +- .../Services/ITransactionAggregateManager.cs | 151 ++++++ .../Services/TransactionAggregateManager.cs | 272 +++++++++++ .../Services/TransactionDomainService.cs | 102 ++-- .../Services/TransactionResponseCode.cs | 1 + TransactionProcessor.Testing/TestData.cs | 69 +++ .../AdditionalRequestDataRecordedEvent.cs | 98 ++++ .../AdditionalResponseDataRecordedEvent.cs | 98 ++++ .../TransactionAuthorisedByOperatorEvent.cs | 179 +++++++ .../TransactionDeclinedByOperatorEvent.cs | 147 ++++++ .../DomainEventTests.cs | 96 ++++ .../TransactionAggregateTests.cs | 444 +++++++++++++++++- ...rocessor.TransactionAggregate.Tests.csproj | 2 +- .../TransactionAggregate.cs | 270 ++++++++++- TransactionProcessor/Startup.cs | 1 + 17 files changed, 2241 insertions(+), 289 deletions(-) create mode 100644 TransactionProcessor.BusinessLogic.Tests/Services/TransactionAggregateManagerTests.cs create mode 100644 TransactionProcessor.BusinessLogic/Services/ITransactionAggregateManager.cs create mode 100644 TransactionProcessor.BusinessLogic/Services/TransactionAggregateManager.cs create mode 100644 TransactionProcessor.Transaction.DomainEvents/AdditionalRequestDataRecordedEvent.cs create mode 100644 TransactionProcessor.Transaction.DomainEvents/AdditionalResponseDataRecordedEvent.cs create mode 100644 TransactionProcessor.Transaction.DomainEvents/TransactionAuthorisedByOperatorEvent.cs create mode 100644 TransactionProcessor.Transaction.DomainEvents/TransactionDeclinedByOperatorEvent.cs diff --git a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionAggregateManagerTests.cs b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionAggregateManagerTests.cs new file mode 100644 index 00000000..26ef8a89 --- /dev/null +++ b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionAggregateManagerTests.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.BusinessLogic.Tests.Services +{ + using System.Threading; + using System.Threading.Tasks; + using BusinessLogic.Services; + using Models; + using Moq; + using Shared.DomainDrivenDesign.EventStore; + using Shared.EventStore.EventStore; + using Shouldly; + using Testing; + using TransactionAggregate; + using Xunit; + + public class TransactionAggregateManagerTests + { + [Fact] + public async Task TransactionAggregateManager_AuthoriseTransaction_TransactionAuthorised() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.AuthoriseTransaction(TestData.EstateId, + TestData.TransactionId, + TestData.OperatorResponse, + TestData.TransactionResponseCodeSuccess, + TestData.ResponseMessage, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_AuthoriseTransactionLocally_TransactionLocallyAuthorised() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.AuthoriseTransactionLocally(TestData.EstateId, + TestData.TransactionId, + TestData.AuthorisationCode, + (TestData.ResponseMessage, TestData.TransactionResponseCodeSuccess), + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_DeclineTransaction_TransactionDeclined() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.DeclineTransaction(TestData.EstateId, + TestData.TransactionId, + TestData.OperatorResponse, + TestData.TransactionResponseCodeDeclinedByOperator, + TestData.ResponseMessage, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_DeclineTransactionLocally_TransactionLocallyDeclined() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.DeclineTransactionLocally(TestData.EstateId, + TestData.TransactionId, + (TestData.ResponseMessage, TestData.TransactionResponseCodeSuccess), + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_GetAggregate_AggregateReturned() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetCompletedTransactionAggregate); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + TransactionAggregate result = await transactionAggregateManager.GetAggregate(TestData.EstateId, + TestData.TransactionId, + CancellationToken.None); + + result.ShouldNotBeNull(); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalRequestData_AdditionalRequestDataRecorded() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalRequestData(TestData.EstateId, + TestData.TransactionId, + TestData.AdditionalTransactionMetaData, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalRequestData_NullAdditionalRequestData_NoActionPerformed() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalRequestData(TestData.EstateId, + TestData.TransactionId, + TestData.NullAdditionalTransactionMetaData, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalRequestData_EmptyAdditionalRequestData_NoActionPerformed() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalRequestData(TestData.EstateId, + TestData.TransactionId, + TestData.EmptyAdditionalTransactionMetaData, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalResponseData_AdditionalResponseDataRecorded() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalResponseData(TestData.EstateId, + TestData.TransactionId, + TestData.AdditionalTransactionMetaData, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalResponseData_NullAdditionalResponseData_NoActionPerformed() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalResponseData(TestData.EstateId, + TestData.TransactionId, + TestData.NullAdditionalTransactionMetaData, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_RecordAdditionalResponseData_EmptyAdditionalResponseData_NoActionPerformed() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetStartedTransactionAggregate()); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.RecordAdditionalResponseData(TestData.EstateId, + TestData.TransactionId, + TestData.EmptyAdditionalTransactionMetaData, + CancellationToken.None); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public async Task TransactionAggregateManager_StartTransaction_TransactionStarted(TransactionType transactionType) + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEmptyTransactionAggregate); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.StartTransaction(TestData.TransactionId, + TestData.TransactionDateTime, + TestData.TransactionNumber, + transactionType, + TestData.EstateId, + TestData.MerchantId, + TestData.DeviceIdentifier, + CancellationToken.None); + } + + [Fact] + public async Task TransactionAggregateManager_CompleteTransaction_TransactionCompleted() + { + Mock> aggregateRepository = new Mock>(); + aggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate); + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(a => a.GetAggregateRepository(It.IsAny())).Returns(aggregateRepository.Object); + TransactionAggregateManager transactionAggregateManager = new TransactionAggregateManager(aggregateRepositoryManager.Object); + + await transactionAggregateManager.CompleteTransaction(TestData.EstateId, + TestData.TransactionId, + CancellationToken.None); + } + } +} diff --git a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs index f4fbb92d..c1573b26 100644 --- a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs @@ -9,6 +9,7 @@ namespace TransactionProcessor.BusinessLogic.Tests.Services using BusinessLogic.OperatorInterfaces; using BusinessLogic.Services; using EstateManagement.Client; + using EstateManagement.DataTransferObjects.Responses; using Microsoft.Extensions.Configuration; using Models; using Moq; @@ -34,17 +35,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_TransactionIs Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); @@ -52,12 +43,14 @@ public async Task TransactionDomainService_ProcessLogonTransaction_TransactionIs estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedTransactionAggregate); Mock operatorProxy = new Mock(); Func operatorProxyResolver = (operatorName) => { return operatorProxy.Object; }; TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -79,17 +72,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithN Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -99,9 +82,11 @@ public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithN estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithNullDevices); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedTransactionAggregate); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -123,17 +108,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithN Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); @@ -144,9 +119,11 @@ public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithN estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithNoDevices); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedTransactionAggregate); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -168,17 +145,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_IncorrectDevi Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidDeviceIdentifier)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -188,9 +155,11 @@ public async Task TransactionDomainService_ProcessLogonTransaction_IncorrectDevi estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidDeviceIdentifier)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -212,17 +181,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_InvalidEstate Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidEstateId)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -232,9 +191,11 @@ public async Task TransactionDomainService_ProcessLogonTransaction_InvalidEstate estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ThrowsAsync(new Exception("Exception", new KeyNotFoundException("Invalid Estate"))); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidEstateId)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -256,17 +217,7 @@ public async Task TransactionDomainService_ProcessLogonTransaction_InvalidMercha Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidMerchantId)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -276,9 +227,11 @@ public async Task TransactionDomainService_ProcessLogonTransaction_InvalidMercha estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetEmptyMerchantResponse); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidMerchantId)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId, @@ -293,24 +246,14 @@ public async Task TransactionDomainService_ProcessLogonTransaction_InvalidMercha } [Fact] - public async Task TransactionDomainService_ProcessSaleTransaction_TransactionIsProcessed() + public async Task TransactionDomainService_ProcessSaleTransaction_SuccesfulOperatorResponse_TransactionIsProcessed() { IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); @@ -318,12 +261,26 @@ public async Task TransactionDomainService_ProcessSaleTransaction_TransactionIsP estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedTransactionAggregate); Mock operatorProxy = new Mock(); + operatorProxy.Setup(o => o.ProcessSaleMessage(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())).ReturnsAsync(new OperatorResponse + { + ResponseMessage = TestData.OperatorResponseMessage, + IsSuccessful = true, + AuthorisationCode = TestData.OperatorAuthorisationCode, + TransactionId = TestData.OperatorTransactionId, + ResponseCode = TestData.ResponseCode + }); Func operatorProxyResolver = (operatorName) => { return operatorProxy.Object; }; TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -340,24 +297,63 @@ public async Task TransactionDomainService_ProcessSaleTransaction_TransactionIsP } [Fact] - public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNullDevices_TransactionIsProcessed() + public async Task TransactionDomainService_ProcessSaleTransaction_FailedOperatorResponse_TransactionIsProcessed() { IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); + Mock transactionAggregateManager = new Mock(); + Mock estateClient = new Mock(); + Mock securityServiceClient = new Mock(); + + securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); + estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.TransactionDeclinedByOperator)); - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoValidDevices)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + Mock operatorProxy = new Mock(); + operatorProxy.Setup(o => o.ProcessSaleMessage(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())).ReturnsAsync(new OperatorResponse + { + ResponseMessage = TestData.DeclinedOperatorResponseMessage, + IsSuccessful = false, + ResponseCode = TestData.DeclinedOperatorResponseCode + }); + Func operatorProxyResolver = (operatorName) => { return operatorProxy.Object; }; + TransactionDomainService transactionDomainService = + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, + operatorProxyResolver); + + ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, + TestData.EstateId, + TestData.MerchantId, + TestData.TransactionDateTime, + TestData.TransactionNumber, + TestData.DeviceIdentifier, + TestData.OperatorIdentifier1, + TestData.AdditionalTransactionMetaData, + CancellationToken.None); + + this.ValidateResponse(response, TransactionResponseCode.TransactionDeclinedByOperator); + } + + [Fact] + public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNullDevices_TransactionIsProcessed() + { + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); + ConfigurationReader.Initialise(configurationRoot); + + Logger.Initialise(NullLogger.Instance); + + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -367,9 +363,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNu estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithNullDevices); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoValidDevices)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -393,17 +391,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNo Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoValidDevices)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); @@ -414,9 +402,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNo estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithNoDevices); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.NoValidDevices)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -440,17 +430,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_IncorrectDevic Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidDeviceIdentifier)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -460,9 +440,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_IncorrectDevic estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidDeviceIdentifier)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -486,17 +468,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_InvalidEstate_ Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidEstateId)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -506,9 +478,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_InvalidEstate_ estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ThrowsAsync(new Exception("Exception", new KeyNotFoundException("Invalid Estate"))); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidEstateId)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -532,17 +506,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_InvalidMerchan Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidMerchantId)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -552,9 +516,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_InvalidMerchan estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetEmptyMerchantResponse); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.InvalidMerchantId)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -578,17 +544,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_EstateWithEmpt Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoEstateOperators)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -598,9 +554,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_EstateWithEmpt estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithEmptyOperators); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoEstateOperators)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -624,17 +582,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_EstateWithNull Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoEstateOperators)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -644,9 +592,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_EstateWithNull estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithNullOperators); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoEstateOperators)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -670,17 +620,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_OperatorNotSup Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.OperatorNotValidForEstate)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -690,9 +630,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_OperatorNotSup estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator1); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.OperatorNotValidForEstate)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -716,17 +658,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithEm Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoMerchantOperators)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -736,9 +668,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithEm estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithEmptyOperators); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.NoMerchantOperators)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -762,17 +696,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNu Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.NoMerchantOperators)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -782,9 +706,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MerchantWithNu estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithNullOperators); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.NoMerchantOperators)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, @@ -808,17 +734,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_OperatorNotSup Logger.Initialise(NullLogger.Instance); - Mock aggregateRepositoryManager = new Mock(); - Mock> transactionAggregateRepository = new Mock>(); - - aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(transactionAggregateRepository.Object); - transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny(), It.IsAny())) - .ReturnsAsync(TestData.GetEmptyTransactionAggregate) - .ReturnsAsync(TestData.GetStartedTransactionAggregate) - .ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.OperatorNotValidForMerchant)) - .ReturnsAsync(TestData.GetCompletedTransactionAggregate); - transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + Mock transactionAggregateManager = new Mock(); Mock estateClient = new Mock(); Mock securityServiceClient = new Mock(); Mock operatorProxy = new Mock(); @@ -828,9 +744,11 @@ public async Task TransactionDomainService_ProcessSaleTransaction_OperatorNotSup estateClient.Setup(e => e.GetEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetEstateResponseWithOperator1); estateClient.Setup(e => e.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.GetMerchantResponseWithOperator2); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.OperatorNotValidForMerchant)); TransactionDomainService transactionDomainService = - new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object, + new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object, operatorProxyResolver); ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId, diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/OperatorResponse.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/OperatorResponse.cs index 34d3cc7f..7ac51a31 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/OperatorResponse.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/OperatorResponse.cs @@ -1,6 +1,7 @@ namespace TransactionProcessor.BusinessLogic.OperatorInterfaces { using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; /// @@ -11,6 +12,14 @@ public class OperatorResponse { #region Properties + /// + /// Gets or sets the additional transaction response metadata. + /// + /// + /// The additional transaction response metadata. + /// + public Dictionary AdditionalTransactionResponseMetadata { get; set; } + /// /// Gets or sets the authorisation code. /// @@ -19,6 +28,14 @@ public class OperatorResponse /// public String AuthorisationCode { get; set; } + /// + /// Gets or sets a value indicating whether this instance is successful. + /// + /// + /// true if this instance is successful; otherwise, false. + /// + public Boolean IsSuccessful { get; set; } + /// /// Gets or sets the response code. /// @@ -36,12 +53,12 @@ public class OperatorResponse public String ResponseMessage { get; set; } /// - /// Gets or sets a value indicating whether this instance is successful. + /// Gets or sets the transaction identifier. /// /// - /// true if this instance is successful; otherwise, false. + /// The transaction identifier. /// - public Boolean IsSuccessful { get; set; } + public String TransactionId { get; set; } #endregion } diff --git a/TransactionProcessor.BusinessLogic/Services/ITransactionAggregateManager.cs b/TransactionProcessor.BusinessLogic/Services/ITransactionAggregateManager.cs new file mode 100644 index 00000000..657a8a6e --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Services/ITransactionAggregateManager.cs @@ -0,0 +1,151 @@ +namespace TransactionProcessor.BusinessLogic.Services +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Models; + using OperatorInterfaces; + using TransactionAggregate; + + /// + /// + /// + public interface ITransactionAggregateManager + { + #region Methods + + /// + /// Authorises the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The operator response. + /// The transaction response code. + /// The response message. + /// The cancellation token. + /// + Task AuthoriseTransaction(Guid estateId, + Guid transactionId, + OperatorResponse operatorResponse, + TransactionResponseCode transactionResponseCode, + String responseMessage, + CancellationToken cancellationToken); + + /// + /// Authorises the transaction locally. + /// + /// The estate identifier. + /// The transaction identifier. + /// The authorisation code. + /// The validation result. + /// The cancellation token. + /// + Task AuthoriseTransactionLocally(Guid estateId, + Guid transactionId, + String authorisationCode, + (String responseMessage, TransactionResponseCode responseCode) validationResult, + CancellationToken cancellationToken); + + /// + /// Completes the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The cancellation token. + /// + Task CompleteTransaction(Guid estateId, + Guid transactionId, + CancellationToken cancellationToken); + + /// + /// Declines the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The operator response. + /// The transaction response code. + /// The response message. + /// The cancellation token. + /// + Task DeclineTransaction(Guid estateId, + Guid transactionId, + OperatorResponse operatorResponse, + TransactionResponseCode transactionResponseCode, + String responseMessage, + CancellationToken cancellationToken); + + /// + /// Declines the transaction locally. + /// + /// The estate identifier. + /// The transaction identifier. + /// The validation result. + /// The cancellation token. + /// + Task DeclineTransactionLocally(Guid estateId, + Guid transactionId, + (String responseMessage, TransactionResponseCode responseCode) validationResult, + CancellationToken cancellationToken); + + /// + /// Gets the aggregate. + /// + /// The estate identifier. + /// The transaction identifier. + /// The cancellation token. + /// + Task GetAggregate(Guid estateId, + Guid transactionId, + CancellationToken cancellationToken); + + /// + /// Records the additional request data. + /// + /// The estate identifier. + /// The transaction identifier. + /// The additional transaction request metadata. + /// The cancellation token. + /// + Task RecordAdditionalRequestData(Guid estateId, + Guid transactionId, + Dictionary additionalTransactionRequestMetadata, + CancellationToken cancellationToken); + + /// + /// Records the additional response data. + /// + /// The estate identifier. + /// The transaction identifier. + /// The additional transaction response metadata. + /// The cancellation token. + /// + Task RecordAdditionalResponseData(Guid estateId, + Guid transactionId, + Dictionary additionalTransactionResponseMetadata, + CancellationToken cancellationToken); + + /// + /// Starts the transaction. + /// + /// The transaction identifier. + /// The transaction date time. + /// The transaction number. + /// Type of the transaction. + /// The estate identifier. + /// The merchant identifier. + /// The device identifier. + /// The cancellation token. + /// + Task StartTransaction(Guid transactionId, + DateTime transactionDateTime, + String transactionNumber, + TransactionType transactionType, + Guid estateId, + Guid merchantId, + String deviceIdentifier, + CancellationToken cancellationToken); + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionAggregateManager.cs b/TransactionProcessor.BusinessLogic/Services/TransactionAggregateManager.cs new file mode 100644 index 00000000..5c5d1f72 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Services/TransactionAggregateManager.cs @@ -0,0 +1,272 @@ +namespace TransactionProcessor.BusinessLogic.Services +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.EntityFrameworkCore.Internal; + using Models; + using OperatorInterfaces; + using Shared.DomainDrivenDesign.EventStore; + using Shared.EventStore.EventStore; + using TransactionAggregate; + + /// + /// + /// + /// + public class TransactionAggregateManager : ITransactionAggregateManager + { + #region Fields + + /// + /// The aggregate repository manager + /// + private readonly IAggregateRepositoryManager AggregateRepositoryManager; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate repository manager. + public TransactionAggregateManager(IAggregateRepositoryManager aggregateRepositoryManager) + { + this.AggregateRepositoryManager = aggregateRepositoryManager; + } + + #endregion + + #region Methods + + /// + /// Authorises the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The operator response. + /// The transaction response code. + /// The response message. + /// The cancellation token. + public async Task AuthoriseTransaction(Guid estateId, + Guid transactionId, + OperatorResponse operatorResponse, + TransactionResponseCode transactionResponseCode, + String responseMessage, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.AuthoriseTransaction(operatorResponse.AuthorisationCode, + operatorResponse.ResponseCode, + operatorResponse.ResponseMessage, + operatorResponse.TransactionId, + ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'), + responseMessage); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + /// + /// Authorises the transaction locally. + /// + /// The estate identifier. + /// The transaction identifier. + /// The authorisation code. + /// The validation result. + /// The cancellation token. + public async Task AuthoriseTransactionLocally(Guid estateId, + Guid transactionId, + String authorisationCode, + (String responseMessage, TransactionResponseCode responseCode) validationResult, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.AuthoriseTransactionLocally(authorisationCode, + ((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), + validationResult.responseMessage); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + /// + /// Completes the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The cancellation token. + public async Task CompleteTransaction(Guid estateId, + Guid transactionId, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.CompleteTransaction(); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + /// + /// Declines the transaction. + /// + /// The estate identifier. + /// The transaction identifier. + /// The operator response. + /// The transaction response code. + /// The response message. + /// The cancellation token. + public async Task DeclineTransaction(Guid estateId, + Guid transactionId, + OperatorResponse operatorResponse, + TransactionResponseCode transactionResponseCode, + String responseMessage, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.DeclineTransaction(operatorResponse.ResponseCode, + operatorResponse.ResponseMessage, + ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'), + responseMessage); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + /// + /// Declines the transaction locally. + /// + /// The estate identifier. + /// The transaction identifier. + /// The validation result. + /// The cancellation token. + public async Task DeclineTransactionLocally(Guid estateId, + Guid transactionId, + (String responseMessage, TransactionResponseCode responseCode) validationResult, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), validationResult.responseMessage); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + /// + /// Gets the aggregate. + /// + /// The estate identifier. + /// The transaction identifier. + /// The cancellation token. + /// + public async Task GetAggregate(Guid estateId, + Guid transactionId, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + return transactionAggregate; + } + + /// + /// Records the additional request data. + /// + /// The estate identifier. + /// The transaction identifier. + /// The additional transaction request metadata. + /// The cancellation token. + public async Task RecordAdditionalRequestData(Guid estateId, + Guid transactionId, + Dictionary additionalTransactionRequestMetadata, + CancellationToken cancellationToken) + { + if (additionalTransactionRequestMetadata != null && additionalTransactionRequestMetadata.Any()) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.RecordAdditionalRequestData(additionalTransactionRequestMetadata); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + } + + /// + /// Records the additional response data. + /// + /// The estate identifier. + /// The transaction identifier. + /// The additional transaction response metadata. + /// The cancellation token. + public async Task RecordAdditionalResponseData(Guid estateId, + Guid transactionId, + Dictionary additionalTransactionResponseMetadata, + CancellationToken cancellationToken) + { + if (additionalTransactionResponseMetadata != null && additionalTransactionResponseMetadata.Any()) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.RecordAdditionalResponseData(additionalTransactionResponseMetadata); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + } + + /// + /// Starts the transaction. + /// + /// The transaction identifier. + /// The transaction date time. + /// The transaction number. + /// Type of the transaction. + /// The estate identifier. + /// The merchant identifier. + /// The device identifier. + /// The cancellation token. + public async Task StartTransaction(Guid transactionId, + DateTime transactionDateTime, + String transactionNumber, + TransactionType transactionType, + Guid estateId, + Guid merchantId, + String deviceIdentifier, + CancellationToken cancellationToken) + { + IAggregateRepository transactionAggregateRepository = + this.AggregateRepositoryManager.GetAggregateRepository(estateId); + + TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); + + transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier); + + await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index e0b16501..b2c36a73 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -28,9 +28,9 @@ public class TransactionDomainService : ITransactionDomainService #region Fields /// - /// The aggregate repository manager + /// The transaction aggregate manager /// - private readonly IAggregateRepositoryManager AggregateRepositoryManager; + private readonly ITransactionAggregateManager TransactionAggregateManager; /// /// The estate client @@ -51,16 +51,16 @@ public class TransactionDomainService : ITransactionDomainService /// /// Initializes a new instance of the class. /// - /// The aggregate repository manager. + /// The transaction aggregate manager. /// The estate client. /// The security service client. /// The operator proxy resolver. - public TransactionDomainService(IAggregateRepositoryManager aggregateRepositoryManager, + public TransactionDomainService(ITransactionAggregateManager transactionAggregateManager, IEstateClient estateClient, ISecurityServiceClient securityServiceClient, Func operatorProxyResolver) { - this.AggregateRepositoryManager = aggregateRepositoryManager; + this.TransactionAggregateManager = transactionAggregateManager; this.EstateClient = estateClient; this.SecurityServiceClient = securityServiceClient; this.OperatorProxyResolver = operatorProxyResolver; @@ -91,34 +91,32 @@ public async Task ProcessLogonTransaction(Guid { TransactionType transactionType = TransactionType.Logon; - IAggregateRepository transactionAggregateRepository = - this.AggregateRepositoryManager.GetAggregateRepository(estateId); - - TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.StartTransaction(transactionId, + transactionDateTime, + transactionNumber, + transactionType, + estateId, + merchantId, + deviceIdentifier, + cancellationToken); (String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateLogonTransaction(estateId, merchantId, deviceIdentifier, cancellationToken); if (validationResult.responseCode == TransactionResponseCode.Success) { // Record the successful validation - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); // TODO: Generate local authcode - transactionAggregate.AuthoriseTransactionLocally("ABCD1234", ((Int32)validationResult.responseCode).ToString().PadLeft(4,'0'), validationResult.responseMessage); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.AuthoriseTransactionLocally(estateId, transactionId, "ABCD1234", validationResult, cancellationToken); } else { // Record the failure - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), validationResult.responseMessage); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.DeclineTransactionLocally(estateId, transactionId, validationResult, cancellationToken); } - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.CompleteTransaction(); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.CompleteTransaction(estateId, transactionId, cancellationToken); + + TransactionAggregate transactionAggregate = await this.TransactionAggregateManager.GetAggregate(estateId, transactionId, cancellationToken); return new ProcessLogonTransactionResponse { @@ -154,39 +152,65 @@ public async Task ProcessSaleTransaction(Guid tr { TransactionType transactionType = TransactionType.Sale; - IAggregateRepository transactionAggregateRepository = - this.AggregateRepositoryManager.GetAggregateRepository(estateId); - - TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.StartTransaction(transactionId, + transactionDateTime, + transactionNumber, + transactionType, + estateId, + merchantId, + deviceIdentifier, + cancellationToken); (String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateSaleTransaction(estateId, merchantId, deviceIdentifier, operatorIdentifier, cancellationToken); if (validationResult.responseCode == TransactionResponseCode.Success) { - // TODO: Do the online processing with the operator here + // Record any additional request metadata + await this.TransactionAggregateManager.RecordAdditionalRequestData(estateId, transactionId, additionalTransactionMetadata, cancellationToken); + + // Do the online processing with the operator here MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken); IOperatorProxy operatorProxy = OperatorProxyResolver(operatorIdentifier); - await operatorProxy.ProcessSaleMessage(transactionId, merchant, transactionDateTime, additionalTransactionMetadata, cancellationToken); + OperatorResponse operatorResponse = await operatorProxy.ProcessSaleMessage(transactionId, merchant, transactionDateTime, additionalTransactionMetadata, cancellationToken); + + if (operatorResponse.IsSuccessful) + { + TransactionResponseCode transactionResponseCode = TransactionResponseCode.Success; + String responseMessage = "SUCCESS"; + + await this.TransactionAggregateManager.AuthoriseTransaction(estateId, + transactionId, + operatorResponse, + transactionResponseCode, + responseMessage, + cancellationToken); + } + else + { + TransactionResponseCode transactionResponseCode = TransactionResponseCode.TransactionDeclinedByOperator; + String responseMessage = "DECLINED BY OPERATOR"; + + await this.TransactionAggregateManager.DeclineTransaction(estateId, + transactionId, + operatorResponse, + transactionResponseCode, + responseMessage, + cancellationToken); + } + + // Record any additional operator response metadata + await this.TransactionAggregateManager.RecordAdditionalResponseData(estateId, transactionId, operatorResponse.AdditionalTransactionResponseMetadata, cancellationToken); - // Record the successful validation - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - // TODO: Generate local authcode - transactionAggregate.AuthoriseTransactionLocally("ABCD1234", ((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), validationResult.responseMessage); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); } else { // Record the failure - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), validationResult.responseMessage); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.DeclineTransactionLocally(estateId, transactionId, validationResult, cancellationToken); } - transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken); - transactionAggregate.CompleteTransaction(); - await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken); + await this.TransactionAggregateManager.CompleteTransaction(estateId, transactionId, cancellationToken); + + TransactionAggregate transactionAggregate = await this.TransactionAggregateManager.GetAggregate(estateId, transactionId, cancellationToken); return new ProcessSaleTransactionResponse { diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs b/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs index 1c6def96..5bdacefa 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs @@ -12,6 +12,7 @@ public enum TransactionResponseCode OperatorNotValidForEstate = 1005, NoMerchantOperators = 1006, OperatorNotValidForMerchant = 1007, + TransactionDeclinedByOperator = 1008, // A Catch All generic Error where reason has not been identified UnknownFailure = 9999 diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index 30137692..12568f3c 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using BusinessLogic.OperatorInterfaces; using BusinessLogic.OperatorInterfaces.SafaricomPinless; using BusinessLogic.Requests; using BusinessLogic.Services; @@ -70,10 +71,59 @@ public class TestData private static String TerminalNumber = "00000001"; + public static String OperatorAuthorisationCode = "OP1234"; + + public static String OperatorResponseCode = "200"; + + public static String OperatorResponseMessage = "Topup Successful"; + + public static String OperatorTransactionId = "SF12345"; + + public static String DeclinedOperatorResponseCode = "400"; + + public static String DeclinedOperatorResponseMessage = "Topup Failed"; + + public static TransactionResponseCode TransactionResponseCodeSuccess = TransactionResponseCode.Success; + + public static TransactionResponseCode TransactionResponseCodeDeclinedByOperator = TransactionResponseCode.TransactionDeclinedByOperator; + + public static OperatorResponse OperatorResponse => + new OperatorResponse + { + ResponseMessage = TestData.OperatorResponseMessage, + AdditionalTransactionResponseMetadata = TestData.AdditionalTransactionMetaData, + TransactionId = TestData.OperatorTransactionId, + IsSuccessful = true, + AuthorisationCode = TestData.OperatorAuthorisationCode, + ResponseCode = TestData.OperatorResponseCode + }; + #endregion #region Properties + /// + /// Gets the null additional transaction meta data. + /// + /// + /// The null additional transaction meta data. + /// + public static Dictionary NullAdditionalTransactionMetaData => null; + + /// + /// Gets the empty additional transaction meta data. + /// + /// + /// The empty additional transaction meta data. + /// + public static Dictionary EmptyAdditionalTransactionMetaData => new Dictionary(); + + /// + /// Gets the additional transaction meta data. + /// + /// + /// The additional transaction meta data. + /// public static Dictionary AdditionalTransactionMetaData => new Dictionary { @@ -376,6 +426,25 @@ public static TransactionAggregate GetLocallyDeclinedTransactionAggregate(Transa return transactionAggregate; } + public static TransactionAggregate GetDeclinedTransactionAggregate(TransactionResponseCode transactionResponseCode) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + + transactionAggregate.StartTransaction(TestData.TransactionDateTime, + TestData.TransactionNumber, + TestData.TransactionTypeLogon, + TestData.EstateId, + TestData.MerchantId, + TestData.DeviceIdentifier); + + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, + TestData.DeclinedOperatorResponseMessage, + TestData.GetResponseCodeAsString(transactionResponseCode), + TestData.GetResponseCodeMessage(transactionResponseCode)); + + return transactionAggregate; + } + public static String GetResponseCodeAsString(TransactionResponseCode transactionResponseCode) { return ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'); diff --git a/TransactionProcessor.Transaction.DomainEvents/AdditionalRequestDataRecordedEvent.cs b/TransactionProcessor.Transaction.DomainEvents/AdditionalRequestDataRecordedEvent.cs new file mode 100644 index 00000000..2783a08f --- /dev/null +++ b/TransactionProcessor.Transaction.DomainEvents/AdditionalRequestDataRecordedEvent.cs @@ -0,0 +1,98 @@ +namespace TransactionProcessor.Transaction.DomainEvents +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + /// + /// + /// + /// + public class AdditionalRequestDataRecordedEvent : DomainEvent + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The merchant identifier. + /// The additional transaction request metadata. + public AdditionalRequestDataRecordedEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid merchantId, + Dictionary additionalTransactionRequestMetadata) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.MerchantId = merchantId; + this.AdditionalTransactionRequestMetadata = additionalTransactionRequestMetadata; + } + + #endregion + + #region Properties + + /// + /// Gets the additional transaction request metadata. + /// + /// + /// The additional transaction request metadata. + /// + [JsonProperty] + public Dictionary AdditionalTransactionRequestMetadata { get; private set; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty] + public Guid MerchantId { get; private set; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + [JsonProperty] + public Guid TransactionId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The merchant identifier. + /// The additional transaction request metadata. + /// + public static AdditionalRequestDataRecordedEvent Create(Guid aggregateId, + Guid estateId, + Guid merchantId, + Dictionary additionalTransactionRequestMetadata) + { + return new AdditionalRequestDataRecordedEvent(aggregateId, Guid.NewGuid(), estateId, merchantId, additionalTransactionRequestMetadata); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.Transaction.DomainEvents/AdditionalResponseDataRecordedEvent.cs b/TransactionProcessor.Transaction.DomainEvents/AdditionalResponseDataRecordedEvent.cs new file mode 100644 index 00000000..00233bc6 --- /dev/null +++ b/TransactionProcessor.Transaction.DomainEvents/AdditionalResponseDataRecordedEvent.cs @@ -0,0 +1,98 @@ +namespace TransactionProcessor.Transaction.DomainEvents +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + /// + /// + /// + /// + public class AdditionalResponseDataRecordedEvent : DomainEvent + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The merchant identifier. + /// The additional transaction response metadata. + public AdditionalResponseDataRecordedEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid merchantId, + Dictionary additionalTransactionResponseMetadata) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.MerchantId = merchantId; + this.AdditionalTransactionResponseMetadata = additionalTransactionResponseMetadata; + } + + #endregion + + #region Properties + + /// + /// Gets the additional transaction request metadata. + /// + /// + /// The additional transaction request metadata. + /// + [JsonProperty] + public Dictionary AdditionalTransactionResponseMetadata { get; private set; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty] + public Guid MerchantId { get; private set; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + [JsonProperty] + public Guid TransactionId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The merchant identifier. + /// The additional transaction response metadata. + /// + public static AdditionalResponseDataRecordedEvent Create(Guid aggregateId, + Guid estateId, + Guid merchantId, + Dictionary additionalTransactionResponseMetadata) + { + return new AdditionalResponseDataRecordedEvent(aggregateId, Guid.NewGuid(), estateId, merchantId, additionalTransactionResponseMetadata); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.Transaction.DomainEvents/TransactionAuthorisedByOperatorEvent.cs b/TransactionProcessor.Transaction.DomainEvents/TransactionAuthorisedByOperatorEvent.cs new file mode 100644 index 00000000..4c8ebc8f --- /dev/null +++ b/TransactionProcessor.Transaction.DomainEvents/TransactionAuthorisedByOperatorEvent.cs @@ -0,0 +1,179 @@ +namespace TransactionProcessor.Transaction.DomainEvents +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + [JsonObject] + public class TransactionAuthorisedByOperatorEvent : DomainEvent + { + #region Constructors + + [ExcludeFromCodeCoverage] + public TransactionAuthorisedByOperatorEvent() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The merchant identifier. + /// The authorisation code. + /// The operator response code. + /// The operator response message. + /// The operator transaction identifier. + /// The response code. + /// The response message. + public TransactionAuthorisedByOperatorEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid merchantId, + String authorisationCode, + String operatorResponseCode, + String operatorResponseMessage, + String operatorTransactionId, + String responseCode, + String responseMessage) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.MerchantId = merchantId; + this.AuthorisationCode = authorisationCode; + this.OperatorResponseCode = operatorResponseCode; + this.OperatorResponseMessage = operatorResponseMessage; + this.OperatorTransactionId = operatorTransactionId; + this.ResponseCode = responseCode; + this.ResponseMessage = responseMessage; + } + + #endregion + + #region Properties + + /// + /// Gets the authorisation code. + /// + /// + /// The authorisation code. + /// + [JsonProperty] + public String AuthorisationCode { get; private set; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty] + public Guid MerchantId { get; private set; } + + /// + /// Gets the operator response code. + /// + /// + /// The operator response code. + /// + [JsonProperty] + public String OperatorResponseCode { get; private set; } + + /// + /// Gets the operator response message. + /// + /// + /// The operator response message. + /// + [JsonProperty] + public String OperatorResponseMessage { get; private set; } + + /// + /// Gets or sets the operator transaction identifier. + /// + /// + /// The operator transaction identifier. + /// + [JsonProperty] + public String OperatorTransactionId { get; set; } + + /// + /// Gets the response code. + /// + /// + /// The response code. + /// + [JsonProperty] + public String ResponseCode { get; private set; } + + /// + /// Gets the response message. + /// + /// + /// The response message. + /// + [JsonProperty] + public String ResponseMessage { get; private set; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + [JsonProperty] + public Guid TransactionId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The merchant identifier. + /// The authorisation code. + /// The operator response code. + /// The operator response message. + /// The operator transaction identifier. + /// The response code. + /// The response message. + /// + public static TransactionAuthorisedByOperatorEvent Create(Guid aggregateId, + Guid estateId, + Guid merchantId, + String authorisationCode, + String operatorResponseCode, + String operatorResponseMessage, + String operatorTransactionId, + String responseCode, + String responseMessage) + { + return new TransactionAuthorisedByOperatorEvent(aggregateId, + Guid.NewGuid(), + estateId, + merchantId, + authorisationCode, + operatorResponseCode, + operatorResponseMessage, + operatorTransactionId, + responseCode, + responseMessage); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.Transaction.DomainEvents/TransactionDeclinedByOperatorEvent.cs b/TransactionProcessor.Transaction.DomainEvents/TransactionDeclinedByOperatorEvent.cs new file mode 100644 index 00000000..02add901 --- /dev/null +++ b/TransactionProcessor.Transaction.DomainEvents/TransactionDeclinedByOperatorEvent.cs @@ -0,0 +1,147 @@ +namespace TransactionProcessor.Transaction.DomainEvents +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + [JsonObject] + public class TransactionDeclinedByOperatorEvent : DomainEvent + { + #region Constructors + + [ExcludeFromCodeCoverage] + public TransactionDeclinedByOperatorEvent() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The merchant identifier. + /// The operator response code. + /// The operator response message. + public TransactionDeclinedByOperatorEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid merchantId, + String operatorResponseCode, + String operatorResponseMessage, + String responseCode, + String responseMessage) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.MerchantId = merchantId; + this.OperatorResponseCode = operatorResponseCode; + this.OperatorResponseMessage = operatorResponseMessage; + this.ResponseCode = responseCode; + this.ResponseMessage = responseMessage; + } + + #endregion + + #region Properties + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty] + public Guid MerchantId { get; private set; } + + /// + /// Gets the operator response code. + /// + /// + /// The operator response code. + /// + [JsonProperty] + public String OperatorResponseCode { get; private set; } + + /// + /// Gets the operator response message. + /// + /// + /// The operator response message. + /// + [JsonProperty] + public String OperatorResponseMessage { get; private set; } + + /// + /// Gets the response code. + /// + /// + /// The response code. + /// + [JsonProperty] + public String ResponseCode { get; private set; } + + /// + /// Gets the response message. + /// + /// + /// The response message. + /// + [JsonProperty] + public String ResponseMessage { get; private set; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + [JsonProperty] + public Guid TransactionId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The merchant identifier. + /// The operator response code. + /// The operator response message. + /// The response code. + /// The response message. + /// + public static TransactionDeclinedByOperatorEvent Create(Guid aggregateId, + Guid estateId, + Guid merchantId, + String operatorResponseCode, + String operatorResponseMessage, + String responseCode, + String responseMessage) + { + return new TransactionDeclinedByOperatorEvent(aggregateId, + Guid.NewGuid(), + estateId, + merchantId, + operatorResponseCode, + operatorResponseMessage, + responseCode, + responseMessage); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs index 1e68d9b7..21b2a242 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs +++ b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs @@ -101,8 +101,104 @@ public void TransactionHasBeenLocallyDeclinedEvent_CanBeCreated_IsCreated() transactionHasBeenLocallyDeclinedEvent.MerchantId.ShouldBe(TestData.MerchantId); transactionHasBeenLocallyDeclinedEvent.ResponseCode.ShouldBe(TestData.DeclinedResponseCode); transactionHasBeenLocallyDeclinedEvent.ResponseMessage.ShouldBe(TestData.DeclinedResponseMessage); + } + + [Fact] + public void TransactionAuthorisedByOperatorEvent_CanBeCreated_IsCreated() + { + TransactionAuthorisedByOperatorEvent transactionAuthorisedByOperatorEvent = TransactionAuthorisedByOperatorEvent.Create(TestData.TransactionId, + TestData.EstateId, + TestData.MerchantId, + TestData.OperatorAuthorisationCode, + TestData.OperatorResponseCode, + TestData.OperatorResponseMessage, + TestData.OperatorTransactionId, + TestData.ResponseCode, + TestData.ResponseMessage); + + transactionAuthorisedByOperatorEvent.ShouldNotBeNull(); + transactionAuthorisedByOperatorEvent.AggregateId.ShouldBe(TestData.TransactionId); + transactionAuthorisedByOperatorEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + transactionAuthorisedByOperatorEvent.EventId.ShouldNotBe(Guid.Empty); + transactionAuthorisedByOperatorEvent.TransactionId.ShouldBe(TestData.TransactionId); + transactionAuthorisedByOperatorEvent.EstateId.ShouldBe(TestData.EstateId); + transactionAuthorisedByOperatorEvent.MerchantId.ShouldBe(TestData.MerchantId); + transactionAuthorisedByOperatorEvent.AuthorisationCode.ShouldBe(TestData.OperatorAuthorisationCode); + transactionAuthorisedByOperatorEvent.OperatorResponseCode.ShouldBe(TestData.OperatorResponseCode); + transactionAuthorisedByOperatorEvent.OperatorResponseMessage.ShouldBe(TestData.OperatorResponseMessage); + transactionAuthorisedByOperatorEvent.OperatorTransactionId.ShouldBe(TestData.OperatorTransactionId); + transactionAuthorisedByOperatorEvent.ResponseCode.ShouldBe(TestData.ResponseCode); + transactionAuthorisedByOperatorEvent.ResponseMessage.ShouldBe(TestData.ResponseMessage); + } + + [Fact] + public void TransactionDeclinedByOperatorEvent_CanBeCreated_IsCreated() + { + TransactionDeclinedByOperatorEvent transactionDeclinedByOperatorEvent = TransactionDeclinedByOperatorEvent.Create(TestData.TransactionId, + TestData.EstateId, + TestData.MerchantId, + TestData.DeclinedOperatorResponseCode, + TestData.DeclinedOperatorResponseMessage, + TestData.DeclinedResponseCode, + TestData.DeclinedResponseMessage); + + transactionDeclinedByOperatorEvent.ShouldNotBeNull(); + transactionDeclinedByOperatorEvent.AggregateId.ShouldBe(TestData.TransactionId); + transactionDeclinedByOperatorEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + transactionDeclinedByOperatorEvent.EventId.ShouldNotBe(Guid.Empty); + transactionDeclinedByOperatorEvent.TransactionId.ShouldBe(TestData.TransactionId); + transactionDeclinedByOperatorEvent.EstateId.ShouldBe(TestData.EstateId); + transactionDeclinedByOperatorEvent.MerchantId.ShouldBe(TestData.MerchantId); + transactionDeclinedByOperatorEvent.OperatorResponseCode.ShouldBe(TestData.DeclinedOperatorResponseCode); + transactionDeclinedByOperatorEvent.OperatorResponseMessage.ShouldBe(TestData.DeclinedOperatorResponseMessage); + transactionDeclinedByOperatorEvent.ResponseCode.ShouldBe(TestData.DeclinedResponseCode); + transactionDeclinedByOperatorEvent.ResponseMessage.ShouldBe(TestData.DeclinedResponseMessage); + } + + [Fact] + public void AdditionalResponseDataRecordedEvent_CanBeCreated_IsCreated() + { + AdditionalResponseDataRecordedEvent additionalResponseDataRecordedEvent = AdditionalResponseDataRecordedEvent.Create(TestData.TransactionId, + TestData.EstateId, + TestData.MerchantId, + TestData.AdditionalTransactionMetaData); + + additionalResponseDataRecordedEvent.ShouldNotBeNull(); + additionalResponseDataRecordedEvent.AggregateId.ShouldBe(TestData.TransactionId); + additionalResponseDataRecordedEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + additionalResponseDataRecordedEvent.EventId.ShouldNotBe(Guid.Empty); + additionalResponseDataRecordedEvent.TransactionId.ShouldBe(TestData.TransactionId); + additionalResponseDataRecordedEvent.EstateId.ShouldBe(TestData.EstateId); + additionalResponseDataRecordedEvent.MerchantId.ShouldBe(TestData.MerchantId); + additionalResponseDataRecordedEvent.AdditionalTransactionResponseMetadata.ShouldNotBeNull(); + + foreach (KeyValuePair keyValuePair in TestData.AdditionalTransactionMetaData) + { + additionalResponseDataRecordedEvent.AdditionalTransactionResponseMetadata.ShouldContainKeyAndValue(keyValuePair.Key, keyValuePair.Value); + } + } + + [Fact] + public void AdditionalRequestDataRecordedEvent_CanBeCreated_IsCreated() + { + AdditionalRequestDataRecordedEvent additionalRequestDataRecordedEvent = AdditionalRequestDataRecordedEvent.Create(TestData.TransactionId, + TestData.EstateId, + TestData.MerchantId, + TestData.AdditionalTransactionMetaData); + additionalRequestDataRecordedEvent.ShouldNotBeNull(); + additionalRequestDataRecordedEvent.AggregateId.ShouldBe(TestData.TransactionId); + additionalRequestDataRecordedEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + additionalRequestDataRecordedEvent.EventId.ShouldNotBe(Guid.Empty); + additionalRequestDataRecordedEvent.TransactionId.ShouldBe(TestData.TransactionId); + additionalRequestDataRecordedEvent.EstateId.ShouldBe(TestData.EstateId); + additionalRequestDataRecordedEvent.MerchantId.ShouldBe(TestData.MerchantId); + additionalRequestDataRecordedEvent.AdditionalTransactionRequestMetadata.ShouldNotBeNull(); + foreach (KeyValuePair keyValuePair in TestData.AdditionalTransactionMetaData) + { + additionalRequestDataRecordedEvent.AdditionalTransactionRequestMetadata.ShouldContainKeyAndValue(keyValuePair.Key, keyValuePair.Value); + } } } } diff --git a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs index c38a46d1..646a4c90 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs +++ b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs @@ -60,7 +60,21 @@ public void TransactionAggregate_StartTransaction_TransactionAlreadyCompleted_Er { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); - transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, + TestData.OperatorResponseCode, + TestData.OperatorResponseMessage, + TestData.OperatorTransactionId, + TestData.ResponseCode, + TestData.ResponseMessage); + } + transactionAggregate.CompleteTransaction(); Should.Throw(() => @@ -144,7 +158,7 @@ public void TransactionAggregate_AuthoriseTransactionLocally_TransactionNotStart [Theory] [InlineData(TransactionType.Logon)] - public void TransactionAggregate_AuthoriseTransactionLocally_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + public void TransactionAggregate_AuthoriseTransactionLocally_TransactionAlreadyAuthorisedLocally_ErrorThrown(TransactionType transactionType) { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); @@ -158,6 +172,82 @@ public void TransactionAggregate_AuthoriseTransactionLocally_TransactionAlreadyA }); } + [Theory] + [InlineData(TransactionType.Logon)] + public void TransactionAggregate_AuthoriseTransactionLocally_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + + Should.Throw(() => + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, + TestData.ResponseCode, + TestData.ResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AuthoriseTransactionLocally_TransactionCannotBeLocallyyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + + Should.Throw(() => + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, + TestData.ResponseCode, + TestData.ResponseMessage); + }); + } + + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AuthoriseTransaction_TransactionIsAuthorised(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + + transactionAggregate.IsLocallyAuthorised.ShouldBeFalse(); + transactionAggregate.IsAuthorised.ShouldBeTrue(); + transactionAggregate.AuthorisationCode.ShouldBe(TestData.OperatorAuthorisationCode); + transactionAggregate.OperatorResponseCode.ShouldBe(TestData.OperatorResponseCode); + transactionAggregate.OperatorResponseMessage.ShouldBe(TestData.OperatorResponseMessage); + transactionAggregate.OperatorTransactionId.ShouldBe(TestData.OperatorTransactionId); + } + + [Fact] + public void TransactionAggregate_AuthoriseTransaction_TransactionNotStarted_ErrorThrown() + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + + Should.Throw(() => + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AuthoriseTransaction_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + + Should.Throw(() => + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + }); + } + [Theory] [InlineData(TransactionType.Logon)] [InlineData(TransactionType.Sale)] @@ -190,8 +280,7 @@ public void TransactionAggregate_DeclineTransactionLocally_TransactionNotStarted [Theory] [InlineData(TransactionType.Logon)] - [InlineData(TransactionType.Sale)] - public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyAuthorisedLocally_ErrorThrown(TransactionType transactionType) { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); @@ -206,7 +295,22 @@ public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyAut [Theory] [InlineData(TransactionType.Logon)] [InlineData(TransactionType.Sale)] - public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyDeclined_ErrorThrown(TransactionType transactionType) + public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + + Should.Throw(() => + { + transactionAggregate.DeclineTransactionLocally(TestData.ResponseCode, TestData.ResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyDeclinedLocally_ErrorThrown(TransactionType transactionType) { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); @@ -218,6 +322,111 @@ public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyDec }); } + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransactionLocally_TransactionAlreadyDeclined_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.DeclineTransaction(TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + + Should.Throw(() => + { + transactionAggregate.DeclineTransactionLocally(TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransaction_TransactionIsDeclined(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + + transactionAggregate.IsAuthorised.ShouldBeFalse(); + transactionAggregate.IsLocallyAuthorised.ShouldBeFalse(); + transactionAggregate.IsDeclined.ShouldBeTrue(); + transactionAggregate.IsLocallyDeclined.ShouldBeFalse(); + + transactionAggregate.OperatorResponseCode.ShouldBe(TestData.DeclinedOperatorResponseCode); + transactionAggregate.OperatorResponseMessage.ShouldBe(TestData.DeclinedOperatorResponseMessage); + } + + [Fact] + public void TransactionAggregate_DeclineTransaction_TransactionNotStarted_ErrorThrown() + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + + Should.Throw(() => + { + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + }); + } + + //[Theory] + //[InlineData(TransactionType.Logon)] + //[InlineData(TransactionType.Sale)] + //public void TransactionAggregate_DeclineTransaction_TransactionAlreadyAuthorisedLocally_ErrorThrown(TransactionType transactionType) + //{ + // TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + // transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + // transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + + // Should.Throw(() => + // { + // transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage); + // }); + //} + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransaction_TransactionAlreadyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + + Should.Throw(() => + { + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransaction_TransactionAlreadyDeclinedLocally_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.DeclineTransactionLocally(TestData.ResponseCode, TestData.ResponseMessage); + + Should.Throw(() => + { + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_DeclineTransaction_TransactionAlreadyDeclined_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + + Should.Throw(() => + { + transactionAggregate.DeclineTransaction(TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, TestData.DeclinedResponseCode, TestData.DeclinedResponseMessage); + }); + } + [Theory] [InlineData(TransactionType.Logon)] [InlineData(TransactionType.Sale)] @@ -225,7 +434,16 @@ public void TransactionAggregate_CompleteTransaction_TransactionIsCompleted(Tran { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); - transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + transactionAggregate.CompleteTransaction(); transactionAggregate.IsStarted.ShouldBeFalse(); @@ -264,7 +482,16 @@ public void TransactionAggregate_CompleteTransaction_TransactionAlreadyCompleted { TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); - transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + transactionAggregate.CompleteTransaction(); Should.Throw(() => @@ -273,5 +500,208 @@ public void TransactionAggregate_CompleteTransaction_TransactionAlreadyCompleted }); } + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_RequestDataRecorded(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + + Should.NotThrow(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_TransactionNotStarted_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_AdditionalRequestDataAlreadyRecorded_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_AlreadyAuthorised_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_AlreadyDeclined_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + + if (transactionType == TransactionType.Logon) + { + transactionAggregate.DeclineTransactionLocally(TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.DeclineTransaction(TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.ResponseCode, TestData.ResponseMessage); + } + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalRequestData_AlreadyCompleted_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + transactionAggregate.CompleteTransaction(); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + }); + } + + //####### + + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalResponseData_ResponseDataRecorded(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + + Should.NotThrow(() => + { + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + }); + + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalResponseData_TransactionNotStarted_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalResponseData_AdditionalResponseDataAlreadyRecorded_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + }); + } + + [Theory] + [InlineData(TransactionType.Logon)] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_RecordAdditionalResponseData_AlreadyCompleted_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier); + transactionAggregate.RecordAdditionalRequestData(TestData.AdditionalTransactionMetaData); + if (transactionType == TransactionType.Logon) + { + transactionAggregate.AuthoriseTransactionLocally(TestData.AuthorisationCode, TestData.ResponseCode, TestData.ResponseMessage); + } + else + { + transactionAggregate.AuthoriseTransaction(TestData.OperatorAuthorisationCode, TestData.OperatorResponseCode, TestData.OperatorResponseMessage, TestData.OperatorTransactionId, TestData.ResponseCode, TestData.ResponseMessage); + } + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + transactionAggregate.CompleteTransaction(); + + Should.Throw(() => + { + transactionAggregate.RecordAdditionalResponseData(TestData.AdditionalTransactionMetaData); + }); + } + } } diff --git a/TransactionProcessor.TransactionAggregate.Tests/TransactionProcessor.TransactionAggregate.Tests.csproj b/TransactionProcessor.TransactionAggregate.Tests/TransactionProcessor.TransactionAggregate.Tests.csproj index 6e63abc0..5ce42fef 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/TransactionProcessor.TransactionAggregate.Tests.csproj +++ b/TransactionProcessor.TransactionAggregate.Tests/TransactionProcessor.TransactionAggregate.Tests.csproj @@ -2,7 +2,7 @@ netcoreapp3.1 - None + None false diff --git a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs index 415728ba..fad2e65e 100644 --- a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs +++ b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs @@ -1,6 +1,7 @@ namespace TransactionProcessor.TransactionAggregate { using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Models; @@ -15,6 +16,20 @@ /// public class TransactionAggregate : Aggregate { + #region Fields + + /// + /// The additional transaction request metadata + /// + private Dictionary AdditionalTransactionRequestMetadata; + + /// + /// The additional transaction response metadata + /// + private Dictionary AdditionalTransactionResponseMetadata; + + #endregion + #region Constructors /// @@ -74,20 +89,20 @@ private TransactionAggregate(Guid aggregateId) public Boolean IsAuthorised { get; private set; } /// - /// Gets a value indicating whether this instance is declined. + /// Gets a value indicating whether this instance is completed. /// /// - /// true if this instance is declined; otherwise, false. + /// true if this instance is completed; otherwise, false. /// - public Boolean IsDeclined { get; private set; } + public Boolean IsCompleted { get; private set; } /// - /// Gets a value indicating whether this instance is completed. + /// Gets a value indicating whether this instance is declined. /// /// - /// true if this instance is completed; otherwise, false. + /// true if this instance is declined; otherwise, false. /// - public Boolean IsCompleted { get; private set; } + public Boolean IsDeclined { get; private set; } /// /// Gets a value indicating whether this instance is locally authorised. @@ -121,6 +136,30 @@ private TransactionAggregate(Guid aggregateId) /// public Guid MerchantId { get; private set; } + /// + /// Gets the operator response code. + /// + /// + /// The operator response code. + /// + public String OperatorResponseCode { get; private set; } + + /// + /// Gets the operator response message. + /// + /// + /// The operator response message. + /// + public String OperatorResponseMessage { get; private set; } + + /// + /// Gets the operator transaction identifier. + /// + /// + /// The operator transaction identifier. + /// + public String OperatorTransactionId { get; private set; } + /// /// Gets the response code. /// @@ -165,6 +204,37 @@ private TransactionAggregate(Guid aggregateId) #region Methods + /// + /// Authorises the transaction. + /// + /// The authorisation code. + /// The operator response code. + /// The operator response message. + /// The operator transaction identifier. + /// The response code. + /// The response message. + public void AuthoriseTransaction(String authorisationCode, + String operatorResponseCode, + String operatorResponseMessage, + String operatorTransactionId, + String responseCode, + String responseMessage) + { + this.CheckTransactionHasBeenStarted(); + this.CheckTransactionNotAlreadyAuthorised(); + + TransactionAuthorisedByOperatorEvent transactionAuthorisedByOperatorEvent = TransactionAuthorisedByOperatorEvent.Create(this.AggregateId, + this.EstateId, + this.MerchantId, + authorisationCode, + operatorResponseCode, + operatorResponseMessage, + operatorTransactionId, + responseCode, + responseMessage); + this.ApplyAndPend(transactionAuthorisedByOperatorEvent); + } + /// /// Authorises the transaction. /// @@ -177,12 +247,13 @@ public void AuthoriseTransactionLocally(String authorisationCode, { this.CheckTransactionHasBeenStarted(); this.CheckTransactionNotAlreadyAuthorised(); + this.CheckTransactionCanBeLocallyAuthorised(); TransactionHasBeenLocallyAuthorisedEvent transactionHasBeenLocallyAuthorisedEvent = TransactionHasBeenLocallyAuthorisedEvent.Create(this.AggregateId, this.EstateId, this.MerchantId, authorisationCode, responseCode, responseMessage); this.ApplyAndPend(transactionHasBeenLocallyAuthorisedEvent); } - + /// /// Completes the transaction. /// @@ -193,7 +264,12 @@ public void CompleteTransaction() this.CheckTransactionNotAlreadyCompleted(); TransactionHasBeenCompletedEvent transactionHasBeenCompletedEvent = - TransactionHasBeenCompletedEvent.Create(this.AggregateId, this.EstateId, this.MerchantId, this.ResponseCode, this.ResponseMessage, true); + TransactionHasBeenCompletedEvent.Create(this.AggregateId, + this.EstateId, + this.MerchantId, + this.ResponseCode, + this.ResponseMessage, + this.IsAuthorised || this.IsLocallyAuthorised); this.ApplyAndPend(transactionHasBeenCompletedEvent); } @@ -211,6 +287,35 @@ public static TransactionAggregate Create(Guid aggregateId) /// /// Declines the transaction. /// + /// The operator response code. + /// The operator response message. + /// The response code. + /// The response message. + public void DeclineTransaction(String operatorResponseCode, + String operatorResponseMessage, + String responseCode, + String responseMessage) + { + this.CheckTransactionHasBeenStarted(); + this.CheckTransactionNotAlreadyAuthorised(); + this.CheckTransactionNotAlreadyDeclined(); + + TransactionDeclinedByOperatorEvent transactionDeclinedByOperatorEvent = + TransactionDeclinedByOperatorEvent.Create(this.AggregateId, + this.EstateId, + this.MerchantId, + operatorResponseCode, + operatorResponseMessage, + responseCode, + responseMessage); + this.ApplyAndPend(transactionDeclinedByOperatorEvent); + } + + /// + /// Declines the transaction. + /// + /// The response code. + /// The response message. public void DeclineTransactionLocally(String responseCode, String responseMessage) { @@ -224,16 +329,36 @@ public void DeclineTransactionLocally(String responseCode, } /// - /// Checks the transaction not already declined. + /// Records the additional request data. /// - /// Transaction [{this.AggregateId}] has already been{authtype}declined - private void CheckTransactionNotAlreadyDeclined() + /// The additional transaction request metadata. + public void RecordAdditionalRequestData(Dictionary additionalTransactionRequestMetadata) { - if (this.IsLocallyDeclined || this.IsDeclined) - { - String authtype = this.IsLocallyAuthorised ? " locally " : " "; - throw new InvalidOperationException($"Transaction [{this.AggregateId}] has already been{authtype}declined"); - } + this.CheckTransactionNotAlreadyCompleted(); + this.CheckTransactionHasBeenStarted(); + this.CheckTransactionNotAlreadyAuthorised(); + this.CheckTransactionNotAlreadyDeclined(); + this.CheckAdditionalRequestDataNotAlreadyRecorded(); + + AdditionalRequestDataRecordedEvent additionalRequestDataRecordedEvent = + AdditionalRequestDataRecordedEvent.Create(this.AggregateId, this.EstateId, this.MerchantId, additionalTransactionRequestMetadata); + + this.ApplyAndPend(additionalRequestDataRecordedEvent); + } + + /// + /// Records the additional response data. + /// + /// The additional transaction response metadata. + public void RecordAdditionalResponseData(Dictionary additionalTransactionResponseMetadata) + { + this.CheckTransactionHasBeenStarted(); + this.CheckAdditionalResponseDataNotAlreadyRecorded(); + + AdditionalResponseDataRecordedEvent additionalResponseDataRecordedEvent = + AdditionalResponseDataRecordedEvent.Create(this.AggregateId, this.EstateId, this.MerchantId, additionalTransactionResponseMetadata); + + this.ApplyAndPend(additionalResponseDataRecordedEvent); } /// @@ -245,13 +370,11 @@ private void CheckTransactionNotAlreadyDeclined() /// The estate identifier. /// The merchant identifier. /// The device identifier. - /// - /// Transaction Number must be numeric + /// Transaction Number must be numeric /// or /// Invalid Transaction Type [{transactionType}] /// or - /// Device Identifier must be alphanumeric - /// + /// Device Identifier must be alphanumeric public void StartTransaction(DateTime transactionDateTime, String transactionNumber, TransactionType transactionType, @@ -282,7 +405,13 @@ public void StartTransaction(DateTime transactionDateTime, this.CheckTransactionNotAlreadyStarted(); this.CheckTransactionNotAlreadyCompleted(); TransactionHasStartedEvent transactionHasStartedEvent = - TransactionHasStartedEvent.Create(this.AggregateId, estateId, merchantId, transactionDateTime, transactionNumber, transactionType.ToString(), deviceIdentifier); + TransactionHasStartedEvent.Create(this.AggregateId, + estateId, + merchantId, + transactionDateTime, + transactionNumber, + transactionType.ToString(), + deviceIdentifier); this.ApplyAndPend(transactionHasStartedEvent); } @@ -309,14 +438,50 @@ protected override void PlayEvent(DomainEvent domainEvent) this.PlayEvent((dynamic)domainEvent); } + /// + /// Checks the additional request data not already recorded. + /// + /// Additional Request Data already recorded + private void CheckAdditionalRequestDataNotAlreadyRecorded() + { + if (this.AdditionalTransactionRequestMetadata != null) + { + throw new InvalidOperationException("Additional Request Data already recorded"); + } + } + + /// + /// Checks the additional response data not already recorded. + /// + /// Additional Response Data already recorded + private void CheckAdditionalResponseDataNotAlreadyRecorded() + { + if (this.AdditionalTransactionResponseMetadata != null) + { + throw new InvalidOperationException("Additional Response Data already recorded"); + } + } + + /// + /// Checks the transaction can be locally authorised. + /// + /// Sales cannot be locally authorised + /// Sales cannot be locally authorised + private void CheckTransactionCanBeLocallyAuthorised() + { + if (this.TransactionType == TransactionType.Sale) + { + throw new InvalidOperationException("Sales cannot be locally authorised"); + } + } + /// /// Checks the transaction has been authorised. /// /// Transaction [{this.AggregateId}] has not been authorised private void CheckTransactionHasBeenAuthorisedOrDeclined() { - if (this.IsAuthorised == false && this.IsLocallyAuthorised == false && - this.IsDeclined == false && this.IsLocallyDeclined == false) + if (this.IsAuthorised == false && this.IsLocallyAuthorised == false && this.IsDeclined == false && this.IsLocallyDeclined == false) { throw new InvalidOperationException($"Transaction [{this.AggregateId}] has not been authorised or declined"); } @@ -359,6 +524,19 @@ private void CheckTransactionNotAlreadyCompleted() } } + /// + /// Checks the transaction not already declined. + /// + /// Transaction [{this.AggregateId}] has already been{authtype}declined + private void CheckTransactionNotAlreadyDeclined() + { + if (this.IsLocallyDeclined || this.IsDeclined) + { + String authtype = this.IsLocallyDeclined ? " locally " : " "; + throw new InvalidOperationException($"Transaction [{this.AggregateId}] has already been{authtype}declined"); + } + } + /// /// Checks the transaction not already started. /// @@ -371,6 +549,24 @@ private void CheckTransactionNotAlreadyStarted() } } + /// + /// Plays the event. + /// + /// The domain event. + private void PlayEvent(AdditionalRequestDataRecordedEvent domainEvent) + { + this.AdditionalTransactionRequestMetadata = domainEvent.AdditionalTransactionRequestMetadata; + } + + /// + /// Plays the event. + /// + /// The domain event. + private void PlayEvent(AdditionalResponseDataRecordedEvent domainEvent) + { + this.AdditionalTransactionResponseMetadata = domainEvent.AdditionalTransactionResponseMetadata; + } + /// /// Plays the event. /// @@ -423,6 +619,34 @@ private void PlayEvent(TransactionHasBeenCompletedEvent domainEvent) this.IsCompleted = true; } + /// + /// Plays the event. + /// + /// The domain event. + private void PlayEvent(TransactionAuthorisedByOperatorEvent domainEvent) + { + this.IsAuthorised = true; + this.OperatorResponseCode = domainEvent.OperatorResponseCode; + this.OperatorResponseMessage = domainEvent.OperatorResponseMessage; + this.OperatorTransactionId = domainEvent.OperatorTransactionId; + this.AuthorisationCode = domainEvent.AuthorisationCode; + this.ResponseCode = domainEvent.ResponseCode; + this.ResponseMessage = domainEvent.ResponseMessage; + } + + /// + /// Plays the event. + /// + /// The domain event. + private void PlayEvent(TransactionDeclinedByOperatorEvent domainEvent) + { + this.IsDeclined = true; + this.OperatorResponseCode = domainEvent.OperatorResponseCode; + this.OperatorResponseMessage = domainEvent.OperatorResponseMessage; + this.ResponseCode = domainEvent.ResponseCode; + this.ResponseMessage = domainEvent.ResponseMessage; + } + #endregion } } \ No newline at end of file diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs index 004dad10..eae2ff11 100644 --- a/TransactionProcessor/Startup.cs +++ b/TransactionProcessor/Startup.cs @@ -142,6 +142,7 @@ public void ConfigureServices(IServiceCollection services) } services.AddTransient(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton, AggregateRepository>(); services.AddSingleton();