From 093762984b0c52d5263d3a9e65ca6457422c9d64 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 8 Oct 2025 12:42:00 +0100 Subject: [PATCH 1/2] fix some of the TODO's --- .../MerchantDepositListAggregate.cs | 1 - .../MerchantStatementAggregate.cs | 2 - .../VoucherDomainEventHandler.cs | 2 - .../Manager/TransactionProcessorManager.cs | 2 - .../Services/SettlementDomainService.cs | 2 - .../Services/TransactionDomainService.cs | 29 ++-- .../Merchant/MerchantStatementLine.cs | 1 - .../EventHandling/EventHandler.cs | 10 +- ...TransactionProcessorReadModelRepository.cs | 4 +- .../Bootstrapper/RepositoryRegistry.cs | 68 ++++----- .../Controllers/MerchantController.cs | 136 ++++++++---------- TransactionProcessor/Extensions.cs | 2 +- 12 files changed, 105 insertions(+), 154 deletions(-) diff --git a/TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs b/TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs index 2cd69fd6..834ed619 100644 --- a/TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs +++ b/TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs @@ -75,7 +75,6 @@ public static Result MakeDeposit(this MerchantDepositListAggregate aggregate, if (result.IsFailed) return result; - // TODO: Change amount to a value object (PositiveAmount VO) result = aggregate.EnsureDepositSourceHasBeenSet(source); if (result.IsFailed) return result; diff --git a/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs index 8055d1ff..449d36ed 100644 --- a/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs +++ b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs @@ -257,8 +257,6 @@ public static Result AddDailySummaryRecord(this MerchantStatementAggregate aggre return Result.Success(); } - // TODO: should this check the date has been added to the statement, before allowing the summary? - MerchantStatementDomainEvents.StatementSummaryForDateEvent statementSummaryForDateEvent = new(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, activityDate,aggregate.MerchantStatementSummaries.Count +1 ,numberOfTransactions, valueOfTransactions, numberOfSettledFees, valueOfSettledFees, numberOfDepoits, valueOfDepoits, numberOfWithdrawals, valueOfWithdrawals); aggregate.ApplyAndAppend(statementSummaryForDateEvent); diff --git a/TransactionProcessor.BusinessLogic/EventHandling/VoucherDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/VoucherDomainEventHandler.cs index f7987acb..e2aeabba 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/VoucherDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/VoucherDomainEventHandler.cs @@ -120,8 +120,6 @@ public async Task Handle(IDomainEvent domainEvent, private async Task GetVoucherOperator(Models.Voucher voucherModel, CancellationToken cancellationToken) { - // TODO: Can this be done in a better way than direct db access ? - using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, voucherModel.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; diff --git a/TransactionProcessor.BusinessLogic/Manager/TransactionProcessorManager.cs b/TransactionProcessor.BusinessLogic/Manager/TransactionProcessorManager.cs index 3a9b97ae..c210280e 100644 --- a/TransactionProcessor.BusinessLogic/Manager/TransactionProcessorManager.cs +++ b/TransactionProcessor.BusinessLogic/Manager/TransactionProcessorManager.cs @@ -183,8 +183,6 @@ public async Task>> GetMerchants(Guid estateId, Guid productId, CancellationToken cancellationToken) { - // TODO: this will need updated to handle merchant specific fees when that is available - Result getContractResult = await this.AggregateService.GetLatest(contractId, cancellationToken); if (getContractResult.IsFailed) return ResultHelpers.CreateFailure(getContractResult); diff --git a/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs b/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs index 3723c86e..5754b15f 100644 --- a/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs @@ -35,8 +35,6 @@ public class SettlementDomainService : ISettlementDomainService { private readonly IAggregateService AggregateService; - // TODO: Add in a Get Settlement - public async Task> ProcessSettlement(SettlementCommands.ProcessSettlementCommand command, CancellationToken cancellationToken) { diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index b119e89c..4e7d6ea0 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -274,19 +274,7 @@ public async Task> ProcessSaleTransaction operatorStartDateTime =DateTime.Now; Result operatorResult = await this.ProcessMessageWithOperator(merchantResult.Data, command.TransactionId, command.TransactionDateTime, command.OperatorId, command.AdditionalTransactionMetadata, transactionReference, cancellationToken); operatorEndDateTime = DateTime.Now; - // Act on the operator response - // TODO: see if we still need this case... - //if (operatorResult.IsFailed) { - // // Failed to perform sed/receive with the operator - // TransactionResponseCode transactionResponseCode = - // TransactionResponseCode.OperatorCommsError; - // String responseMessage = "OPERATOR COMMS ERROR"; - - // transactionAggregate.DeclineTransactionLocally( - // ((Int32)transactionResponseCode).ToString().PadLeft(4, '0'), responseMessage); - //} - //else { - + if (operatorResult.IsSuccess) { TransactionResponseCode transactionResponseCode = TransactionResponseCode.Success; String responseMessage = "SUCCESS"; @@ -405,12 +393,12 @@ public async Task CalculateFeesForTransaction(TransactionCommands.Calcul if (RequireFeeCalculation(transactionAggregate) == false) return Result.Success(); - List feesForCalculation = await this.GetTransactionFeesForCalculation(transactionAggregate, cancellationToken); + Result> feesForCalculationResult = await this.GetTransactionFeesForCalculation(transactionAggregate, cancellationToken); - if (feesForCalculation == null) - return Result.Failure("Error getting transaction fees"); + if (feesForCalculationResult.IsFailed) + return ResultHelpers.CreateFailure(feesForCalculationResult); - List resultFees = this.FeeCalculationManager.CalculateFees(feesForCalculation, transactionAggregate.TransactionAmount.Value, command.CompletedDateTime); + List resultFees = this.FeeCalculationManager.CalculateFees(feesForCalculationResult.Data, transactionAggregate.TransactionAmount.Value, command.CompletedDateTime); IEnumerable nonMerchantFees = resultFees.Where(f => f.FeeType == TransactionProcessor.Models.Contract.FeeType.ServiceProvider); foreach (CalculatedFee calculatedFee in nonMerchantFees) { @@ -558,15 +546,14 @@ internal static DateTime CalculateSettlementDate(Models.Merchant.SettlementSched return completeDateTime.Date; } - private async Task> GetTransactionFeesForCalculation(TransactionAggregate transactionAggregate, + private async Task>> GetTransactionFeesForCalculation(TransactionAggregate transactionAggregate, CancellationToken cancellationToken) { - // TODO: convert to result?? // Get the fees to be calculated Result> feesForProduct = await this.GetTransactionFeesForProduct(transactionAggregate.ContractId, transactionAggregate.ProductId, cancellationToken); if (feesForProduct.IsFailed) { Logger.LogWarning($"Failed to get fees {feesForProduct.Message}"); - return null; + return ResultHelpers.CreateFailure(feesForProduct); } Logger.LogInformation($"After getting Fees {feesForProduct.Data.Count} returned"); @@ -579,7 +566,7 @@ private async Task> GetTransactionFeesForCalcula feesForCalculation.Add(transactionFeeToCalculate); } - return feesForCalculation; + return Result.Success(feesForCalculation); } private async Task AddDeviceToMerchant(Guid merchantId, diff --git a/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs b/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs index 00ec7682..c7d4a2d4 100644 --- a/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs +++ b/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs @@ -32,7 +32,6 @@ public class MerchantStatementLine /// public String Description { get; set; } - // TODO: maybe make this an enum type /// /// Gets or sets the type of the line. /// diff --git a/TransactionProcessor.ProjectionEngine/EventHandling/EventHandler.cs b/TransactionProcessor.ProjectionEngine/EventHandling/EventHandler.cs index 88116878..2ced4619 100644 --- a/TransactionProcessor.ProjectionEngine/EventHandling/EventHandler.cs +++ b/TransactionProcessor.ProjectionEngine/EventHandling/EventHandler.cs @@ -12,18 +12,10 @@ namespace TransactionProcessor.ProjectionEngine.EventHandling public class EventHandler : IDomainEventHandler { private readonly Func Resolver; - - public static Dictionary StateTypes; - + public EventHandler(Func resolver) { this.Resolver = resolver; - List subclassTypes = Assembly.GetAssembly(typeof(Shared.EventStore.ProjectionEngine.State))?.GetTypes().Where(t => t.IsSubclassOf(typeof(Shared.EventStore.ProjectionEngine.State))).ToList(); - - if (subclassTypes != null) - { - EventHandler.StateTypes = subclassTypes.ToDictionary(x => x.Name, x => x); - } } public async Task Handle(IDomainEvent domainEvent, diff --git a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs index 336e71dc..7f1468bc 100644 --- a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs +++ b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs @@ -1161,7 +1161,8 @@ public async Task DisableContractProductTransactionFee(ContractDomainEve EstateManagementContext context = await this.GetContext(domainEvent.EstateId); Result loadContractProductTransactionFeeResult = await context.LoadContractProductTransactionFee(domainEvent, cancellationToken); - // TODO: Check the result value + if (loadContractProductTransactionFeeResult.IsFailed) + return ResultHelpers.CreateFailure(loadContractProductTransactionFeeResult); ContractProductTransactionFee transactionFee = loadContractProductTransactionFeeResult.Data; @@ -1174,7 +1175,6 @@ public async Task MarkMerchantFeeAsSettled(SettlementDomainEvents.Mercha CancellationToken cancellationToken) { EstateManagementContext context = await this.GetContext(domainEvent.EstateId); - // TODO: LoadMerchantSettlementFee MerchantSettlementFee merchantFee = await context.MerchantSettlementFees.Where(m => m.MerchantId == domainEvent.MerchantId && m.TransactionId == domainEvent.TransactionId && diff --git a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs index 9b20ae06..23526d8e 100644 --- a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs +++ b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs @@ -36,27 +36,24 @@ namespace TransactionProcessor.Bootstrapper [ExcludeFromCodeCoverage] public class RepositoryRegistry : ServiceRegistry { - #region Constructors + private static bool CachedAggregatesAdded; + private static readonly object CachedAggregatesLock = new(); - private static Boolean CachedAggregatesAdded; - /// - /// Initializes a new instance of the class. - /// public RepositoryRegistry() { - String eventStoreConnectionString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString"); + string eventStoreConnectionString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString"); this.AddEventStoreProjectionManagementClient(eventStoreConnectionString); this.AddEventStorePersistentSubscriptionsClient(eventStoreConnectionString); - this.AddEventStoreClient(eventStoreConnectionString); - this.AddSingleton>(cont => (esConnString, cacheDuration) => { - return SubscriptionRepository.Create(esConnString, cacheDuration); - }); + this.AddSingleton>(cont => (esConnString, cacheDuration) => + SubscriptionRepository.Create(esConnString, cacheDuration)); this.AddSingleton(typeof(IDbContextResolver<>), typeof(DbContextResolver<>)); - if (Startup.WebHostEnvironment.IsEnvironment("IntegrationTest") || Startup.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase") == true) + + if (Startup.WebHostEnvironment.IsEnvironment("IntegrationTest") || + Startup.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase")) { this.AddDbContext(builder => builder.UseInMemoryDatabase("TransactionProcessorReadModel")); } @@ -67,28 +64,15 @@ public RepositoryRegistry() } this.AddTransient(); - this.AddSingleton>(c => () => { - - IAggregateService aggregateService = Startup.ServiceProvider.GetService(); - if (RepositoryRegistry.CachedAggregatesAdded == false) - { - aggregateService.AddCachedAggregate(typeof(EstateAggregate), null); - aggregateService.AddCachedAggregate(typeof(ContractAggregate), null); - aggregateService.AddCachedAggregate(typeof(OperatorAggregate), null); - aggregateService.AddCachedAggregate(typeof(MerchantAggregate), null); - RepositoryRegistry.CachedAggregatesAdded=true; - } - - return aggregateService; - }); + + // ✅ Move static behavior to a thread-safe static method + EnsureCachedAggregatesRegistered(); + this.AddSingleton(); this.AddSingleton(); - this.AddSingleton, - AggregateRepository>(); - this.AddSingleton, - AggregateRepository>(); - this.AddSingleton, - AggregateRepository>(); + this.AddSingleton, AggregateRepository>(); + this.AddSingleton, AggregateRepository>(); + this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); @@ -99,7 +83,6 @@ public RepositoryRegistry() this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); - this.AddSingleton, MerchantBalanceStateRepository>(); this.AddSingleton, VoucherStateRepository>(); this.AddSingleton(); @@ -107,9 +90,28 @@ public RepositoryRegistry() this.AddSingleton, MerchantBalanceProjection>(); } - #endregion + private static void EnsureCachedAggregatesRegistered() + { + if (CachedAggregatesAdded) + return; + + lock (CachedAggregatesLock) + { + if (CachedAggregatesAdded) + return; + + IAggregateService aggregateService = Startup.ServiceProvider.GetService(); + aggregateService.AddCachedAggregate(typeof(EstateAggregate), null); + aggregateService.AddCachedAggregate(typeof(ContractAggregate), null); + aggregateService.AddCachedAggregate(typeof(OperatorAggregate), null); + aggregateService.AddCachedAggregate(typeof(MerchantAggregate), null); + + CachedAggregatesAdded = true; + } + } } + [ExcludeFromCodeCoverage] public class ConfigurationReaderConnectionStringRepository : IConnectionStringConfigurationRepository { diff --git a/TransactionProcessor/Controllers/MerchantController.cs b/TransactionProcessor/Controllers/MerchantController.cs index c144c86f..8f13a373 100644 --- a/TransactionProcessor/Controllers/MerchantController.cs +++ b/TransactionProcessor/Controllers/MerchantController.cs @@ -232,26 +232,26 @@ internal ClaimsPrincipal GetUser() }; } - private bool PerformStandardChecks(Guid estateId) + private Result PerformStandardChecks(Guid estateId) { // Get the Estate Id claim from the user Result estateIdClaimResult = ClaimsHelper.GetUserClaim(GetUser(), "EstateId",estateId.ToString()); if (estateIdClaimResult.IsFailed) - return false; // TODO: Shoudl this be a result? + return ResultHelpers.CreateFailure(estateIdClaimResult); Claim estateIdClaim = estateIdClaimResult.Data; - string estateRoleName = Environment.GetEnvironmentVariable("EstateRoleName"); + String estateRoleName = Environment.GetEnvironmentVariable("EstateRoleName"); if (ClaimsHelper.IsUserRolesValid(GetUser(), new[] { string.IsNullOrEmpty(estateRoleName) ? "Estate" : estateRoleName }) == false) { - return false; + return Result.Invalid("User Roles not valid"); } if (ClaimsHelper.ValidateRouteParameter(estateId, estateIdClaim) == false) { - return false; + return Result.Invalid("Route parameter not valid"); } - return true; + return Result.Success(); } [HttpPost] @@ -261,9 +261,8 @@ public async Task CreateMerchant([FromRoute] Guid estateId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -285,9 +284,8 @@ public async Task AssignOperator([FromRoute] Guid estateId, AssignOperatorRequest assignOperatorRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -307,9 +305,8 @@ public async Task RemoveOperator([FromRoute] Guid estateId, [FromRoute] Guid operatorId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -329,9 +326,8 @@ public async Task AddDevice([FromRoute] Guid estateId, [FromBody] AddMerchantDeviceRequest addMerchantDeviceRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -351,9 +347,8 @@ public async Task AddContract([FromRoute] Guid estateId, [FromBody] AddMerchantContractRequest addMerchantContractRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -373,9 +368,8 @@ public async Task RemoveContract([FromRoute] Guid estateId, [FromRoute] Guid contractId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -395,9 +389,8 @@ public async Task CreateMerchantUser([FromRoute] Guid estateId, [FromBody] CreateMerchantUserRequest createMerchantUserRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -417,9 +410,8 @@ public async Task MakeDeposit([FromRoute] Guid estateId, [FromBody] MakeMerchantDepositRequest makeMerchantDepositRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -442,9 +434,8 @@ public async Task MakeWithdrawal([FromRoute] Guid estateId, [FromBody] MakeMerchantWithdrawalRequest makeMerchantWithdrawalRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -465,9 +456,8 @@ public async Task SwapMerchantDevice([FromRoute] Guid estateId, [FromBody] SwapMerchantDeviceRequest swapMerchantDeviceRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -480,12 +470,12 @@ public async Task SwapMerchantDevice([FromRoute] Guid estateId, return result.ToActionResultX(); } - private bool PerformMerchantUserChecks(Guid estateId, Guid merchantId) + private Result PerformMerchantUserChecks(Guid estateId, Guid merchantId) { - if (PerformStandardChecks(estateId) == false) - { - return false; + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { + return ResultHelpers.CreateFailure(isRequestAllowedResult); } string merchantRoleName = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MerchantRoleName")) @@ -493,23 +483,22 @@ private bool PerformMerchantUserChecks(Guid estateId, Guid merchantId) : Environment.GetEnvironmentVariable("MerchantRoleName"); if (GetUser().IsInRole(merchantRoleName) == false) - return true; + return Result.Success(); - if (ClaimsHelper.IsUserRolesValid(GetUser(), new[] { merchantRoleName }) == false) - { - return false; + if (ClaimsHelper.IsUserRolesValid(GetUser(), new[] { merchantRoleName }) == false) { + return Result.Invalid("User Roles not valid"); } Result getMerchantIdClaimResult = ClaimsHelper.GetUserClaim(GetUser(), "MerchantId"); if (getMerchantIdClaimResult.IsFailed) - return false; // TODO: Shoudl this be a result? + return ResultHelpers.CreateFailure(getMerchantIdClaimResult); + Claim merchantIdClaim = getMerchantIdClaimResult.Data; - if (ClaimsHelper.ValidateRouteParameter(merchantId, merchantIdClaim) == false) - { - return false; + if (ClaimsHelper.ValidateRouteParameter(merchantId, merchantIdClaim) == false) { + return Result.Invalid("Route parameter not valid"); } - return true; + return Result.Success(); } [HttpGet] @@ -518,9 +507,8 @@ public async Task GetMerchant([FromRoute] Guid estateId, [FromRoute] Guid merchantId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformMerchantUserChecks(estateId, merchantId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformMerchantUserChecks(estateId, merchantId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -620,8 +608,8 @@ public async Task GetMerchantContracts([FromRoute] Guid estateId, [FromRoute] Guid merchantId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformMerchantUserChecks(estateId, merchantId); - if (isRequestAllowed == false) + Result isRequestAllowedResult = PerformMerchantUserChecks(estateId, merchantId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -641,9 +629,8 @@ public async Task GetMerchantContracts([FromRoute] Guid estateId, public async Task GetMerchants([FromRoute] Guid estateId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -662,9 +649,8 @@ public async Task GetTransactionFeesForProduct([FromRoute] Guid e [FromRoute] Guid productId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformMerchantUserChecks(estateId, merchantId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformMerchantUserChecks(estateId, merchantId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -685,9 +671,8 @@ public async Task UpdateMerchant([FromRoute] Guid estateId, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -707,9 +692,8 @@ public async Task AddMerchantAddress([FromRoute] Guid estateId, [FromBody] DataTransferObjects.Requests.Merchant.Address addAddressRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -730,9 +714,8 @@ public async Task UpdateMerchantAddress([FromRoute] Guid estateId [FromBody] DataTransferObjects.Requests.Merchant.Address updateAddressRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -752,9 +735,8 @@ public async Task AddMerchantContact([FromRoute] Guid estateId, [FromBody] DataTransferObjects.Requests.Merchant.Contact addContactRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -775,9 +757,8 @@ public async Task UpdateMerchantContact([FromRoute] Guid estateId [FromBody] DataTransferObjects.Requests.Merchant.Contact updateContactRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } @@ -797,9 +778,8 @@ public async Task GenerateMerchantStatement([FromRoute] Guid esta [FromBody] GenerateMerchantStatementRequest generateMerchantStatementRequest, CancellationToken cancellationToken) { - bool isRequestAllowed = PerformStandardChecks(estateId); - if (isRequestAllowed == false) - { + Result isRequestAllowedResult = PerformStandardChecks(estateId); + if (isRequestAllowedResult.IsFailed) { return Forbid(); } diff --git a/TransactionProcessor/Extensions.cs b/TransactionProcessor/Extensions.cs index 0362382a..c3dc242d 100644 --- a/TransactionProcessor/Extensions.cs +++ b/TransactionProcessor/Extensions.cs @@ -100,7 +100,7 @@ public class StatementFilePollerService { private readonly IEnumerable FileProfiles; public StatementFilePollerService(IMediator mediator) { this.Mediator = mediator; - // TODO: File Profiles + // Load up the file configuration this.FileProfiles = Startup.Configuration.GetSection("AppSettings:FileProfiles").GetChildren().ToList().Select(x => new { From 537f6580e1b3761592be4bde233600f3352b0769 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 8 Oct 2025 13:28:31 +0100 Subject: [PATCH 2/2] fix test failures --- .../Bootstrapper/RepositoryRegistry.cs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs index 23526d8e..da38f721 100644 --- a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs +++ b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs @@ -65,8 +65,30 @@ public RepositoryRegistry() this.AddTransient(); - // ✅ Move static behavior to a thread-safe static method - EnsureCachedAggregatesRegistered(); + // ✅ Defer to the container — safe and compliant + this.AddSingleton>(c => () => + { + var aggregateService = c.GetService(); + + // Thread-safe single initialization + if (!CachedAggregatesAdded) + { + lock (CachedAggregatesLock) + { + if (!CachedAggregatesAdded) + { + aggregateService.AddCachedAggregate(typeof(EstateAggregate), null); + aggregateService.AddCachedAggregate(typeof(ContractAggregate), null); + aggregateService.AddCachedAggregate(typeof(OperatorAggregate), null); + aggregateService.AddCachedAggregate(typeof(MerchantAggregate), null); + + CachedAggregatesAdded = true; + } + } + } + + return aggregateService; + }); this.AddSingleton(); this.AddSingleton(); @@ -89,26 +111,6 @@ public RepositoryRegistry() this.AddSingleton(); this.AddSingleton, MerchantBalanceProjection>(); } - - private static void EnsureCachedAggregatesRegistered() - { - if (CachedAggregatesAdded) - return; - - lock (CachedAggregatesLock) - { - if (CachedAggregatesAdded) - return; - - IAggregateService aggregateService = Startup.ServiceProvider.GetService(); - aggregateService.AddCachedAggregate(typeof(EstateAggregate), null); - aggregateService.AddCachedAggregate(typeof(ContractAggregate), null); - aggregateService.AddCachedAggregate(typeof(OperatorAggregate), null); - aggregateService.AddCachedAggregate(typeof(MerchantAggregate), null); - - CachedAggregatesAdded = true; - } - } }