diff --git a/TransactionProcessor.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs b/TransactionProcessor.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs index 542e6a98..6fdb8752 100644 --- a/TransactionProcessor.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs @@ -80,25 +80,7 @@ public async Task MerchantDomainService_CreateMerchant_MerchantIsCreated( Result result = await this.DomainService.CreateMerchant(command, TestContext.Current.CancellationToken); result.IsSuccess.ShouldBeTrue(); } - - [Fact] - public async Task MerchantDomainService_CreateMerchant_MerchantIdNotSet_MerchantIsCreated() { - this.AggregateService.GetLatest(Arg.Any(), Arg.Any()) - .ReturnsAsync(Result.Success(TestData.Aggregates.EmptyMerchantAggregate())); - this.AggregateService - .Save(Arg.Any(), Arg.Any()) - .ReturnsAsync(Result.Success()); - - this.AggregateService.Get(Arg.Any(), Arg.Any()) - .ReturnsAsync(TestData.Aggregates.CreatedEstateAggregate()); - - MerchantCommands.CreateMerchantCommand command = TestData.Commands.CreateMerchantCommand; - command.RequestDto.MerchantId = null; - - Result result = await this.DomainService.CreateMerchant(command, TestContext.Current.CancellationToken); - result.IsSuccess.ShouldBeTrue(); - } - + [Fact] public async Task MerchantDomainService_CreateMerchant_AlreadyCreated_MerchantIsCreated() { this.AggregateService.GetLatest(Arg.Any(), Arg.Any()) diff --git a/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs index 0f350853..72c33528 100644 --- a/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs +++ b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs @@ -23,7 +23,7 @@ public class MerchantRequestHandler : IRequestHandler>>, IRequestHandler, IRequestHandler, - IRequestHandler, + IRequestHandler>, IRequestHandler, IRequestHandler, IRequestHandler, @@ -128,7 +128,7 @@ public async Task Handle(MerchantCommands.AddMerchantContractCommand com return await this.MerchantDomainService.AddContractToMerchant(command, cancellationToken); } - public async Task Handle(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken) + public async Task> Handle(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken) { return await this.MerchantDomainService.CreateMerchant(command, cancellationToken); } diff --git a/TransactionProcessor.BusinessLogic/Requests/MerchantCommands.cs b/TransactionProcessor.BusinessLogic/Requests/MerchantCommands.cs index 47d24890..52b64349 100644 --- a/TransactionProcessor.BusinessLogic/Requests/MerchantCommands.cs +++ b/TransactionProcessor.BusinessLogic/Requests/MerchantCommands.cs @@ -10,7 +10,7 @@ namespace TransactionProcessor.BusinessLogic.Requests [ExcludeFromCodeCoverage] public class MerchantCommands{ - public record CreateMerchantCommand(Guid EstateId, CreateMerchantRequest RequestDto) : IRequest; + public record CreateMerchantCommand(Guid EstateId, CreateMerchantRequest RequestDto) : IRequest>; public record AssignOperatorToMerchantCommand(Guid EstateId, Guid MerchantId, AssignOperatorRequest RequestDto) : IRequest; diff --git a/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs b/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs index 68d98519..075c2945 100644 --- a/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs +++ b/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs @@ -65,7 +65,7 @@ public static Guid GenerateMerchantScheduleAggregateId(Guid estateId, Guid merch }); public static Guid GenerateContractId(Guid estateId, Guid operatorId, String contractName) { - String normalisedContractName = contractName.ToLower().Replace(" ", String.Empty); + String normalisedContractName = contractName.ToLowerInvariant().Replace(" ", String.Empty); return IdGenerationService.GenerateUniqueId(new { estateId, operatorId, normalisedContractName }); } @@ -75,5 +75,11 @@ public static Guid GenerateEstateId(String estateName) { return IdGenerationService.GenerateUniqueId(new { normalisedEstateName }); } + + public static Guid GenerateMerchantId(Guid estateId, String merchantName) { + String normalisedMerchantName = merchantName.ToLowerInvariant().Replace(" ", String.Empty); + + return IdGenerationService.GenerateUniqueId(new { estateId, normalisedMerchantName }); + } } } diff --git a/TransactionProcessor.BusinessLogic/Services/MerchantDomainService.cs b/TransactionProcessor.BusinessLogic/Services/MerchantDomainService.cs index f992efaa..f0101b2e 100644 --- a/TransactionProcessor.BusinessLogic/Services/MerchantDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/MerchantDomainService.cs @@ -30,7 +30,7 @@ namespace TransactionProcessor.BusinessLogic.Services public interface IMerchantDomainService { #region Methods - Task CreateMerchant(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken); + Task> CreateMerchant(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken); Task AssignOperatorToMerchant(MerchantCommands.AssignOperatorToMerchantCommand command, CancellationToken cancellationToken); Task CreateMerchantUser(MerchantCommands.CreateMerchantUserCommand command, CancellationToken cancellationToken); Task AddDeviceToMerchant(MerchantCommands.AddMerchantDeviceCommand command, CancellationToken cancellationToken); @@ -199,11 +199,11 @@ private Result EnsureMerchantDepositListCreated(MerchantDepositListAggregate mer return merchantDepositListAggregate.Create(merchantAggregate, depositDateTime); } - public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken) + public async Task> CreateMerchant(MerchantCommands.CreateMerchantCommand command, CancellationToken cancellationToken) { try { // Check if we have been sent a merchant id to use - Guid merchantId = command.RequestDto.MerchantId ?? Guid.NewGuid(); + Guid merchantId = IdGenerationService.GenerateMerchantId(command.EstateId, command.RequestDto.Name); Result estateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => this.AggregateService.Get(command.EstateId, ct), command.EstateId, cancellationToken); if (estateResult.IsFailed) @@ -227,7 +227,7 @@ public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand // Set the settlement schedule SettlementSchedule settlementSchedule = ConvertSettlementSchedule(command.RequestDto.SettlementSchedule); - var stateResult = merchantAggregate.Create(command.EstateId, command.RequestDto.Name, command.RequestDto.CreatedDateTime.GetValueOrDefault(DateTimeOffset.UtcNow), + Result stateResult = merchantAggregate.Create(command.EstateId, command.RequestDto.Name, command.RequestDto.CreatedDateTime.GetValueOrDefault(DateTimeOffset.UtcNow), address, contact, settlementSchedule); if (stateResult.IsFailed) return stateResult; @@ -237,7 +237,7 @@ public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand if (saveResult.IsFailed) return ResultHelpers.CreateFailure(saveResult); - return saveResult; + return Result.Success(merchantId); } catch (Exception ex) { return Result.Failure(ex.GetExceptionMessages()); diff --git a/TransactionProcessor.Client/ITransactionProcessorClient.cs b/TransactionProcessor.Client/ITransactionProcessorClient.cs index e926be92..74bde5a2 100644 --- a/TransactionProcessor.Client/ITransactionProcessorClient.cs +++ b/TransactionProcessor.Client/ITransactionProcessorClient.cs @@ -115,10 +115,10 @@ Task CreateEstateUser(String accessToken, CreateEstateUserRequest createEstateUserRequest, CancellationToken cancellationToken); - Task CreateMerchant(String accessToken, - Guid estateId, - CreateMerchantRequest createMerchantRequest, - CancellationToken cancellationToken); + Task> CreateMerchant(String accessToken, + Guid estateId, + CreateMerchantRequest createMerchantRequest, + CancellationToken cancellationToken); Task CreateMerchantSchedule(String accessToken, Guid estateId, diff --git a/TransactionProcessor.Client/TransactionProcessorClient.cs b/TransactionProcessor.Client/TransactionProcessorClient.cs index 43de9411..e7f9ed2e 100644 --- a/TransactionProcessor.Client/TransactionProcessorClient.cs +++ b/TransactionProcessor.Client/TransactionProcessorClient.cs @@ -728,19 +728,19 @@ public async Task CreateEstateUser(String accessToken, } } - public async Task CreateMerchant(String accessToken, + public async Task> CreateMerchant(String accessToken, Guid estateId, CreateMerchantRequest createMerchantRequest, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/merchants"); try { - var result = await this.Post(requestUri, createMerchantRequest, accessToken, cancellationToken); + Result result = await this.Post(requestUri, createMerchantRequest, accessToken, cancellationToken); if (result.IsFailed) return ResultHelpers.CreateFailure(result); - return Result.Success(); + return Result.Success(result.Data); } catch (Exception ex) { // An exception has occurred, add some additional information to the message diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs index 54fa0c76..f0326fd2 100644 --- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs +++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; using TransactionProcessor.DataTransferObjects.Responses.Merchant; namespace TransactionProcessor.DataTransferObjects.Requests.Merchant @@ -7,7 +6,7 @@ namespace TransactionProcessor.DataTransferObjects.Requests.Merchant /// /// Merchant identity, contact, address, and settlement schedule used at creation. /// - /// {"address":{"addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"},"contact":{"contactName":"Example Name","emailAddress":"customer@example.com","phoneNumber":"+15550100"},"name":"Example Name","settlementSchedule":0,"merchantId":"00000000-0000-0000-0000-000000000227","createdDateTime":"2026-01-15T10:30:00Z"} + /// {"address":{"addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"},"contact":{"contactName":"Example Name","emailAddress":"customer@example.com","phoneNumber":"+15550100"},"name":"Example Name","settlementSchedule":0,"createdDateTime":"2026-01-15T10:30:00Z"} public class CreateMerchantRequest { /// @@ -29,12 +28,7 @@ public class CreateMerchantRequest /// Settlement schedule assigned to the merchant. /// public SettlementSchedule SettlementSchedule { get; set; } - - /// - /// Optional caller-supplied merchant identifier; when omitted the service creates one. - /// - public Guid? MerchantId { get; set; } - + /// /// Optional merchant creation timestamp. /// diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs index b8139d00..bdcc304c 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs @@ -1092,7 +1092,6 @@ public static List ToCreateEstateRequests(this DataTableRow PostalCode = ReqnrollTableHelper.GetStringRowValue(tableRow, "PostalCode") }, SettlementSchedule = schedule, - MerchantId = Guid.NewGuid() }; requests.Add((estateDetails, createMerchantRequest, ReqnrollTableHelper.GetBooleanValue(tableRow, "AgencyBankingEnabled"))); } diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 7a64f653..961f8abf 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -342,25 +342,24 @@ await Retry.For(async () => } } - public async Task> WhenICreateTheFollowingMerchants(string accessToken, List<(EstateDetails estate, CreateMerchantRequest request, Boolean enableAgencyBanking)> requests) + public async Task> WhenICreateTheFollowingMerchants(string accessToken, List<(EstateDetails estate, CreateMerchantRequest request, Boolean enableAgencyBanking)> requests) { - List responses = new List(); + Dictionary createdMerchants = new(); - List<(Guid, Guid, String)> merchants = new List<(Guid, Guid, String)>(); + List<(Guid, Guid, String)> merchants = new(); foreach ((EstateDetails estate, CreateMerchantRequest request, Boolean enableAgencyBanking) request in requests) { - Result? result = await this.TransactionProcessorClient + Result result = await this.TransactionProcessorClient .CreateMerchant(accessToken, request.estate.EstateId, request.request, CancellationToken.None) .ConfigureAwait(false); result.IsSuccess.ShouldBeTrue(result.Message); - if (request.enableAgencyBanking) { CreateAgentRequest createAgentRequest = new() { - AgentId = request.request.MerchantId.Value.ToString(), + AgentId = result.Data.ToString(), DailyLimit = 100000, Name = request.request.Name, MinimumFloat = 10000, @@ -383,7 +382,7 @@ await Retry.For(async () => // Activate the agent ActivateAgentRequest activateAgentRequest = new ActivateAgentRequest { ActivatedBy = "INTEGRATIONTEST" }; - var activateAgentResult = await this.AgencyBankingClient.ActivateAgent(createAgentRequest.AgentId, activateAgentRequest, CancellationToken.None); + Result activateAgentResult = await this.AgencyBankingClient.ActivateAgent(createAgentRequest.AgentId, activateAgentRequest, CancellationToken.None); activateAgentResult.IsSuccess.ShouldBeTrue(activateAgentResult.Message); await Retry.For(async () => @@ -399,7 +398,7 @@ await Retry.For(async () => ConfigureFloatRequest configureFloatRequest = new ConfigureFloatRequest { AgentId = createAgentRequest.AgentId, DailyFloatLimit = 100000, MaximumFloat = 100000, MinimumFloat = 10000, }; - var configureFloatResult = await this.AgencyBankingClient.ConfigureFloat(configureFloatRequest, CancellationToken.None); + Result configureFloatResult = await this.AgencyBankingClient.ConfigureFloat(configureFloatRequest, CancellationToken.None); configureFloatResult.IsSuccess.ShouldBeTrue(configureFloatResult.Message); await Retry.For(async () => @@ -423,7 +422,7 @@ await Retry.For(async () => SourceAccount = "Test Account", TransactionId = Guid.NewGuid().ToString() }; - var floatCreditResult = await this.AgencyBankingClient.CreditFloat(floatCreditRequest, CancellationToken.None); + Result floatCreditResult = await this.AgencyBankingClient.CreditFloat(floatCreditRequest, CancellationToken.None); floatCreditResult.IsSuccess.ShouldBeTrue(floatCreditResult.Message); await Retry.For(async () => @@ -444,7 +443,7 @@ await Retry.For(async () => } - merchants.Add((request.estate.EstateId, request.request.MerchantId.Value, request.request.Name)); + merchants.Add((request.estate.EstateId, result.Data, request.request.Name)); } foreach ((Guid, Guid, String) m in merchants) @@ -471,10 +470,10 @@ await Retry.For(async () => { getMerchantBalanceResult2.Data.ShouldNotBeNull(); }); merchant.ShouldNotBeNull(); - responses.Add(merchant); + createdMerchants.Add(m.Item2, merchant); } - return responses; + return createdMerchants; } public void ValidateTransactions(List<(TransactionResponse, String, String, String)> transactions) diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs index 5a362f18..c5895f1b 100644 --- a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -308,12 +308,12 @@ public async Task WhenICreateTheFollowingMerchants(DataTable table) { List<(EstateDetails estate, CreateMerchantRequest, Boolean)> requests = table.Rows.ToCreateMerchantRequests(this.TestingContext.Estates); - List verifiedMerchants = await this.TransactionProcessorSteps.WhenICreateTheFollowingMerchants(this.TestingContext.AccessToken, requests); + Dictionary verifiedMerchants = await this.TransactionProcessorSteps.WhenICreateTheFollowingMerchants(this.TestingContext.AccessToken, requests); - foreach (DataTransferObjects.Responses.Merchant.MerchantResponse verifiedMerchant in verifiedMerchants){ - EstateDetails estateDetails = this.TestingContext.GetEstateDetails(verifiedMerchant.EstateId); - estateDetails.AddMerchant(verifiedMerchant); - this.TestingContext.Logger.LogInformation($"Merchant {verifiedMerchant.MerchantName} created with Id {verifiedMerchant.MerchantId} for Estate {estateDetails.EstateName}"); + foreach (KeyValuePair verifiedMerchant in verifiedMerchants){ + EstateDetails estateDetails = this.TestingContext.GetEstateDetails(verifiedMerchant.Value.EstateId); + estateDetails.AddMerchant(verifiedMerchant.Value); + this.TestingContext.Logger.LogInformation($"Merchant {verifiedMerchant.Value.MerchantName} created with Id {verifiedMerchant.Key} for Estate {estateDetails.EstateName}"); } } diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index d7b75f0e..40538e0a 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -279,7 +279,6 @@ static TestData() { PhoneNumber = TestData.MerchantContactPhoneNumber }, CreatedDateTime = TestData.DateMerchantCreated, - MerchantId = TestData.MerchantId, Name = TestData.MerchantName, SettlementSchedule = TestData.SettlementScheduleDTO }; diff --git a/TransactionProcessor.Tests/HandlerTests/WebHandlersTests.cs b/TransactionProcessor.Tests/HandlerTests/WebHandlersTests.cs index 12e7c3d9..bbd4e233 100644 --- a/TransactionProcessor.Tests/HandlerTests/WebHandlersTests.cs +++ b/TransactionProcessor.Tests/HandlerTests/WebHandlersTests.cs @@ -415,7 +415,7 @@ public static IEnumerable MerchantCases() yield return Case( "CreateMerchant", - SetupSuccess(command => + SetupSuccess(MerchantId, command => { command.EstateId.ShouldBe(EstateId); command.RequestDto.Name.ShouldBe("Merchant"); @@ -425,7 +425,6 @@ public static IEnumerable MerchantCases() EstateId, new CreateMerchantRequest { - MerchantId = MerchantId, Name = "Merchant" }, CancellationToken.None)); diff --git a/TransactionProcessor/Handlers/MerchantHandlers.cs b/TransactionProcessor/Handlers/MerchantHandlers.cs index ce099545..7edc5d0a 100644 --- a/TransactionProcessor/Handlers/MerchantHandlers.cs +++ b/TransactionProcessor/Handlers/MerchantHandlers.cs @@ -80,9 +80,9 @@ public static async Task CreateMerchant(IMediator mediator, CreateMerchantRequest createMerchantRequest, CancellationToken cancellationToken) { MerchantCommands.CreateMerchantCommand command = new(estateId, createMerchantRequest); - Result result = await mediator.Send(command, cancellationToken); + Result result = await mediator.Send(command, cancellationToken); - return ResponseFactory.FromResult(result); + return ResponseFactory.FromResult(result, (merchantId) => result.Data); } public static async Task AssignOperator(IMediator mediator,