From 38402b9cd06278f9a1f2edff079e3b5a13714820 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Oct 2025 13:28:24 +0100 Subject: [PATCH 1/3] complexity refactoring on Process Sale Transaction --- .../ReconciliationAggregateTests.cs | 4 +- .../TransactionAggregateTests.cs | 4 +- .../ReconciliationAggregate.cs | 8 +- .../TransactionAggregate.cs | 57 +++++- .../Services/TransactionDomainServiceTests.cs | 21 ++- .../Services/TransactionDomainService.cs | 173 +++++++++++++++++- .../Services/TransactionResponseCode.cs | 2 +- .../TransactionValidationException.cs | 4 +- TransactionProcessor.Testing/TestData.cs | 16 +- 9 files changed, 244 insertions(+), 45 deletions(-) diff --git a/TransactionProcessor.Aggregates.Tests/ReconciliationAggregateTests.cs b/TransactionProcessor.Aggregates.Tests/ReconciliationAggregateTests.cs index 1a6ae48f..2638f044 100644 --- a/TransactionProcessor.Aggregates.Tests/ReconciliationAggregateTests.cs +++ b/TransactionProcessor.Aggregates.Tests/ReconciliationAggregateTests.cs @@ -121,7 +121,7 @@ public void ReconciliationAggregate_Authorise_ReconciliationIsAuthorised() Result result = reconciliationAggregate.Authorise(TestData.ResponseCode, TestData.ResponseMessage); result.IsSuccess.ShouldBeTrue(); - reconciliationAggregate.ResponseCode.ShouldBe(TestData.ResponseCode); + reconciliationAggregate.ResponseCode.ShouldBe(TestData.ResponseCode.ToCodeString()); reconciliationAggregate.ResponseMessage.ShouldBe(TestData.ResponseMessage); reconciliationAggregate.IsAuthorised.ShouldBeTrue(); } @@ -186,7 +186,7 @@ public void ReconciliationAggregate_Decline_ReconciliationIsDeclined() Result result = reconciliationAggregate.Decline(TestData.ResponseCode, TestData.ResponseMessage); result.IsSuccess.ShouldBeTrue(); - reconciliationAggregate.ResponseCode.ShouldBe(TestData.ResponseCode); + reconciliationAggregate.ResponseCode.ShouldBe(TestData.ResponseCode.ToCodeString()); reconciliationAggregate.ResponseMessage.ShouldBe(TestData.ResponseMessage); reconciliationAggregate.IsAuthorised.ShouldBeFalse(); } diff --git a/TransactionProcessor.Aggregates.Tests/TransactionAggregateTests.cs b/TransactionProcessor.Aggregates.Tests/TransactionAggregateTests.cs index 197b9f1b..49aad81d 100644 --- a/TransactionProcessor.Aggregates.Tests/TransactionAggregateTests.cs +++ b/TransactionProcessor.Aggregates.Tests/TransactionAggregateTests.cs @@ -378,7 +378,7 @@ public void TransactionAggregate_AuthoriseTransactionLocally_TransactionIsAuthor transactionAggregate.IsLocallyAuthorised.ShouldBeTrue(); transactionAggregate.IsAuthorised.ShouldBeFalse(); transactionAggregate.AuthorisationCode.ShouldBe(TestData.AuthorisationCode); - transactionAggregate.ResponseCode.ShouldBe(TestData.ResponseCode); + transactionAggregate.ResponseCode.ShouldBe(TestData.ResponseCode.ToCodeString()); transactionAggregate.ResponseMessage.ShouldBe(TestData.ResponseMessage); } @@ -543,7 +543,7 @@ public void TransactionAggregate_DeclineTransactionLocally_TransactionIsDeclined transactionAggregate.IsDeclined.ShouldBeFalse(); transactionAggregate.IsLocallyDeclined.ShouldBeTrue(); - transactionAggregate.ResponseCode.ShouldBe(TestData.DeclinedResponseCode); + transactionAggregate.ResponseCode.ShouldBe(TestData.DeclinedResponseCode.ToCodeString()); transactionAggregate.ResponseMessage.ShouldBe(TestData.DeclinedResponseMessage); } diff --git a/TransactionProcessor.Aggregates/ReconciliationAggregate.cs b/TransactionProcessor.Aggregates/ReconciliationAggregate.cs index 8cc24858..3532bdf6 100644 --- a/TransactionProcessor.Aggregates/ReconciliationAggregate.cs +++ b/TransactionProcessor.Aggregates/ReconciliationAggregate.cs @@ -93,7 +93,7 @@ public static Result RecordOverallTotals(this ReconciliationAggregate aggregate, return Result.Success(); } - public static Result Authorise(this ReconciliationAggregate aggregate, String responseCode, String responseMessage) + public static Result Authorise(this ReconciliationAggregate aggregate, TransactionResponseCode responseCode, String responseMessage) { if (aggregate.IsAuthorised || aggregate.IsDeclined) { return Result.Success(); @@ -107,7 +107,7 @@ public static Result Authorise(this ReconciliationAggregate aggregate, String re return result; ReconciliationDomainEvents.ReconciliationHasBeenLocallyAuthorisedEvent reconciliationHasBeenLocallyAuthorisedEvent = new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, - responseCode, responseMessage, aggregate.TransactionDateTime); + responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(reconciliationHasBeenLocallyAuthorisedEvent); return Result.Success(); @@ -122,7 +122,7 @@ private static Result CheckReconciliationHasBeenAuthorisedOrDeclined(this Reconc return Result.Success(); } - public static Result Decline(this ReconciliationAggregate aggregate,String responseCode, String responseMessage) + public static Result Decline(this ReconciliationAggregate aggregate,TransactionResponseCode responseCode, String responseMessage) { if (aggregate.IsAuthorised || aggregate.IsDeclined) { return Result.Success(); @@ -136,7 +136,7 @@ public static Result Decline(this ReconciliationAggregate aggregate,String respo return result; ReconciliationDomainEvents.ReconciliationHasBeenLocallyDeclinedEvent reconciliationHasBeenLocallyDeclinedEvent = new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, - responseCode, responseMessage, aggregate.TransactionDateTime); + responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(reconciliationHasBeenLocallyDeclinedEvent); diff --git a/TransactionProcessor.Aggregates/TransactionAggregate.cs b/TransactionProcessor.Aggregates/TransactionAggregate.cs index f211edd7..a32e19f4 100644 --- a/TransactionProcessor.Aggregates/TransactionAggregate.cs +++ b/TransactionProcessor.Aggregates/TransactionAggregate.cs @@ -13,13 +13,50 @@ namespace TransactionProcessor.Aggregates using Shared.EventStore.Aggregate; using Shared.General; + public enum TransactionResponseCode + { + Success = 0, + SuccessNeedToAddDevice = 1, + + InvalidDeviceIdentifier = 1000, + InvalidEstateId = 1001, + InvalidMerchantId = 1002, + NoValidDevices = 1003, + NoEstateOperators = 1004, + OperatorNotValidForEstate = 1005, + NoMerchantOperators = 1006, + OperatorNotValidForMerchant = 1007, + TransactionDeclinedByOperator = 1008, + MerchantDoesNotHaveEnoughCredit = 1009, + OperatorCommsError = 1010, + InvalidSaleTransactionAmount = 1011, + InvalidContractIdValue = 1012, + InvalidProductIdValue = 1013, + MerchantHasNoContractsConfigured = 1014, + ContractNotValidForMerchant = 1015, + ProductNotValidForMerchant = 1016, + OperatorNotEnabledForEstate = 1017, + OperatorNotEnabledForMerchant = 1018, + + // A Catch All generic Error where reason has not been identified + UnknownFailure = 9999 + } + + public static class TransactionResponseCodeExtensions + { + public static string ToCodeString(this TransactionResponseCode code) + { + return ((int)code).ToString("D4"); + } + } + public static class TransactionAggregateExtensions{ public static Result DeclineTransaction(this TransactionAggregate aggregate, Guid operatorId, String operatorResponseCode, String operatorResponseMessage, - String responseCode, + TransactionResponseCode responseCode, String responseMessage) { Result result = aggregate.CheckTransactionHasBeenStarted(); @@ -32,6 +69,8 @@ public static Result DeclineTransaction(this TransactionAggregate aggregate, if (result.IsFailed) return result; + + TransactionDomainEvents.TransactionDeclinedByOperatorEvent transactionDeclinedByOperatorEvent = new(aggregate.AggregateId, aggregate.EstateId, @@ -39,7 +78,7 @@ public static Result DeclineTransaction(this TransactionAggregate aggregate, operatorId, operatorResponseCode, operatorResponseMessage, - responseCode, + responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(transactionDeclinedByOperatorEvent); @@ -47,8 +86,8 @@ public static Result DeclineTransaction(this TransactionAggregate aggregate, return Result.Success(); } - public static Result DeclineTransactionLocally(this TransactionAggregate aggregate, - String responseCode, + public static Result DeclineTransactionLocally(this TransactionAggregate aggregate, + TransactionResponseCode responseCode, String responseMessage) { Result result = aggregate.CheckTransactionHasBeenStarted(); @@ -61,7 +100,7 @@ public static Result DeclineTransactionLocally(this TransactionAggregate aggrega if (result.IsFailed) return result; TransactionDomainEvents.TransactionHasBeenLocallyDeclinedEvent transactionHasBeenLocallyDeclinedEvent = - new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, responseCode, responseMessage, + new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(transactionHasBeenLocallyDeclinedEvent); @@ -276,7 +315,7 @@ public static Result AuthoriseTransaction(this TransactionAggregate aggregate, String operatorResponseCode, String operatorResponseMessage, String operatorTransactionId, - String responseCode, + TransactionResponseCode responseCode, String responseMessage) { Result result = aggregate.CheckTransactionHasBeenStarted(); @@ -294,7 +333,7 @@ public static Result AuthoriseTransaction(this TransactionAggregate aggregate, operatorResponseCode, operatorResponseMessage, operatorTransactionId, - responseCode, + responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(transactionAuthorisedByOperatorEvent); @@ -304,7 +343,7 @@ public static Result AuthoriseTransaction(this TransactionAggregate aggregate, public static Result AuthoriseTransactionLocally(this TransactionAggregate aggregate, String authorisationCode, - String responseCode, + TransactionResponseCode responseCode, String responseMessage) { var result = aggregate.CheckTransactionHasBeenStarted(); @@ -317,7 +356,7 @@ public static Result AuthoriseTransactionLocally(this TransactionAggregate aggre if (result.IsFailed) return result; TransactionDomainEvents.TransactionHasBeenLocallyAuthorisedEvent transactionHasBeenLocallyAuthorisedEvent = - new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, authorisationCode, responseCode, responseMessage, + new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, authorisationCode, responseCode.ToCodeString(), responseMessage, aggregate.TransactionDateTime); aggregate.ApplyAndAppend(transactionHasBeenLocallyAuthorisedEvent); diff --git a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs index 9bbaa313..41018442 100644 --- a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs @@ -254,7 +254,8 @@ public async Task TransactionDomainService_ProcessSaleTransaction_DeclinedByOper AuthorisationCode = TestData.OperatorAuthorisationCode, TransactionId = TestData.OperatorTransactionId, - ResponseCode = TestData.ResponseCode}, IsSuccess = false}); + ResponseCode = TestData.ResponseCode.ToCodeString() + }, IsSuccess = false}); TransactionCommands.ProcessSaleTransactionCommand command = new TransactionCommands.ProcessSaleTransactionCommand(TestData.TransactionId, TestData.EstateId, @@ -356,8 +357,8 @@ public async Task TransactionDomainService_ProcessSaleTransaction_TransactionIsP AuthorisationCode = TestData.OperatorAuthorisationCode, TransactionId = TestData.OperatorTransactionId, - ResponseCode = TestData.ResponseCode - }); + ResponseCode = TestData.ResponseCode.ToCodeString() + }); TransactionCommands.ProcessSaleTransactionCommand command = new TransactionCommands.ProcessSaleTransactionCommand(TestData.TransactionId, TestData.EstateId, @@ -420,7 +421,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_NoFloatFound_T AuthorisationCode = TestData.OperatorAuthorisationCode, TransactionId = TestData.OperatorTransactionId, - ResponseCode = TestData.ResponseCode + ResponseCode = TestData.ResponseCode.ToCodeString() }); TransactionCommands.ProcessSaleTransactionCommand command = @@ -503,7 +504,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsNotAuthorised TransactionType.Sale, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionAmount); - transactionAggregate.DeclineTransaction(TestData.OperatorId, "111", "SUCCESS", "0000", "SUCCESS"); + transactionAggregate.DeclineTransaction(TestData.OperatorId, "111", "SUCCESS", TransactionResponseCode.Success, "SUCCESS"); // TODO: maybe move this to an extension on aggregate var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate); @@ -518,7 +519,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsNotCompelted_ TransactionType.Sale, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionAmount); - transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", "0000", "SUCCESS"); + transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", TransactionResponseCode.Success, "SUCCESS"); var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate); result.ShouldBeFalse(); @@ -532,7 +533,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsALogon_Return TransactionType.Logon, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionAmount); - transactionAggregate.AuthoriseTransactionLocally("111", "0001", "SUCCESS"); + transactionAggregate.AuthoriseTransactionLocally("111", TransactionResponseCode.InvalidDeviceIdentifier, "SUCCESS"); transactionAggregate.CompleteTransaction(); @@ -548,7 +549,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_NoContractId_Re TransactionType.Sale, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionAmount); - transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", "0000", "SUCCESS"); + transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", TransactionResponseCode.Success, "SUCCESS"); transactionAggregate.CompleteTransaction(); @@ -565,7 +566,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_NullAmount_Retu TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, null); transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId); - transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", "0000", "SUCCESS"); + transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", TransactionResponseCode.Success, "SUCCESS"); transactionAggregate.CompleteTransaction(); @@ -582,7 +583,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_ReturnsTrue() TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionAmount); transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId); - transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", "0000", "SUCCESS"); + transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", TransactionResponseCode.Success, "SUCCESS"); transactionAggregate.CompleteTransaction(); diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index 3595245b..f2d5f0f6 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -114,13 +114,13 @@ public async Task> ProcessLogonTransacti } // Record the successful validation - stateResult = transactionAggregate.AuthoriseTransactionLocally(TransactionHelpers.GenerateAuthCode(), ((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage); + stateResult = transactionAggregate.AuthoriseTransactionLocally(TransactionHelpers.GenerateAuthCode(), validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } else { // Record the failure - stateResult = transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage); + stateResult = transactionAggregate.DeclineTransactionLocally(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } @@ -170,13 +170,13 @@ public async Task> ProcessRecon if (validationResult.IsSuccess && validationResult.Data.ResponseCode == TransactionResponseCode.Success) { // Record the successful validation - stateResult = reconciliationAggregate.Authorise(((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage); + stateResult = reconciliationAggregate.Authorise(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } else { // Record the failure - stateResult = reconciliationAggregate.Decline(((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage); + stateResult = reconciliationAggregate.Decline(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } @@ -204,7 +204,7 @@ public async Task> ProcessRecon } } - public async Task> ProcessSaleTransaction(TransactionCommands.ProcessSaleTransactionCommand command, + public async Task> ProcessSaleTransactionX(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { try { @@ -276,7 +276,7 @@ public async Task> ProcessSaleTransaction TransactionResponseCode transactionResponseCode = TransactionResponseCode.Success; String responseMessage = "SUCCESS"; - stateResult= transactionAggregate.AuthoriseTransaction(command.OperatorId, operatorResult.Data.AuthorisationCode, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, operatorResult.Data.TransactionId, ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'), responseMessage); + stateResult= transactionAggregate.AuthoriseTransaction(command.OperatorId, operatorResult.Data.AuthorisationCode, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, operatorResult.Data.TransactionId, transactionResponseCode, responseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } @@ -284,7 +284,7 @@ public async Task> ProcessSaleTransaction TransactionResponseCode transactionResponseCode = TransactionResponseCode.TransactionDeclinedByOperator; String responseMessage = "DECLINED BY OPERATOR"; - stateResult = transactionAggregate.DeclineTransaction(command.OperatorId, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'), responseMessage); + stateResult = transactionAggregate.DeclineTransaction(command.OperatorId, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, transactionResponseCode, responseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } @@ -296,7 +296,7 @@ public async Task> ProcessSaleTransaction } else { // Record the failure - stateResult = transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage); + stateResult = transactionAggregate.DeclineTransactionLocally(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); } @@ -666,5 +666,162 @@ private async Task ResendEmailMessage(String accessToken, List transactionFees = product.TransactionFees.ToList(); return Result.Success(transactionFees); } + + public async Task> ProcessSaleTransaction(TransactionCommands.ProcessSaleTransactionCommand command, + CancellationToken cancellationToken) { + Logger.LogInformation($"Starting ProcessSaleTransaction for TransactionId {command.TransactionId}"); + + try { + var transactionResult = await GetTransactionAggregateAsync(command, cancellationToken); + if (transactionResult.IsFailed) + return ResultHelpers.CreateFailure(transactionResult); + + var transactionAggregate = transactionResult.Data; + + var validationResult = await ValidateTransactionAsync(command, cancellationToken); + var (unitCost, totalCost) = await GetFloatCostAsync(command, validationResult, cancellationToken); + + var startResult = StartTransaction(transactionAggregate, command, unitCost, totalCost, validationResult); + if (startResult.IsFailed) + return ResultHelpers.CreateFailure(startResult); + + var operatorTiming = await HandleOperatorProcessingAsync(transactionAggregate, command, validationResult, cancellationToken); + + transactionAggregate.RecordTransactionTimings(command.TransactionReceivedDateTime, operatorTiming.Start, operatorTiming.End, DateTime.Now); + + transactionAggregate.CompleteTransaction(); + + if (!string.IsNullOrEmpty(command.CustomerEmailAddress)) + transactionAggregate.RequestEmailReceipt(command.CustomerEmailAddress); + + var saveResult = await AggregateService.Save(transactionAggregate, cancellationToken); + if (saveResult.IsFailed) + return ResultHelpers.CreateFailure(saveResult); + + var transaction = transactionAggregate.GetTransaction(); + Logger.LogInformation($"Transaction {command.TransactionId} completed successfully"); + + return Result.Success(new ProcessSaleTransactionResponse { + ResponseMessage = transaction.ResponseMessage, + ResponseCode = transaction.ResponseCode, + EstateId = command.EstateId, + MerchantId = command.MerchantId, + AdditionalTransactionMetadata = transaction.AdditionalResponseMetadata, + TransactionId = command.TransactionId + }); + } + catch (Exception ex) { + Logger.LogError($"Unhandled exception during ProcessSaleTransaction for {command.TransactionId}", ex); + return Result.Failure(ex.GetExceptionMessages()); + } + } + + private async Task> GetTransactionAggregateAsync(TransactionCommands.ProcessSaleTransactionCommand command, + CancellationToken cancellationToken) { + Logger.LogDebug($"Fetching TransactionAggregate for TransactionId {command.TransactionId}"); + + var result = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(command.TransactionId, ct), command.TransactionId, cancellationToken, false); + + if (result.IsFailed) + Logger.LogWarning($"TransactionAggregate not found for TransactionId {command.TransactionId}"); + + return result; + } + + + private async Task> ValidateTransactionAsync(TransactionCommands.ProcessSaleTransactionCommand command, + CancellationToken cancellationToken) { + var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); + Logger.LogInformation($"Validating Sale Transaction for Merchant {command.MerchantId}, Product {command.ProductId}"); + + var result = await TransactionValidationService.ValidateSaleTransaction(command.EstateId, command.MerchantId, command.ContractId, command.ProductId, command.DeviceIdentifier, command.OperatorId, amount, cancellationToken); + + Logger.LogInformation($"Validation completed with ResponseCode {result.Data?.ResponseCode}, Message {result.Data?.ResponseMessage}"); + + return result; + } + + + private async Task<(decimal UnitCost, decimal TotalCost)> GetFloatCostAsync(TransactionCommands.ProcessSaleTransactionCommand command, + Result validationResult, + CancellationToken cancellationToken) { + var floatAggregateId = IdGenerationService.GenerateFloatAggregateId(command.EstateId, command.ContractId, command.ProductId); + + Logger.LogDebug($"Fetching FloatAggregate {floatAggregateId}"); + + var floatAggregateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(floatAggregateId, ct), floatAggregateId, cancellationToken, false); + + if (floatAggregateResult.IsFailed) + return (0, 0); + + var floatAggregate = floatAggregateResult.Data; + var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount") ?? 0; + var unitCost = floatAggregate.GetUnitCostPrice(); + var totalCost = floatAggregate.GetTotalCostPrice(amount); + + Logger.LogInformation($"Float cost calculated: UnitCost={unitCost}, TotalCost={totalCost}"); + return (unitCost, totalCost); + } + + + private Result StartTransaction(TransactionAggregate transactionAggregate, + TransactionCommands.ProcessSaleTransactionCommand command, + decimal unitCost, + decimal totalCost, + Result validationResult) { + var transactionType = TransactionType.Sale; + var transactionSourceValue = (TransactionSource)command.TransactionSource; + var transactionReference = TransactionHelpers.GenerateTransactionReference(); + var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); + + var result = transactionAggregate.StartTransaction(command.TransactionDateTime, command.TransactionNumber, transactionType, transactionReference, command.EstateId, command.MerchantId, command.DeviceIdentifier, amount); + + if (result.IsFailed) + return result; + + if (validationResult.Data.ResponseCode is not (TransactionResponseCode.InvalidEstateId or TransactionResponseCode.InvalidContractIdValue or TransactionResponseCode.InvalidProductIdValue or TransactionResponseCode.ContractNotValidForMerchant or TransactionResponseCode.ProductNotValidForMerchant)) { + transactionAggregate.AddProductDetails(command.ContractId, command.ProductId); + } + + transactionAggregate.RecordCostPrice(unitCost, totalCost); + transactionAggregate.AddTransactionSource(transactionSourceValue); + return Result.Success(); + } + + private async Task<(DateTime? Start, DateTime? End)> HandleOperatorProcessingAsync(TransactionAggregate transactionAggregate, + TransactionCommands.ProcessSaleTransactionCommand command, + Result validationResult, + CancellationToken cancellationToken) { + DateTime? start = null, end = null; + + if (validationResult.Data.ResponseCode != TransactionResponseCode.Success) { + Logger.LogWarning($"Validation failed with ResponseCode {validationResult.Data.ResponseCode}: {validationResult.Data.ResponseMessage}"); + transactionAggregate.DeclineTransactionLocally(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); + return (start, end); + } + + Logger.LogInformation($"Validation success, processing with Operator {command.OperatorId}"); + + transactionAggregate.RecordAdditionalRequestData(command.OperatorId, command.AdditionalTransactionMetadata); + var merchantResult = await GetMerchant(command.MerchantId, cancellationToken); + if (merchantResult.IsFailed) + return (start, end); + + start = DateTime.Now; + var operatorResult = await ProcessMessageWithOperator(merchantResult.Data, command.TransactionId, command.TransactionDateTime, command.OperatorId, command.AdditionalTransactionMetadata, TransactionHelpers.GenerateTransactionReference(), cancellationToken); + end = DateTime.Now; + + if (operatorResult.IsSuccess) { + Logger.LogInformation("Operator authorised transaction successfully"); + transactionAggregate.AuthoriseTransaction(command.OperatorId, operatorResult.Data.AuthorisationCode, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, operatorResult.Data.TransactionId, TransactionResponseCode.Success, "SUCCESS"); + } + else { + Logger.LogWarning($"Operator declined transaction: {operatorResult.Data.ResponseMessage}"); + transactionAggregate.DeclineTransaction(command.OperatorId, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, TransactionResponseCode.TransactionDeclinedByOperator, "DECLINED BY OPERATOR"); + } + + transactionAggregate.RecordAdditionalResponseData(command.OperatorId, operatorResult.Data.AdditionalTransactionResponseMetadata); + return (start, end); + } } } \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs b/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs index 9feb7bca..d35c6689 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs @@ -1,6 +1,6 @@ namespace TransactionProcessor.BusinessLogic.Services { - public enum TransactionResponseCode + public enum TransactionResponseCodeX { Success = 0, SuccessNeedToAddDevice = 1, diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionValidationException.cs b/TransactionProcessor.BusinessLogic/Services/TransactionValidationException.cs index 93a4f1fb..00809951 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionValidationException.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionValidationException.cs @@ -1,4 +1,6 @@ -namespace TransactionProcessor.BusinessLogic.Services +using TransactionProcessor.Aggregates; + +namespace TransactionProcessor.BusinessLogic.Services { using System; using System.Diagnostics.CodeAnalysis; diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index c67fbbe7..a7fcfb42 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -348,7 +348,7 @@ public class TestData public static String AuthorisationCode = "ABCD1234"; - public static String DeclinedResponseCode = "0001"; + public static TransactionResponseCode DeclinedResponseCode = TransactionResponseCode.InvalidEstateId; public static String DeclinedResponseMessage = "DeclinedResponseMessage"; @@ -396,7 +396,7 @@ public class TestData public static Boolean RequireCustomTerminalNumberTrue = true; - public static String ResponseCode = "0000"; + public static TransactionResponseCode ResponseCode = TransactionResponseCode.Success; public static String ResponseMessage = "SUCCESS"; @@ -1038,14 +1038,14 @@ public static Dictionary AdditionalTransactionMetaDataForPataPaw new ProcessLogonTransactionResponse { ResponseMessage = TestData.ResponseMessage, - ResponseCode = TestData.ResponseCode + ResponseCode = TestData.ResponseCode.ToCodeString() }; public static ProcessSaleTransactionResponse ProcessSaleTransactionResponseModel => new ProcessSaleTransactionResponse { ResponseMessage = TestData.ResponseMessage, - ResponseCode = TestData.ResponseCode, + ResponseCode = TestData.ResponseCode.ToCodeString(), AdditionalTransactionMetadata = new Dictionary { {"OperatorResponseCode", "1000"} @@ -1069,7 +1069,7 @@ public static Dictionary AdditionalTransactionMetaDataForPataPaw new ProcessReconciliationTransactionResponse { ResponseMessage = TestData.ResponseMessage, - ResponseCode = TestData.ResponseCode + ResponseCode = TestData.ResponseCode.ToCodeString() }; #endregion @@ -1335,7 +1335,7 @@ public static TransactionAggregate GetLocallyDeclinedTransactionAggregate(Transa TestData.DeviceIdentifier, TestData.TransactionAmount); - transactionAggregate.DeclineTransactionLocally(TestData.GetResponseCodeAsString(transactionResponseCode), + transactionAggregate.DeclineTransactionLocally(transactionResponseCode, TestData.GetResponseCodeMessage(transactionResponseCode)); return transactionAggregate; @@ -1357,7 +1357,7 @@ public static TransactionAggregate GetDeclinedTransactionAggregate(TransactionRe transactionAggregate.DeclineTransaction(TestData.OperatorId, TestData.DeclinedOperatorResponseCode, TestData.DeclinedOperatorResponseMessage, - TestData.GetResponseCodeAsString(transactionResponseCode), + transactionResponseCode, TestData.GetResponseCodeMessage(transactionResponseCode)); return transactionAggregate; @@ -2603,7 +2603,7 @@ public static class DomainEvents { public static TransactionDomainEvents.TransactionHasBeenCompletedEvent TransactionHasBeenCompletedEvent => new TransactionDomainEvents.TransactionHasBeenCompletedEvent(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, - TestData.ResponseCode, + TestData.ResponseCode.ToCodeString(), TestData.ResponseMessage, TestData.IsAuthorised, TestData.TransactionDateTime, From 51c856faa41eb105f8a083e30d81992ac216323e Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Oct 2025 13:47:38 +0100 Subject: [PATCH 2/3] some extra tweaks --- .../Services/TransactionDomainService.cs | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index f2d5f0f6..31f76d01 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -672,20 +672,20 @@ public async Task> ProcessSaleTransaction Logger.LogInformation($"Starting ProcessSaleTransaction for TransactionId {command.TransactionId}"); try { - var transactionResult = await GetTransactionAggregateAsync(command, cancellationToken); + Result transactionResult = await GetTransactionAggregate(command, cancellationToken); if (transactionResult.IsFailed) return ResultHelpers.CreateFailure(transactionResult); - var transactionAggregate = transactionResult.Data; + TransactionAggregate transactionAggregate = transactionResult.Data; - var validationResult = await ValidateTransactionAsync(command, cancellationToken); - var (unitCost, totalCost) = await GetFloatCostAsync(command, validationResult, cancellationToken); + Result validationResult = await ValidateTransaction(command, cancellationToken); + (Decimal unitCost, Decimal totalCost) = await GetFloatCost(command, cancellationToken); - var startResult = StartTransaction(transactionAggregate, command, unitCost, totalCost, validationResult); + Result startResult = StartTransaction(transactionAggregate, command, unitCost, totalCost, validationResult); if (startResult.IsFailed) return ResultHelpers.CreateFailure(startResult); - var operatorTiming = await HandleOperatorProcessingAsync(transactionAggregate, command, validationResult, cancellationToken); + (DateTime? Start, DateTime? End) operatorTiming = await HandleOperatorProcessing(transactionAggregate, command, validationResult, cancellationToken); transactionAggregate.RecordTransactionTimings(command.TransactionReceivedDateTime, operatorTiming.Start, operatorTiming.End, DateTime.Now); @@ -694,11 +694,11 @@ public async Task> ProcessSaleTransaction if (!string.IsNullOrEmpty(command.CustomerEmailAddress)) transactionAggregate.RequestEmailReceipt(command.CustomerEmailAddress); - var saveResult = await AggregateService.Save(transactionAggregate, cancellationToken); + Result saveResult = await AggregateService.Save(transactionAggregate, cancellationToken); if (saveResult.IsFailed) return ResultHelpers.CreateFailure(saveResult); - var transaction = transactionAggregate.GetTransaction(); + Transaction transaction = transactionAggregate.GetTransaction(); Logger.LogInformation($"Transaction {command.TransactionId} completed successfully"); return Result.Success(new ProcessSaleTransactionResponse { @@ -716,11 +716,11 @@ public async Task> ProcessSaleTransaction } } - private async Task> GetTransactionAggregateAsync(TransactionCommands.ProcessSaleTransactionCommand command, + private async Task> GetTransactionAggregate(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { Logger.LogDebug($"Fetching TransactionAggregate for TransactionId {command.TransactionId}"); - var result = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(command.TransactionId, ct), command.TransactionId, cancellationToken, false); + Result result = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(command.TransactionId, ct), command.TransactionId, cancellationToken, false); if (result.IsFailed) Logger.LogWarning($"TransactionAggregate not found for TransactionId {command.TransactionId}"); @@ -729,12 +729,12 @@ private async Task> GetTransactionAggregateAsync(Tr } - private async Task> ValidateTransactionAsync(TransactionCommands.ProcessSaleTransactionCommand command, + private async Task> ValidateTransaction(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { - var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); + Decimal? amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); Logger.LogInformation($"Validating Sale Transaction for Merchant {command.MerchantId}, Product {command.ProductId}"); - var result = await TransactionValidationService.ValidateSaleTransaction(command.EstateId, command.MerchantId, command.ContractId, command.ProductId, command.DeviceIdentifier, command.OperatorId, amount, cancellationToken); + Result result = await TransactionValidationService.ValidateSaleTransaction(command.EstateId, command.MerchantId, command.ContractId, command.ProductId, command.DeviceIdentifier, command.OperatorId, amount, cancellationToken); Logger.LogInformation($"Validation completed with ResponseCode {result.Data?.ResponseCode}, Message {result.Data?.ResponseMessage}"); @@ -742,22 +742,21 @@ private async Task> ValidateTransactionAsync } - private async Task<(decimal UnitCost, decimal TotalCost)> GetFloatCostAsync(TransactionCommands.ProcessSaleTransactionCommand command, - Result validationResult, + private async Task<(decimal UnitCost, decimal TotalCost)> GetFloatCost(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { - var floatAggregateId = IdGenerationService.GenerateFloatAggregateId(command.EstateId, command.ContractId, command.ProductId); + Guid floatAggregateId = IdGenerationService.GenerateFloatAggregateId(command.EstateId, command.ContractId, command.ProductId); Logger.LogDebug($"Fetching FloatAggregate {floatAggregateId}"); - var floatAggregateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(floatAggregateId, ct), floatAggregateId, cancellationToken, false); + Result floatAggregateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(floatAggregateId, ct), floatAggregateId, cancellationToken, false); if (floatAggregateResult.IsFailed) return (0, 0); - var floatAggregate = floatAggregateResult.Data; - var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount") ?? 0; - var unitCost = floatAggregate.GetUnitCostPrice(); - var totalCost = floatAggregate.GetTotalCostPrice(amount); + FloatAggregate floatAggregate = floatAggregateResult.Data; + Decimal amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount") ?? 0; + Decimal unitCost = floatAggregate.GetUnitCostPrice(); + Decimal totalCost = floatAggregate.GetTotalCostPrice(amount); Logger.LogInformation($"Float cost calculated: UnitCost={unitCost}, TotalCost={totalCost}"); return (unitCost, totalCost); @@ -769,12 +768,12 @@ private Result StartTransaction(TransactionAggregate transactionAggregate, decimal unitCost, decimal totalCost, Result validationResult) { - var transactionType = TransactionType.Sale; - var transactionSourceValue = (TransactionSource)command.TransactionSource; - var transactionReference = TransactionHelpers.GenerateTransactionReference(); - var amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); + TransactionType transactionType = TransactionType.Sale; + TransactionSource transactionSourceValue = (TransactionSource)command.TransactionSource; + String transactionReference = TransactionHelpers.GenerateTransactionReference(); + Decimal? amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); - var result = transactionAggregate.StartTransaction(command.TransactionDateTime, command.TransactionNumber, transactionType, transactionReference, command.EstateId, command.MerchantId, command.DeviceIdentifier, amount); + Result result = transactionAggregate.StartTransaction(command.TransactionDateTime, command.TransactionNumber, transactionType, transactionReference, command.EstateId, command.MerchantId, command.DeviceIdentifier, amount); if (result.IsFailed) return result; @@ -788,7 +787,7 @@ private Result StartTransaction(TransactionAggregate transactionAggregate, return Result.Success(); } - private async Task<(DateTime? Start, DateTime? End)> HandleOperatorProcessingAsync(TransactionAggregate transactionAggregate, + private async Task<(DateTime? Start, DateTime? End)> HandleOperatorProcessing(TransactionAggregate transactionAggregate, TransactionCommands.ProcessSaleTransactionCommand command, Result validationResult, CancellationToken cancellationToken) { @@ -803,12 +802,12 @@ private Result StartTransaction(TransactionAggregate transactionAggregate, Logger.LogInformation($"Validation success, processing with Operator {command.OperatorId}"); transactionAggregate.RecordAdditionalRequestData(command.OperatorId, command.AdditionalTransactionMetadata); - var merchantResult = await GetMerchant(command.MerchantId, cancellationToken); + Result merchantResult = await GetMerchant(command.MerchantId, cancellationToken); if (merchantResult.IsFailed) return (start, end); start = DateTime.Now; - var operatorResult = await ProcessMessageWithOperator(merchantResult.Data, command.TransactionId, command.TransactionDateTime, command.OperatorId, command.AdditionalTransactionMetadata, TransactionHelpers.GenerateTransactionReference(), cancellationToken); + Result operatorResult = await ProcessMessageWithOperator(merchantResult.Data, command.TransactionId, command.TransactionDateTime, command.OperatorId, command.AdditionalTransactionMetadata, TransactionHelpers.GenerateTransactionReference(), cancellationToken); end = DateTime.Now; if (operatorResult.IsSuccess) { From b272d10e83200759d8647a19e33712aa77c2a600 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Oct 2025 13:54:06 +0100 Subject: [PATCH 3/3] oops :| --- .../Services/TransactionDomainService.cs | 137 +----------------- 1 file changed, 1 insertion(+), 136 deletions(-) diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index 31f76d01..ed67766e 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -203,142 +203,7 @@ public async Task> ProcessRecon return Result.Failure(ex.GetExceptionMessages()); } } - - public async Task> ProcessSaleTransactionX(TransactionCommands.ProcessSaleTransactionCommand command, - CancellationToken cancellationToken) { - try - { - Result transactionResult = await DomainServiceHelper.GetAggregateOrFailure(ct => this.AggregateService.GetLatest(command.TransactionId, ct), command.TransactionId, cancellationToken, false); - if (transactionResult.IsFailed) - return ResultHelpers.CreateFailure(transactionResult); - - TransactionAggregate transactionAggregate = transactionResult.Data; - - TransactionType transactionType = TransactionType.Sale; - TransactionSource transactionSourceValue = (TransactionSource)command.TransactionSource; - - // Generate a transaction reference - String transactionReference = TransactionHelpers.GenerateTransactionReference(); - - // Extract the transaction amount from the metadata - Decimal? transactionAmount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); - - Result validationResult = await this.TransactionValidationService.ValidateSaleTransaction(command.EstateId, command.MerchantId, command.ContractId, command.ProductId, command.DeviceIdentifier, command.OperatorId, transactionAmount, cancellationToken); - - Logger.LogInformation($"Validation response is [{JsonConvert.SerializeObject(validationResult)}]"); - - Guid floatAggregateId = IdGenerationService.GenerateFloatAggregateId(command.EstateId, command.ContractId, command.ProductId); - Result floatAggregateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => this.AggregateService.GetLatest(floatAggregateId, ct), floatAggregateId, cancellationToken, false); - Decimal unitCost = 0; - Decimal totalCost = 0; - if (floatAggregateResult.IsSuccess) { - FloatAggregate floatAggregate = floatAggregateResult.Data; - unitCost = floatAggregate.GetUnitCostPrice(); - totalCost = floatAggregate.GetTotalCostPrice(transactionAmount.GetValueOrDefault()); - } - - Result stateResult = transactionAggregate.StartTransaction(command.TransactionDateTime, command.TransactionNumber, transactionType, transactionReference, command.EstateId, command.MerchantId, command.DeviceIdentifier, transactionAmount); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - - // Add the product details (unless invalid estate) - if (validationResult.Data.ResponseCode != TransactionResponseCode.InvalidEstateId && validationResult.Data.ResponseCode != TransactionResponseCode.InvalidContractIdValue && validationResult.Data.ResponseCode != TransactionResponseCode.InvalidProductIdValue && validationResult.Data.ResponseCode != TransactionResponseCode.ContractNotValidForMerchant && validationResult.Data.ResponseCode != TransactionResponseCode.ProductNotValidForMerchant) { - stateResult = transactionAggregate.AddProductDetails(command.ContractId, command.ProductId); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - - stateResult = transactionAggregate.RecordCostPrice(unitCost, totalCost); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - - // Add the transaction source - stateResult = transactionAggregate.AddTransactionSource(transactionSourceValue); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - DateTime? operatorStartDateTime = null; - DateTime? operatorEndDateTime = null; - if (validationResult.Data.ResponseCode == TransactionResponseCode.Success) { - // Record any additional request metadata - stateResult = transactionAggregate.RecordAdditionalRequestData(command.OperatorId, command.AdditionalTransactionMetadata); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - - // Do the online processing with the operator here - Result merchantResult = await this.GetMerchant(command.MerchantId, cancellationToken); - if (merchantResult.IsFailed) - return ResultHelpers.CreateFailure(merchantResult); - operatorStartDateTime =DateTime.Now; - Result operatorResult = await this.ProcessMessageWithOperator(merchantResult.Data, command.TransactionId, command.TransactionDateTime, command.OperatorId, command.AdditionalTransactionMetadata, transactionReference, cancellationToken); - operatorEndDateTime = DateTime.Now; - - if (operatorResult.IsSuccess) { - TransactionResponseCode transactionResponseCode = TransactionResponseCode.Success; - String responseMessage = "SUCCESS"; - - stateResult= transactionAggregate.AuthoriseTransaction(command.OperatorId, operatorResult.Data.AuthorisationCode, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, operatorResult.Data.TransactionId, transactionResponseCode, responseMessage); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - else { - TransactionResponseCode transactionResponseCode = TransactionResponseCode.TransactionDeclinedByOperator; - String responseMessage = "DECLINED BY OPERATOR"; - - stateResult = transactionAggregate.DeclineTransaction(command.OperatorId, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, transactionResponseCode, responseMessage); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - - // Record any additional operator response metadata - stateResult = transactionAggregate.RecordAdditionalResponseData(command.OperatorId, operatorResult.Data.AdditionalTransactionResponseMetadata); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - else { - // Record the failure - stateResult = transactionAggregate.DeclineTransactionLocally(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - - stateResult = transactionAggregate.RecordTransactionTimings(command.TransactionReceivedDateTime, operatorStartDateTime, operatorEndDateTime, DateTime.Now);; - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - - stateResult= transactionAggregate.CompleteTransaction(); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - - // Determine if the email receipt is required - if (String.IsNullOrEmpty(command.CustomerEmailAddress) == false) { - stateResult = transactionAggregate.RequestEmailReceipt(command.CustomerEmailAddress); - if (stateResult.IsFailed) - return ResultHelpers.CreateFailure(stateResult); - } - - // Get the model from the aggregate - Models.Transaction transaction = transactionAggregate.GetTransaction(); - - Result saveResult = await this.AggregateService.Save(transactionAggregate, cancellationToken); - if (saveResult.IsFailed) - return ResultHelpers.CreateFailure(saveResult); - - return Result.Success(new ProcessSaleTransactionResponse - { - ResponseMessage = transaction.ResponseMessage, - ResponseCode = transaction.ResponseCode, - EstateId = command.EstateId, - MerchantId = command.MerchantId, - AdditionalTransactionMetadata = transaction.AdditionalResponseMetadata, - TransactionId = command.TransactionId - }); - } - catch (Exception ex) - { - return Result.Failure(ex.GetExceptionMessages()); - } - } - + public async Task ResendTransactionReceipt(TransactionCommands.ResendTransactionReceiptCommand command, CancellationToken cancellationToken) {