Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="9.0.0" />
<PackageVersion Include="ClientProxyBase" Version="2026.9.7" />
<PackageVersion Include="HealthMonitoring.Client" Version="2026.9.3.34" />
<PackageVersion Include="ClientProxyBase" Version="2026.10.1" />
<PackageVersion Include="HealthMonitoring.Client" Version="2026.9.4.48" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="7.7.1" />
<PackageVersion Include="OpenIddict.Validation.SystemNetHttp" Version="7.7.1" />
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
Expand All @@ -16,7 +16,7 @@
<PackageVersion Include="Imposter" Version="0.1.11" />
<PackageVersion Include="Lamar" Version="16.0.0" />
<PackageVersion Include="Lamar.Microsoft.DependencyInjection" Version="16.0.0" />
<PackageVersion Include="MessagingService.IntegrationTesting.Helpers" Version="2026.9.3.152" />
<PackageVersion Include="MessagingService.IntegrationTesting.Helpers" Version="2026.9.3.153" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.12" />
Expand All @@ -39,14 +39,14 @@
<PackageVersion Include="Sentry.AspNetCore" Version="6.11.1" />
<PackageVersion Include="SharpCompress" Version="0.50.4" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="Shared" Version="2026.9.7" />
<PackageVersion Include="Shared.DomainDrivenDesign" Version="2026.9.7" />
<PackageVersion Include="Shared.EventStore" Version="2026.9.7" />
<PackageVersion Include="Shared.IntegrationTesting" Version="2026.9.7" />
<PackageVersion Include="Shared.Results" Version="2026.9.7" />
<PackageVersion Include="Shared.Results.Web" Version="2026.9.7" />
<PackageVersion Include="SecurityService.Client" Version="2026.9.3.183" />
<PackageVersion Include="SecurityService.IntegrationTesting.Helpers" Version="2026.9.3.183" />
<PackageVersion Include="Shared" Version="2026.10.1" />
<PackageVersion Include="Shared.DomainDrivenDesign" Version="2026.10.1" />
<PackageVersion Include="Shared.EventStore" Version="2026.10.1" />
<PackageVersion Include="Shared.IntegrationTesting" Version="2026.10.1" />
<PackageVersion Include="Shared.Results" Version="2026.10.1" />
<PackageVersion Include="Shared.Results.Web" Version="2026.10.1" />
<PackageVersion Include="SecurityService.Client" Version="2026.9.3.196" />
<PackageVersion Include="SecurityService.IntegrationTesting.Helpers" Version="2026.9.3.196" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.2.3" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.2.3" />
<PackageVersion Include="Swashbuckle.AspNetCore.Filters" Version="10.0.1" />
Expand Down
115 changes: 66 additions & 49 deletions FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,55 +398,13 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
return stateResult;
}

Int32 transactionNumber = Interlocked.Increment(ref TransactionNumber);

Result<(Guid ContractId, Guid OperatorId, Guid ProductId, String MerchantDevice)> detailsForTransaction = await this.GetDetailsForTransaction(cancellationToken,
fileDetails,
fileProfile,
processingContext.OperatorName,
processingContext);
if (detailsForTransaction.IsFailed)
return ResultHelpers.CreateFailure(detailsForTransaction);

processingContext.Stage = FileLineProcessingStage.TransactionDetailsResolved;
Result<SaleTransactionResponse> saleResult = await this.SendSaleTransaction(fileDetails,
detailsForTransaction,
metadataResult.Data,
transactionNumber,
processingContext,
cancellationToken);
processingContext.TransactionDispatchSucceeded = saleResult.IsSuccess;
if (saleResult.IsSuccess)
processingContext.ResponseCode = saleResult.Data.ResponseCode;

// A failed dispatch has no definitive processor response. Leave the line unmodified so
// the file processing workflow can retry it with the same file and line identity.
if (saleResult.IsFailed)
{
stateResult = fileAggregate.RecordTransactionDispatchFailure(command.LineNumber,
transactionNumber,
processingContext.TransactionDispatchFailureType,
DateTime.UtcNow);
if (stateResult.IsFailed)
return stateResult;

dispatchFailureResult = ResultHelpers.CreateFailure(saleResult);
processingContext.Stage = FileLineProcessingStage.TransactionDispatchAttemptPersisted;
return Result.Success();
}

processingContext.Stage = FileLineProcessingStage.FileLineStateUpdate;
stateResult = saleResult.Data.ResponseCode == "0000"
? fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleResult.Data.TransactionId)
: fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleResult.Data.TransactionId, saleResult.Data.ResponseCode, saleResult.Data.ResponseMessage);

ProcessingResult processingResult = saleResult.IsSuccess && saleResult.Data.ResponseCode == "0000"
? ProcessingResult.Successful
: ProcessingResult.Failed;
FileLineProcessingDiagnostics.LineStateDetermined(processingContext, processingResult);
if (stateResult.IsSuccess)
processingContext.Stage = FileLineProcessingStage.FileLineStatePersisted;

(stateResult, dispatchFailureResult) = await this.ProcessTransaction(fileAggregate,
command,
fileDetails,
fileProfile,
metadataResult.Data,
processingContext,
cancellationToken);
return stateResult;
}, command.FileId, cancellationToken);

Expand All @@ -466,6 +424,65 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
return result;
}

private async Task<(Result StateResult, Result DispatchFailureResult)> ProcessTransaction(FileAggregate fileAggregate,
FileCommands.ProcessTransactionForFileLineCommand command,
FileDetails fileDetails,
FileProfileModel fileProfile,
Dictionary<String, String> transactionMetadata,
FileLineProcessingContext processingContext,
CancellationToken cancellationToken)
{
Int32 transactionNumber = Interlocked.Increment(ref TransactionNumber);

Result<(Guid ContractId, Guid OperatorId, Guid ProductId, String MerchantDevice)> detailsForTransaction = await this.GetDetailsForTransaction(cancellationToken,
fileDetails,
fileProfile,
processingContext.OperatorName,
processingContext);
if (detailsForTransaction.IsFailed)
return (ResultHelpers.CreateFailure(detailsForTransaction), null);

processingContext.Stage = FileLineProcessingStage.TransactionDetailsResolved;
Result<SaleTransactionResponse> saleResult = await this.SendSaleTransaction(fileDetails,
detailsForTransaction,
transactionMetadata,
transactionNumber,
processingContext,
cancellationToken);
processingContext.TransactionDispatchSucceeded = saleResult.IsSuccess;
if (saleResult.IsSuccess)
processingContext.ResponseCode = saleResult.Data.ResponseCode;

// A failed dispatch has no definitive processor response. Leave the line unmodified so
// the file processing workflow can retry it with the same file and line identity.
if (saleResult.IsFailed)
{
Result stateResult = fileAggregate.RecordTransactionDispatchFailure(command.LineNumber,
transactionNumber,
processingContext.TransactionDispatchFailureType,
DateTime.UtcNow);
if (stateResult.IsFailed)
return (stateResult, null);

processingContext.Stage = FileLineProcessingStage.TransactionDispatchAttemptPersisted;
return (Result.Success(), ResultHelpers.CreateFailure(saleResult));
}

processingContext.Stage = FileLineProcessingStage.FileLineStateUpdate;
Result stateUpdateResult = saleResult.Data.ResponseCode == "0000"
? fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleResult.Data.TransactionId)
: fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleResult.Data.TransactionId, saleResult.Data.ResponseCode, saleResult.Data.ResponseMessage);

ProcessingResult processingResult = saleResult.Data.ResponseCode == "0000"
? ProcessingResult.Successful
: ProcessingResult.Failed;
FileLineProcessingDiagnostics.LineStateDetermined(processingContext, processingResult);
if (stateUpdateResult.IsSuccess)
processingContext.Stage = FileLineProcessingStage.FileLineStatePersisted;

return (stateUpdateResult, null);
}

private Result<Dictionary<String, String>> BuildTransactionMetadata(FileCommands.ProcessTransactionForFileLineCommand command,
FileLine fileLine,
FileProfileModel fileProfile,
Expand Down
12 changes: 11 additions & 1 deletion FileProcessor.IntegrationTests/Common/DockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace FileProcessor.IntegrationTests.Common
using Shared.IntegrationTesting;
using Shared.Serialisation;
using System.Net.Http;
using System.Threading;
using TestHosts.Clients;
using TransactionProcessor.Client;

Expand Down Expand Up @@ -79,7 +80,9 @@ public override ContainerBuilder SetupFileProcessorContainer() {
this.AdditionalVariables[ContainerType.FileProcessor] = variables;
return base.SetupFileProcessorContainer();
}


public String AccessToken;

public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices){

await base.StartContainersForScenarioRun(scenarioName, dockerServices);
Expand Down Expand Up @@ -116,6 +119,13 @@ public override async Task StartContainersForScenarioRun(String scenarioName, Do
this.AgencyBankingClient = new AgencyBankingClient(TestHostServiceBaseAddressResolver, httpClient, Serialise_CamelCase, this.Deserialise_CamelCase);

this.ProjectionManagementClient = new EventStoreProjectionManagementClient(ConfigureEventStoreSettings());

SimpleResults.Result<SecurityService.DataTransferObjects.TokenResponse> bootstrapToken = await this.SecurityServiceClient.GetToken("management-bootstrap", "management-bootstrap-secret", CancellationToken.None);
if (bootstrapToken.IsFailed || String.IsNullOrWhiteSpace(bootstrapToken.Data?.AccessToken))
{
throw new InvalidOperationException("Unable to obtain the integration-test management bootstrap token.");
}
this.AccessToken = bootstrapToken.Data.AccessToken;
}

String Serialise(Object arg)
Expand Down
2 changes: 1 addition & 1 deletion FileProcessor.IntegrationTests/Steps/SharedSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public SharedSteps(ScenarioContext scenarioContext,
{
ScenarioContext = scenarioContext;
TestingContext = testingContext;
this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient);
this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient, testingContext.DockerHelper.AccessToken);
}

private TransactionProcessorSteps GetTransactionProcessorSteps()
Expand Down
33 changes: 16 additions & 17 deletions FileProcessor/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FileProcessor.Bootstrapper;
using FileProcessor.Common;
using HealthChecks.UI.Client;
using Lamar;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.Extensions;
using Shared.General;
using Shared.Logger;
using Shared.Middleware;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace FileProcessor
{
using Bootstrapper;
using Common;
using HealthChecks.UI.Client;
using Lamar;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.Extensions;
using Shared.General;
using Shared.Logger;
using Shared.Middleware;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILogger = Microsoft.Extensions.Logging.ILogger;

[ExcludeFromCodeCoverage]
public class Startup
{
Expand Down
Loading