From b0822c19db4a506e2cffae412fca5a663674e745 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 8 Jul 2026 17:46:26 +0100 Subject: [PATCH 1/3] Add resend receipt endpoint and command support Added support for resending transaction receipts via a new API endpoint and command. Introduced DTOs for request and response, updated the application service and handler logic, and implemented validation. Extended unit and integration tests to cover the new functionality. Refactored integration test table variable names for clarity. --- .../MediatorTests.cs | 11 + .../RequestHandlerTests.cs | 27 +++ .../ProcessLogonTransactionRequestHandler.cs | 15 +- .../Requests/TransactionCommands.cs | 6 + ...ansactionProcessorACLApplicationService.cs | 6 + ...ansactionProcessorACLApplicationService.cs | 194 ++++++++++++++++ .../Requests/ResendReceiptRequestMessage.cs | 11 + .../Reporting/Reporting.feature.cs | 212 ++++++++--------- .../SalesTransaction.feature.cs | 216 +++++++++--------- .../ResendReceiptResponse.cs | 14 ++ .../Handlers/TransactionHandlersTests.cs | 56 +++++ .../Endpoints/TransactionEndpoints.cs | 6 + .../Handlers/TransactionHandlers.cs | 62 +++-- 13 files changed, 607 insertions(+), 229 deletions(-) create mode 100644 TransactionProcessorACL.DataTransferObjects/Requests/ResendReceiptRequestMessage.cs create mode 100644 TransactionProcessorACL.Models/ResendReceiptResponse.cs diff --git a/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs b/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs index 9fa99de..85a169e 100644 --- a/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs +++ b/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs @@ -18,6 +18,7 @@ namespace TransactionProcessorACL.BusinessLogic.Tests { using System.Threading; using Microsoft.Extensions.DependencyInjection; + using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.DataTransferObjects.Requests; using Models; using Services; @@ -40,6 +41,7 @@ public MediatorTests() this.Requests.Add(TestData.GetMerchantDailyPerformanceSummaryQuery); this.Requests.Add(TestData.GetMerchantTransactionMixSummaryQuery); this.Requests.Add(TestData.GetRecentActivityReceiptSearchQuery); + this.Requests.Add(new TransactionCommands.ResendReceiptCommand(TestData.EstateId, TestData.MerchantId, "RCPT-0001", "recipient@example.com")); } [Fact] @@ -185,5 +187,14 @@ public async Task> GetRecentActivity { return Result.Success(new RecentActivityReceiptSearchResponse()); } + + public async Task> ResendReceipt(Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { + return Result.Success(new ResendReceiptResponse { Success = true, Message = "Receipt resend requested." }); + } } } diff --git a/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs b/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs index 8097c11..b60f6ba 100644 --- a/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs +++ b/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs @@ -285,6 +285,33 @@ public async Task ReportingRequestHandler_GetRecentActivityReceiptSearchQuery_Ha result.Data.ShouldNotBeNull(); } + [Fact] + public async Task TransactionRequestHandler_ResendReceiptCommand_Handle_RequestIsHandled() + { + Mock applicationService = new Mock(); + applicationService + .Setup(a => a.ResendReceipt(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new ResendReceiptResponse { Success = true, Message = "Receipt resend requested." }); + + TransactionRequestHandler requestHandler = new TransactionRequestHandler(applicationService.Object); + + TransactionCommands.ResendReceiptCommand command = new( + TestData.EstateId, + TestData.MerchantId, + "RCPT-0001", + "recipient@example.com"); + + Result result = await requestHandler.Handle(command, CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldNotBeNull(); + result.Data.Success.ShouldBeTrue(); + } + #endregion } } diff --git a/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs index a5337ed..8193016 100644 --- a/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs +++ b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs @@ -16,7 +16,8 @@ namespace TransactionProcessorACL.BusinessLogic.RequestHandlers /// public class TransactionRequestHandler : IRequestHandler>, IRequestHandler>, - IRequestHandler> + IRequestHandler>, + IRequestHandler> { #region Fields @@ -62,7 +63,7 @@ public async Task> Handle(TransactionComma } public async Task> Handle(TransactionCommands.ProcessSaleTransactionCommand command, - CancellationToken cancellationToken) + CancellationToken cancellationToken) { return await this.ApplicationService.ProcessSaleTransaction((command.EstateId, command.MerchantId), command.TransactionDateTime, @@ -74,6 +75,16 @@ public async Task> Handle(TransactionComm cancellationToken); } + public async Task> Handle(TransactionCommands.ResendReceiptCommand command, + CancellationToken cancellationToken) + { + return await this.ApplicationService.ResendReceipt(command.EstateId, + command.MerchantId, + command.Reference, + command.RecipientEmailAddress, + cancellationToken); + } + #endregion } } diff --git a/TransactionProcessorACL.BusinessLogic/Requests/TransactionCommands.cs b/TransactionProcessorACL.BusinessLogic/Requests/TransactionCommands.cs index 8b4ecff..e772fc2 100644 --- a/TransactionProcessorACL.BusinessLogic/Requests/TransactionCommands.cs +++ b/TransactionProcessorACL.BusinessLogic/Requests/TransactionCommands.cs @@ -36,4 +36,10 @@ public record ProcessReconciliationCommand(Guid EstateId, Int32 TransactionCount, Decimal TransactionValue) : IRequest>; + + public record ResendReceiptCommand(Guid EstateId, + Guid MerchantId, + String Reference, + String RecipientEmailAddress) + : IRequest>; } diff --git a/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs b/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs index 881dbf2..6713655 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs @@ -73,5 +73,11 @@ Task> GetMerchantTransactionMixSum Task> GetRecentActivityReceiptSearch(Guid estateId, RecentActivityReceiptSearchRequest request, CancellationToken cancellationToken); + + Task> ResendReceipt(Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken); } } diff --git a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs index 5ba486b..8cbb678 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs @@ -492,6 +492,200 @@ public async Task> GetRecentActivity return response; } + public async Task> ResendReceipt(Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { + Result accessTokenResult = await this.GetAccessToken(cancellationToken); + if (accessTokenResult.IsFailed) { + return ResultHelpers.CreateFailure(accessTokenResult); + } + + if (string.IsNullOrWhiteSpace(reference)) { + return Result.Invalid("A transaction reference or receipt reference is required."); + } + + if (string.IsNullOrWhiteSpace(recipientEmailAddress)) { + return Result.Invalid("Recipient email address is required."); + } + + try { + _ = new System.Net.Mail.MailAddress(recipientEmailAddress); + } + catch (FormatException) { + return Result.Invalid("Recipient email address is not valid."); + } + + Result resendResult = await this.InvokeResendReceiptAsync(accessTokenResult.Data.AccessToken, + estateId, + merchantId, + reference, + recipientEmailAddress, + cancellationToken); + + if (resendResult.IsFailed) { + return resendResult; + } + + return resendResult; + } + + private async Task> InvokeResendReceiptAsync(String accessToken, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { + System.Reflection.MethodInfo? method = typeof(ITransactionProcessorClient) + .GetMethods() + .SingleOrDefault(m => string.Equals(m.Name, "ResendEmailReceipt", StringComparison.Ordinal)); + + if (method == null) { + return Result.Failure("Receipt resend is not supported by the transaction client."); + } + + System.Reflection.ParameterInfo[] parameters = method.GetParameters(); + object?[] args = new object?[parameters.Length]; + bool estateAssigned = false; + bool merchantAssigned = false; + object? request = null; + + for (int index = 0; index < parameters.Length; index++) { + System.Reflection.ParameterInfo parameter = parameters[index]; + + if (parameter.ParameterType == typeof(String)) { + args[index] = accessToken; + continue; + } + + if (parameter.ParameterType == typeof(Guid)) { + if (estateAssigned == false) { + args[index] = estateId; + estateAssigned = true; + } + else if (merchantAssigned == false) { + args[index] = merchantId; + merchantAssigned = true; + } + else { + args[index] = Guid.Empty; + } + + continue; + } + + if (parameter.ParameterType == typeof(CancellationToken)) { + args[index] = cancellationToken; + continue; + } + + request ??= System.Activator.CreateInstance(parameter.ParameterType); + if (request == null) { + return Result.Failure($"Unable to create resend receipt request type '{parameter.ParameterType.FullName}'."); + } + + PopulateResendReceiptRequest(request, estateId, merchantId, reference, recipientEmailAddress); + args[index] = request; + } + + object? invocationResult = method.Invoke(this.TransactionProcessorClient, args); + if (invocationResult is not Task task) { + return Result.Failure("Receipt resend call did not return a task."); + } + + await task.ConfigureAwait(false); + + object? taskResult = task.GetType().GetProperty("Result")?.GetValue(task); + if (taskResult == null) { + return Result.Failure("Receipt resend call returned no result."); + } + + bool isFailed = (bool)(taskResult.GetType().GetProperty("IsFailed")?.GetValue(taskResult) ?? true); + if (isFailed) { + string? message = taskResult.GetType().GetProperty("Message")?.GetValue(taskResult)?.ToString(); + string? status = taskResult.GetType().GetProperty("Status")?.GetValue(taskResult)?.ToString(); + + if (string.Equals(status, "NotFound", StringComparison.OrdinalIgnoreCase)) { + return Result.NotFound(message ?? "Receipt could not be found."); + } + + if (string.Equals(status, "Invalid", StringComparison.OrdinalIgnoreCase)) { + return Result.Invalid(message ?? "Receipt resend request was invalid."); + } + + return Result.Failure(message ?? "Receipt resend failed."); + } + + object? data = taskResult.GetType().GetProperty("Data")?.GetValue(taskResult); + return Result.Success(MapResendReceiptResponse(data, reference)); + } + + private static void PopulateResendReceiptRequest(object request, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress) + { + Type requestType = request.GetType(); + SetIfPresent(requestType, request, "EstateId", estateId); + SetIfPresent(requestType, request, "MerchantId", merchantId); + SetIfPresent(requestType, request, "Reference", reference); + SetIfPresent(requestType, request, "ReceiptReference", reference); + SetIfPresent(requestType, request, "TransactionReference", reference); + SetIfPresent(requestType, request, "RecipientEmail", recipientEmailAddress); + SetIfPresent(requestType, request, "RecipientEmailAddress", recipientEmailAddress); + SetIfPresent(requestType, request, "EmailAddress", recipientEmailAddress); + } + + private static void SetIfPresent(Type requestType, object request, String propertyName, object value) + { + System.Reflection.PropertyInfo? property = requestType.GetProperty(propertyName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase); + if (property == null || property.CanWrite == false) { + return; + } + + property.SetValue(request, value); + } + + private static ResendReceiptResponse MapResendReceiptResponse(object? data, String reference) + { + ResendReceiptResponse response = new() + { + Success = true, + Message = "Receipt resend requested.", + Reference = reference + }; + + if (data == null) { + return response; + } + + Type responseType = data.GetType(); + response.Message = GetStringProperty(responseType, data, "Message") ?? response.Message; + response.Reference = GetStringProperty(responseType, data, "Reference") ?? response.Reference; + response.ReceiptReference = GetStringProperty(responseType, data, "ReceiptReference"); + response.TransactionReference = GetStringProperty(responseType, data, "TransactionReference"); + + if (string.IsNullOrWhiteSpace(response.Reference) && string.IsNullOrWhiteSpace(response.ReceiptReference) == false) { + response.Reference = response.ReceiptReference; + } + + if (string.IsNullOrWhiteSpace(response.Reference) && string.IsNullOrWhiteSpace(response.TransactionReference) == false) { + response.Reference = response.TransactionReference; + } + + return response; + } + + private static string? GetStringProperty(Type type, object instance, String propertyName) + { + System.Reflection.PropertyInfo? property = type.GetProperty(propertyName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase); + return property?.GetValue(instance)?.ToString(); + } + private static ProcessReconciliationResponse CreateProcessReconciliationResponse(ReconciliationResponse reconciliationResponse) { return new ProcessReconciliationResponse diff --git a/TransactionProcessorACL.DataTransferObjects/Requests/ResendReceiptRequestMessage.cs b/TransactionProcessorACL.DataTransferObjects/Requests/ResendReceiptRequestMessage.cs new file mode 100644 index 0000000..aaab1d2 --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/Requests/ResendReceiptRequestMessage.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + +namespace TransactionProcessorACL.DataTransferObjects.Requests; + +[ExcludeFromCodeCoverage] +public class ResendReceiptRequestMessage +{ + public string Reference { get; set; } + + public string RecipientEmailAddress { get; set; } +} diff --git a/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs index c529e36..911167e 100644 --- a/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs +++ b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs @@ -113,167 +113,167 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa { #line 4 #line hidden - global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table73 = new global::Reqnroll.Table(new string[] { "Role Name"}); - table1.AddRow(new string[] { + table73.AddRow(new string[] { "Merchant"}); #line 6 - await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table1, "Given "); + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table73, "Given "); #line hidden - global::Reqnroll.Table table2 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table74 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Description"}); - table2.AddRow(new string[] { + table74.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST Scope", "A scope for Transaction Processor REST"}); - table2.AddRow(new string[] { + table74.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST Scope", "A scope for Transaction Processor ACL REST"}); - table2.AddRow(new string[] { + table74.AddRow(new string[] { "estateReporting", "Estate Reporting REST Scope", "Scope for Estate Reporting REST"}); #line 10 - await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table2, "Given "); + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table74, "Given "); #line hidden - global::Reqnroll.Table table3 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table75 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table3.AddRow(new string[] { + table75.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST", "Secret1", "transactionProcessor", "merchantId, estateId, role"}); - table3.AddRow(new string[] { + table75.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST", "Secret1", "transactionProcessorACL", "merchantId, estateId, role"}); - table3.AddRow(new string[] { + table75.AddRow(new string[] { "estateReporting", "Estate Reporting REST", "Secret1", "estateReporting", "merchantId,estateId,role"}); #line 16 - await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table3, "Given "); + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table75, "Given "); #line hidden - global::Reqnroll.Table table4 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table76 = new global::Reqnroll.Table(new string[] { "ClientId", "ClientName", "Secret", "Scopes", "GrantTypes"}); - table4.AddRow(new string[] { + table76.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "transactionProcessor,transactionProcessorACL, estateReporting", "client_credentials"}); - table4.AddRow(new string[] { + table76.AddRow(new string[] { "merchantClient", "Merchant Client", "Secret1", "transactionProcessorACL", "password"}); #line 22 - await testRunner.GivenAsync("the following clients exist", ((string)(null)), table4, "Given "); + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table76, "Given "); #line hidden - global::Reqnroll.Table table5 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table77 = new global::Reqnroll.Table(new string[] { "ClientId"}); - table5.AddRow(new string[] { + table77.AddRow(new string[] { "serviceClient"}); #line 27 await testRunner.GivenAsync("I have a token to access the estate management and transaction processor acl reso" + - "urces", ((string)(null)), table5, "Given "); + "urces", ((string)(null)), table77, "Given "); #line hidden - global::Reqnroll.Table table6 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table78 = new global::Reqnroll.Table(new string[] { "EstateName"}); - table6.AddRow(new string[] { + table78.AddRow(new string[] { "Test Estate 1"}); - table6.AddRow(new string[] { + table78.AddRow(new string[] { "Test Estate 2"}); #line 31 - await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table6, "Given "); + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table78, "Given "); #line hidden - global::Reqnroll.Table table7 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table79 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "RequireCustomMerchantNumber", "RequireCustomTerminalNumber"}); - table7.AddRow(new string[] { + table79.AddRow(new string[] { "Test Estate 1", "Safaricom", "True", "True"}); - table7.AddRow(new string[] { + table79.AddRow(new string[] { "Test Estate 1", "Voucher", "True", "True"}); - table7.AddRow(new string[] { + table79.AddRow(new string[] { "Test Estate 2", "Safaricom", "True", "True"}); - table7.AddRow(new string[] { + table79.AddRow(new string[] { "Test Estate 2", "Voucher", "True", "True"}); #line 36 - await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table7, "Given "); + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table79, "Given "); #line hidden - global::Reqnroll.Table table8 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table80 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName"}); - table8.AddRow(new string[] { + table80.AddRow(new string[] { "Test Estate 1", "Safaricom"}); - table8.AddRow(new string[] { + table80.AddRow(new string[] { "Test Estate 1", "Voucher"}); - table8.AddRow(new string[] { + table80.AddRow(new string[] { "Test Estate 2", "Safaricom"}); - table8.AddRow(new string[] { + table80.AddRow(new string[] { "Test Estate 2", "Voucher"}); #line 43 - await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table8, "And "); + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table80, "And "); #line hidden - global::Reqnroll.Table table9 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table81 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription"}); - table9.AddRow(new string[] { + table81.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract"}); - table9.AddRow(new string[] { + table81.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract"}); - table9.AddRow(new string[] { + table81.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract"}); - table9.AddRow(new string[] { + table81.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract"}); #line 50 - await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table9, "Given "); + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table81, "Given "); #line hidden - global::Reqnroll.Table table10 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table82 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -281,7 +281,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "DisplayText", "Value", "ProductType"}); - table10.AddRow(new string[] { + table82.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -289,7 +289,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table10.AddRow(new string[] { + table82.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract", @@ -297,7 +297,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "10 KES", "", "Voucher"}); - table10.AddRow(new string[] { + table82.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -305,7 +305,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table10.AddRow(new string[] { + table82.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract", @@ -314,9 +314,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "Voucher"}); #line 57 - await testRunner.WhenAsync("I create the following Products", ((string)(null)), table10, "When "); + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table82, "When "); #line hidden - global::Reqnroll.Table table11 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table83 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -324,7 +324,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "CalculationType", "FeeDescription", "Value"}); - table11.AddRow(new string[] { + table83.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -332,7 +332,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Fixed", "Merchant Commission", "2.50"}); - table11.AddRow(new string[] { + table83.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -341,9 +341,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Merchant Commission", "0.85"}); #line 64 - await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table11, "When "); + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table83, "When "); #line hidden - global::Reqnroll.Table table12 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table84 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -353,7 +353,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ContactName", "EmailAddress", "EstateName"}); - table12.AddRow(new string[] { + table84.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -363,7 +363,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table12.AddRow(new string[] { + table84.AddRow(new string[] { "Test Merchant 2", "Address Line 1", "TestTown", @@ -373,7 +373,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 2", "testcontact2@merchant2.co.uk", "Test Estate 1"}); - table12.AddRow(new string[] { + table84.AddRow(new string[] { "Test Merchant 3", "Address Line 1", "TestTown", @@ -384,152 +384,152 @@ await testRunner.GivenAsync("I have a token to access the estate management and "testcontact3@merchant2.co.uk", "Test Estate 2"}); #line 69 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table12, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table84, "Given "); #line hidden - global::Reqnroll.Table table13 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table85 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Safaricom", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Voucher", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Safaricom", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Voucher", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Safaricom", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); - table13.AddRow(new string[] { + table85.AddRow(new string[] { "Voucher", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); #line 75 - await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table13, "Given "); + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table85, "Given "); #line hidden - global::Reqnroll.Table table14 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table86 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table14.AddRow(new string[] { + table86.AddRow(new string[] { "123456780", "Test Merchant 1", "Test Estate 1"}); - table14.AddRow(new string[] { + table86.AddRow(new string[] { "123456781", "Test Merchant 2", "Test Estate 1"}); - table14.AddRow(new string[] { + table86.AddRow(new string[] { "123456782", "Test Merchant 3", "Test Estate 2"}); #line 84 - await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table14, "Given "); + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table86, "Given "); #line hidden - global::Reqnroll.Table table15 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table87 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "ContractDescription"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Safaricom Contract"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Hospital 1 Contract"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Safaricom Contract"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Hospital 1 Contract"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Safaricom Contract"}); - table15.AddRow(new string[] { + table87.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Hospital 1 Contract"}); #line 90 - await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table15, "When "); + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table87, "When "); #line hidden - global::Reqnroll.Table table16 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table88 = new global::Reqnroll.Table(new string[] { "Reference", "Amount", "DateTime", "MerchantName", "EstateName"}); - table16.AddRow(new string[] { + table88.AddRow(new string[] { "Deposit1", "210.00", "Today", "Test Merchant 1", "Test Estate 1"}); - table16.AddRow(new string[] { + table88.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 2", "Test Estate 1"}); - table16.AddRow(new string[] { + table88.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 3", "Test Estate 2"}); #line 99 - await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table16, "Given "); + await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table88, "Given "); #line hidden - global::Reqnroll.Table table17 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table89 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "EstateName", "MerchantName"}); - table17.AddRow(new string[] { + table89.AddRow(new string[] { "merchantuser@testmerchant1.co.uk", "123456", "TestMerchant", "User1", "Test Estate 1", "Test Merchant 1"}); - table17.AddRow(new string[] { + table89.AddRow(new string[] { "merchantuser@testmerchant2.co.uk", "123456", "TestMerchant", "User2", "Test Estate 1", "Test Merchant 2"}); - table17.AddRow(new string[] { + table89.AddRow(new string[] { "merchantuser@testmerchant3.co.uk", "123456", "TestMerchant", @@ -537,7 +537,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 2", "Test Merchant 3"}); #line 105 - await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table17, "Given "); + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table89, "Given "); #line hidden } @@ -576,7 +576,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table18 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table90 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -591,7 +591,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table18.AddRow(new string[] { + table90.AddRow(new string[] { "Today", "8", "Sale", @@ -607,7 +607,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", ""}); #line 114 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table18, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table90, "When "); #line hidden #line 118 await testRunner.WhenAsync("I get the merchant daily performance summary for Merchant \"Test Merchant 1\" for E" + @@ -651,7 +651,7 @@ await testRunner.ThenAsync("the merchant daily performance summary response shou "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table19 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table91 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -666,7 +666,7 @@ await testRunner.ThenAsync("the merchant daily performance summary response shou "ProductName", "RecipientEmail", "RecipientMobile"}); - table19.AddRow(new string[] { + table91.AddRow(new string[] { "Today", "9", "Sale", @@ -682,7 +682,7 @@ await testRunner.ThenAsync("the merchant daily performance summary response shou "", ""}); #line 124 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table19, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table91, "When "); #line hidden #line 128 await testRunner.WhenAsync("I get the merchant transaction mix summary for Merchant \"Test Merchant 1\" for Est" + @@ -726,7 +726,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table20 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table92 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -741,7 +741,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "ProductName", "RecipientEmail", "RecipientMobile"}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1001", "Sale", @@ -756,7 +756,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1002", "Sale", @@ -771,7 +771,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1003", "Sale", @@ -786,7 +786,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1004", "Sale", @@ -801,7 +801,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1005", "Sale", @@ -816,7 +816,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1006", "Sale", @@ -831,7 +831,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1007", "Sale", @@ -846,7 +846,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1008", "Sale", @@ -861,7 +861,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1009", "Sale", @@ -876,7 +876,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "Variable Topup", "", ""}); - table20.AddRow(new string[] { + table92.AddRow(new string[] { "Today", "1010", "Sale", @@ -892,7 +892,7 @@ await testRunner.ThenAsync("the merchant transaction mix summary response should "", ""}); #line 134 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table20, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table92, "When "); #line hidden #line 147 await testRunner.WhenAsync("I get the recent activity receipt search for Merchant \"Test Merchant 1\" for Estat" + diff --git a/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs b/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs index ae0c58a..6386cb2 100644 --- a/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs +++ b/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs @@ -111,157 +111,157 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa { #line 4 #line hidden - global::Reqnroll.Table table92 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table93 = new global::Reqnroll.Table(new string[] { "Role Name"}); - table92.AddRow(new string[] { + table93.AddRow(new string[] { "Merchant"}); #line 6 - await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table92, "Given "); + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table93, "Given "); #line hidden - global::Reqnroll.Table table93 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table94 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Description"}); - table93.AddRow(new string[] { + table94.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST Scope", "A scope for Transaction Processor REST"}); - table93.AddRow(new string[] { + table94.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST Scope", "A scope for Transaction Processor ACL REST"}); #line 10 - await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table93, "Given "); + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table94, "Given "); #line hidden - global::Reqnroll.Table table94 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table95 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table94.AddRow(new string[] { + table95.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST", "Secret1", "transactionProcessor", "merchantId, estateId, role"}); - table94.AddRow(new string[] { + table95.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST", "Secret1", "transactionProcessorACL", "merchantId, estateId, role"}); #line 15 - await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table94, "Given "); + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table95, "Given "); #line hidden - global::Reqnroll.Table table95 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table96 = new global::Reqnroll.Table(new string[] { "ClientId", "ClientName", "Secret", "Scopes", "GrantTypes"}); - table95.AddRow(new string[] { + table96.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "transactionProcessor,transactionProcessorACL", "client_credentials"}); - table95.AddRow(new string[] { + table96.AddRow(new string[] { "merchantClient", "Merchant Client", "Secret1", "transactionProcessorACL", "password"}); #line 20 - await testRunner.GivenAsync("the following clients exist", ((string)(null)), table95, "Given "); + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table96, "Given "); #line hidden - global::Reqnroll.Table table96 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table97 = new global::Reqnroll.Table(new string[] { "ClientId"}); - table96.AddRow(new string[] { + table97.AddRow(new string[] { "serviceClient"}); #line 25 await testRunner.GivenAsync("I have a token to access the estate management and transaction processor acl reso" + - "urces", ((string)(null)), table96, "Given "); + "urces", ((string)(null)), table97, "Given "); #line hidden - global::Reqnroll.Table table97 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table98 = new global::Reqnroll.Table(new string[] { "EstateName"}); - table97.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 1"}); - table97.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 2"}); #line 29 - await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table97, "Given "); + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table98, "Given "); #line hidden - global::Reqnroll.Table table98 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table99 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "RequireCustomMerchantNumber", "RequireCustomTerminalNumber"}); - table98.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 1", "Safaricom", "True", "True"}); - table98.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 1", "Voucher", "True", "True"}); - table98.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 2", "Safaricom", "True", "True"}); - table98.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 2", "Voucher", "True", "True"}); #line 34 - await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table98, "Given "); + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table99, "Given "); #line hidden - global::Reqnroll.Table table99 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table100 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName"}); - table99.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 1", "Safaricom"}); - table99.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 1", "Voucher"}); - table99.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 2", "Safaricom"}); - table99.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 2", "Voucher"}); #line 41 - await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table99, "And "); + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table100, "And "); #line hidden - global::Reqnroll.Table table100 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table101 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription"}); - table100.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract"}); - table100.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract"}); - table100.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract"}); - table100.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract"}); #line 48 - await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table100, "Given "); + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table101, "Given "); #line hidden - global::Reqnroll.Table table101 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table102 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -269,7 +269,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "DisplayText", "Value", "ProductType"}); - table101.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -277,7 +277,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table101.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract", @@ -285,7 +285,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "10 KES", "", "Voucher"}); - table101.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -293,7 +293,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table101.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract", @@ -302,9 +302,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "Voucher"}); #line 55 - await testRunner.WhenAsync("I create the following Products", ((string)(null)), table101, "When "); + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table102, "When "); #line hidden - global::Reqnroll.Table table102 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table103 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -312,7 +312,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "CalculationType", "FeeDescription", "Value"}); - table102.AddRow(new string[] { + table103.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -320,7 +320,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Fixed", "Merchant Commission", "2.50"}); - table102.AddRow(new string[] { + table103.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -329,9 +329,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Merchant Commission", "0.85"}); #line 62 - await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table102, "When "); + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table103, "When "); #line hidden - global::Reqnroll.Table table103 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table104 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -341,7 +341,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ContactName", "EmailAddress", "EstateName"}); - table103.AddRow(new string[] { + table104.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -351,7 +351,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table103.AddRow(new string[] { + table104.AddRow(new string[] { "Test Merchant 2", "Address Line 1", "TestTown", @@ -361,7 +361,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 2", "testcontact2@merchant2.co.uk", "Test Estate 1"}); - table103.AddRow(new string[] { + table104.AddRow(new string[] { "Test Merchant 3", "Address Line 1", "TestTown", @@ -372,152 +372,152 @@ await testRunner.GivenAsync("I have a token to access the estate management and "testcontact3@merchant2.co.uk", "Test Estate 2"}); #line 67 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table103, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table104, "Given "); #line hidden - global::Reqnroll.Table table104 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table105 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Safaricom", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Voucher", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Safaricom", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Voucher", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Safaricom", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); - table104.AddRow(new string[] { + table105.AddRow(new string[] { "Voucher", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); #line 73 - await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table104, "Given "); + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table105, "Given "); #line hidden - global::Reqnroll.Table table105 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table106 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table105.AddRow(new string[] { + table106.AddRow(new string[] { "123456780", "Test Merchant 1", "Test Estate 1"}); - table105.AddRow(new string[] { + table106.AddRow(new string[] { "123456781", "Test Merchant 2", "Test Estate 1"}); - table105.AddRow(new string[] { + table106.AddRow(new string[] { "123456782", "Test Merchant 3", "Test Estate 2"}); #line 82 - await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table105, "Given "); + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table106, "Given "); #line hidden - global::Reqnroll.Table table106 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table107 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "ContractDescription"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Safaricom Contract"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Hospital 1 Contract"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Safaricom Contract"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Hospital 1 Contract"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Safaricom Contract"}); - table106.AddRow(new string[] { + table107.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Hospital 1 Contract"}); #line 88 - await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table106, "When "); + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table107, "When "); #line hidden - global::Reqnroll.Table table107 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table108 = new global::Reqnroll.Table(new string[] { "Reference", "Amount", "DateTime", "MerchantName", "EstateName"}); - table107.AddRow(new string[] { + table108.AddRow(new string[] { "Deposit1", "210.00", "Today", "Test Merchant 1", "Test Estate 1"}); - table107.AddRow(new string[] { + table108.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 2", "Test Estate 1"}); - table107.AddRow(new string[] { + table108.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 3", "Test Estate 2"}); #line 97 - await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table107, "Given "); + await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table108, "Given "); #line hidden - global::Reqnroll.Table table108 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table109 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "EstateName", "MerchantName"}); - table108.AddRow(new string[] { + table109.AddRow(new string[] { "merchantuser@testmerchant1.co.uk", "123456", "TestMerchant", "User1", "Test Estate 1", "Test Merchant 1"}); - table108.AddRow(new string[] { + table109.AddRow(new string[] { "merchantuser@testmerchant2.co.uk", "123456", "TestMerchant", "User2", "Test Estate 1", "Test Merchant 2"}); - table108.AddRow(new string[] { + table109.AddRow(new string[] { "merchantuser@testmerchant3.co.uk", "123456", "TestMerchant", @@ -525,7 +525,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 2", "Test Merchant 3"}); #line 103 - await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table108, "Given "); + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table109, "Given "); #line hidden } @@ -564,7 +564,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table109 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table110 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -579,7 +579,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table109.AddRow(new string[] { + table110.AddRow(new string[] { "Today", "1", "Sale", @@ -594,7 +594,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table109.AddRow(new string[] { + table110.AddRow(new string[] { "Today", "4", "Sale", @@ -609,7 +609,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table109.AddRow(new string[] { + table110.AddRow(new string[] { "Today", "5", "Sale", @@ -625,14 +625,14 @@ await testRunner.GivenAsync("I have a token to access the estate management and "test@recipient.co.uk", ""}); #line 112 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table109, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table110, "When "); #line hidden #line 118 await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant2.co.uk\" with password \"123456\" for M" + "erchant \"Test Merchant 2\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table110 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table111 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -647,7 +647,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table110.AddRow(new string[] { + table111.AddRow(new string[] { "Today", "2", "Sale", @@ -662,7 +662,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table110.AddRow(new string[] { + table111.AddRow(new string[] { "Today", "6", "Sale", @@ -678,14 +678,14 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "123456789"}); #line 119 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table110, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table111, "When "); #line hidden #line 124 await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant3.co.uk\" with password \"123456\" for M" + "erchant \"Test Merchant 3\" for Estate \"Test Estate 2\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table111 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table112 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -700,7 +700,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table111.AddRow(new string[] { + table112.AddRow(new string[] { "Today", "3", "Sale", @@ -715,7 +715,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table111.AddRow(new string[] { + table112.AddRow(new string[] { "Today", "7", "Sale", @@ -731,58 +731,58 @@ await testRunner.GivenAsync("I have a token to access the estate management and "test@recipient.co.uk", ""}); #line 125 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table111, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table112, "When "); #line hidden - global::Reqnroll.Table table112 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table113 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "TransactionNumber", "TransactionType", "ResponseCode", "ResponseMessage"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "1", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "2", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "3", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "4", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "5", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "6", "Sale", "0000", "SUCCESS"}); - table112.AddRow(new string[] { + table113.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "7", @@ -790,7 +790,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0000", "SUCCESS"}); #line 130 - await testRunner.ThenAsync("the sale transaction response should contain the following information", ((string)(null)), table112, "Then "); + await testRunner.ThenAsync("the sale transaction response should contain the following information", ((string)(null)), table113, "Then "); #line hidden } await this.ScenarioCleanupAsync(); diff --git a/TransactionProcessorACL.Models/ResendReceiptResponse.cs b/TransactionProcessorACL.Models/ResendReceiptResponse.cs new file mode 100644 index 0000000..de2b9a2 --- /dev/null +++ b/TransactionProcessorACL.Models/ResendReceiptResponse.cs @@ -0,0 +1,14 @@ +namespace TransactionProcessorACL.Models; + +public class ResendReceiptResponse +{ + public bool Success { get; set; } + + public string Message { get; set; } + + public string Reference { get; set; } + + public string ReceiptReference { get; set; } + + public string TransactionReference { get; set; } +} diff --git a/TransactionProcessorACL.Tests/Handlers/TransactionHandlersTests.cs b/TransactionProcessorACL.Tests/Handlers/TransactionHandlersTests.cs index e34a01d..3019928 100644 --- a/TransactionProcessorACL.Tests/Handlers/TransactionHandlersTests.cs +++ b/TransactionProcessorACL.Tests/Handlers/TransactionHandlersTests.cs @@ -9,6 +9,7 @@ using SimpleResults; using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.DataTransferObjects; +using TransactionProcessorACL.DataTransferObjects.Requests; using TransactionProcessorACL.Handlers; using Xunit; @@ -60,4 +61,59 @@ public async Task PerformSaleTransaction_PassesBusinessFieldsIntoCommand() mediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); } + [Fact] + public async Task ResendReceipt_PassesClaimsAndRequestIntoCommand() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("estateId", "1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3"), + new Claim("merchantId", "2C8354B7-B97A-46EA-9AD1-C43F33F7E3C4"), + }, "Bearer")); + + var request = new ResendReceiptRequestMessage + { + Reference = "RCPT-0001", + RecipientEmailAddress = "recipient@example.com" + }; + + TransactionCommands.ResendReceiptCommand? capturedCommand = null; + + var mediator = new Mock(MockBehavior.Strict); + mediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .Callback((command, _) => capturedCommand = (TransactionCommands.ResendReceiptCommand)command) + .ReturnsAsync(Result.Success(new TransactionProcessorACL.Models.ResendReceiptResponse { Success = true, Message = "Receipt resend requested." })); + + await TransactionHandlers.ResendReceipt(mediator.Object, user, request, CancellationToken.None); + + capturedCommand.ShouldNotBeNull(); + capturedCommand!.EstateId.ShouldBe(System.Guid.Parse("1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3")); + capturedCommand.MerchantId.ShouldBe(System.Guid.Parse("2C8354B7-B97A-46EA-9AD1-C43F33F7E3C4")); + capturedCommand.Reference.ShouldBe("RCPT-0001"); + capturedCommand.RecipientEmailAddress.ShouldBe("recipient@example.com"); + mediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task ResendReceipt_InvalidEmail_IsRejected() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("estateId", "1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3"), + new Claim("merchantId", "2C8354B7-B97A-46EA-9AD1-C43F33F7E3C4"), + }, "Bearer")); + + var request = new ResendReceiptRequestMessage + { + Reference = "RCPT-0001", + RecipientEmailAddress = "not-an-email" + }; + + var mediator = new Mock(MockBehavior.Strict); + + var result = await TransactionHandlers.ResendReceipt(mediator.Object, user, request, CancellationToken.None); + + result.ShouldNotBeNull(); + } + } diff --git a/TransactionProcessorACL/Endpoints/TransactionEndpoints.cs b/TransactionProcessorACL/Endpoints/TransactionEndpoints.cs index caca659..858a97d 100644 --- a/TransactionProcessorACL/Endpoints/TransactionEndpoints.cs +++ b/TransactionProcessorACL/Endpoints/TransactionEndpoints.cs @@ -17,6 +17,7 @@ public static class TransactionEndpoints private const string SaleBaseRoute = "/api/saletransactions"; private const string LogonBaseRoute = "/api/logontransactions"; private const string ReconciliationBaseRoute = "/api/reconciliationtransactions"; + private const string TransactionsBaseRoute = "/api/transactions"; public static IEndpointRouteBuilder MapTransactionEndpoints(this IEndpointRouteBuilder app) { @@ -35,6 +36,11 @@ public static IEndpointRouteBuilder MapTransactionEndpoints(this IEndpointRouteB // POST /api/reconciliationtransactions reconciliationGroup.MapPost("", TransactionHandlers.PerformReconciliationTransaction); + var transactionGroup = app.MapGroup(TransactionsBaseRoute).RequireAuthorization().RequireAuthorization(AuthorizationExtensions.PolicyNames.PasswordTokenOnlyPolicy); + + // POST /api/transactions/resendreceipt + transactionGroup.MapPost("resendreceipt", TransactionHandlers.ResendReceipt); + return app; } } diff --git a/TransactionProcessorACL/Handlers/TransactionHandlers.cs b/TransactionProcessorACL/Handlers/TransactionHandlers.cs index f9981c4..07cc54d 100644 --- a/TransactionProcessorACL/Handlers/TransactionHandlers.cs +++ b/TransactionProcessorACL/Handlers/TransactionHandlers.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using System.Net.Mail; using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.DataTransferObjects; using TransactionProcessorACL.DataTransferObjects.Requests; @@ -20,7 +21,7 @@ namespace TransactionProcessorACL.Handlers; /// Static handler methods for transaction processing; callable from minimal endpoints or controllers. /// Returns IResult so endpoints can return directly. /// -public static class TransactionHandlers + public static class TransactionHandlers { public static async Task PerformSaleTransaction(IMediator mediator, ClaimsPrincipal user, @@ -50,24 +51,59 @@ public static async Task PerformLogonTransaction(IMediator mediator, return ResponseFactory.FromResult(logonResponse, ModelFactory.ConvertFrom); } - public static async Task PerformReconciliationTransaction(IMediator mediator, - ClaimsPrincipal user, - ReconciliationRequestMessage transactionRequest, - CancellationToken cancellationToken) + public static async Task PerformReconciliationTransaction(IMediator mediator, + ClaimsPrincipal user, + ReconciliationRequestMessage transactionRequest, + CancellationToken cancellationToken) { Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); if (claimsResult.IsFailed) return ResponseFactory.FromResult(Result.Forbidden()); - TransactionCommands.ProcessReconciliationCommand reconCommand = CreateReconciliationCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); - Result reconResponse = await mediator.Send(reconCommand, cancellationToken); - return ResponseFactory.FromResult(reconResponse, ModelFactory.ConvertFrom); - } + TransactionCommands.ProcessReconciliationCommand reconCommand = CreateReconciliationCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); + Result reconResponse = await mediator.Send(reconCommand, cancellationToken); + return ResponseFactory.FromResult(reconResponse, ModelFactory.ConvertFrom); + } - private static TransactionCommands.ProcessLogonTransactionCommand CreateLogonCommand(Guid estateId, Guid merchantId, LogonTransactionRequestMessage msg) - { - return new TransactionCommands.ProcessLogonTransactionCommand(estateId, merchantId, msg.TransactionDateTime, msg.TransactionNumber, msg.DeviceIdentifier); - } + public static async Task ResendReceipt(IMediator mediator, + ClaimsPrincipal user, + ResendReceiptRequestMessage transactionRequest, + CancellationToken cancellationToken) + { + Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); + if (claimsResult.IsFailed) + return ResponseFactory.FromResult(Result.Forbidden()); + + if (string.IsNullOrWhiteSpace(transactionRequest.Reference)) + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "A transaction reference or receipt reference is required." }); + + if (string.IsNullOrWhiteSpace(transactionRequest.RecipientEmailAddress)) + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is required." }); + + try { + _ = new MailAddress(transactionRequest.RecipientEmailAddress); + } + catch (FormatException) { + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is not valid." }); + } + + TransactionCommands.ResendReceiptCommand resendCommand = new(claimsResult.Data.estateId, + claimsResult.Data.merchantId, + transactionRequest.Reference, + transactionRequest.RecipientEmailAddress); + Result resendResponse = await mediator.Send(resendCommand, cancellationToken); + + if (resendResponse.IsFailed && resendResponse.Status == ResultStatus.NotFound) { + return Results.NotFound(new ResendReceiptResponse { Success = false, Message = resendResponse.Message }); + } + + return ResponseFactory.FromResult(resendResponse, response => response); + } + + private static TransactionCommands.ProcessLogonTransactionCommand CreateLogonCommand(Guid estateId, Guid merchantId, LogonTransactionRequestMessage msg) + { + return new TransactionCommands.ProcessLogonTransactionCommand(estateId, merchantId, msg.TransactionDateTime, msg.TransactionNumber, msg.DeviceIdentifier); + } private static TransactionCommands.ProcessSaleTransactionCommand CreateSaleCommand(Guid estateId, Guid merchantId, SaleTransactionRequestMessage msg) { From 657c2797d162beb3069bbd9d95ecbdca311122cc Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 8 Jul 2026 18:05:02 +0100 Subject: [PATCH 2/3] codacy tweaks --- ...ansactionProcessorACLApplicationService.cs | 203 ++++++++++++------ 1 file changed, 137 insertions(+), 66 deletions(-) diff --git a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs index 8cbb678..8b503c8 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs @@ -518,12 +518,12 @@ public async Task> ResendReceipt(Guid estateId, return Result.Invalid("Recipient email address is not valid."); } - Result resendResult = await this.InvokeResendReceiptAsync(accessTokenResult.Data.AccessToken, - estateId, - merchantId, - reference, - recipientEmailAddress, - cancellationToken); + Result resendResult = await this.InvokeResendReceipt(accessTokenResult.Data.AccessToken, + estateId, + merchantId, + reference, + recipientEmailAddress, + cancellationToken); if (resendResult.IsFailed) { return resendResult; @@ -532,95 +532,127 @@ public async Task> ResendReceipt(Guid estateId, return resendResult; } - private async Task> InvokeResendReceiptAsync(String accessToken, - Guid estateId, - Guid merchantId, - String reference, - String recipientEmailAddress, - CancellationToken cancellationToken) + private async Task> InvokeResendReceipt(String accessToken, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) { - System.Reflection.MethodInfo? method = typeof(ITransactionProcessorClient) - .GetMethods() - .SingleOrDefault(m => string.Equals(m.Name, "ResendEmailReceipt", StringComparison.Ordinal)); - + System.Reflection.MethodInfo? method = FindResendEmailReceiptMethod(); if (method == null) { return Result.Failure("Receipt resend is not supported by the transaction client."); } + object?[]? args = BuildResendReceiptArguments(method, accessToken, estateId, merchantId, reference, recipientEmailAddress, cancellationToken); + if (args == null) { + return Result.Failure("Unable to prepare receipt resend request."); + } + + object? invocationResult = method.Invoke(this.TransactionProcessorClient, args); + if (invocationResult is not Task task) { + return Result.Failure("Receipt resend call did not return a task."); + } + + await task.ConfigureAwait(false); + return MapResendReceiptInvocationResult(task, reference); + } + + private static System.Reflection.MethodInfo? FindResendEmailReceiptMethod() + { + return typeof(ITransactionProcessorClient) + .GetMethods() + .SingleOrDefault(m => string.Equals(m.Name, "ResendEmailReceipt", StringComparison.Ordinal)); + } + + private static object?[]? BuildResendReceiptArguments(System.Reflection.MethodInfo method, + String accessToken, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { System.Reflection.ParameterInfo[] parameters = method.GetParameters(); object?[] args = new object?[parameters.Length]; - bool estateAssigned = false; - bool merchantAssigned = false; - object? request = null; + ResendReceiptInvocationState state = new(); for (int index = 0; index < parameters.Length; index++) { - System.Reflection.ParameterInfo parameter = parameters[index]; - - if (parameter.ParameterType == typeof(String)) { - args[index] = accessToken; - continue; + object? argument = BuildResendReceiptArgument(parameters[index], + state, + accessToken, + estateId, + merchantId, + reference, + recipientEmailAddress, + cancellationToken); + if (argument == ResendReceiptInvocationState.UnableToBuildArgument) { + return null; } - if (parameter.ParameterType == typeof(Guid)) { - if (estateAssigned == false) { - args[index] = estateId; - estateAssigned = true; - } - else if (merchantAssigned == false) { - args[index] = merchantId; - merchantAssigned = true; - } - else { - args[index] = Guid.Empty; - } - - continue; - } + args[index] = argument; + } - if (parameter.ParameterType == typeof(CancellationToken)) { - args[index] = cancellationToken; - continue; - } + return args; + } - request ??= System.Activator.CreateInstance(parameter.ParameterType); - if (request == null) { - return Result.Failure($"Unable to create resend receipt request type '{parameter.ParameterType.FullName}'."); - } + private static object? BuildResendReceiptArgument(System.Reflection.ParameterInfo parameter, + ResendReceiptInvocationState state, + String accessToken, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { + if (parameter.ParameterType == typeof(String)) { + return accessToken; + } - PopulateResendReceiptRequest(request, estateId, merchantId, reference, recipientEmailAddress); - args[index] = request; + if (parameter.ParameterType == typeof(Guid)) { + return state.GetNextGuidArgument(estateId, merchantId); } - object? invocationResult = method.Invoke(this.TransactionProcessorClient, args); - if (invocationResult is not Task task) { - return Result.Failure("Receipt resend call did not return a task."); + if (parameter.ParameterType == typeof(CancellationToken)) { + return cancellationToken; } - await task.ConfigureAwait(false); + return state.GetResendReceiptRequest(parameter.ParameterType, estateId, merchantId, reference, recipientEmailAddress); + } + private static Result MapResendReceiptInvocationResult(Task task, String reference) + { object? taskResult = task.GetType().GetProperty("Result")?.GetValue(task); if (taskResult == null) { return Result.Failure("Receipt resend call returned no result."); } - bool isFailed = (bool)(taskResult.GetType().GetProperty("IsFailed")?.GetValue(taskResult) ?? true); - if (isFailed) { - string? message = taskResult.GetType().GetProperty("Message")?.GetValue(taskResult)?.ToString(); - string? status = taskResult.GetType().GetProperty("Status")?.GetValue(taskResult)?.ToString(); + if (IsFailedResult(taskResult, out String? message, out String? status)) { + return MapResendReceiptFailure(status, message); + } - if (string.Equals(status, "NotFound", StringComparison.OrdinalIgnoreCase)) { - return Result.NotFound(message ?? "Receipt could not be found."); - } + object? data = taskResult.GetType().GetProperty("Data")?.GetValue(taskResult); + return Result.Success(MapResendReceiptResponse(data, reference)); + } - if (string.Equals(status, "Invalid", StringComparison.OrdinalIgnoreCase)) { - return Result.Invalid(message ?? "Receipt resend request was invalid."); - } + private static bool IsFailedResult(object taskResult, out String? message, out String? status) + { + message = taskResult.GetType().GetProperty("Message")?.GetValue(taskResult)?.ToString(); + status = taskResult.GetType().GetProperty("Status")?.GetValue(taskResult)?.ToString(); + return (bool)(taskResult.GetType().GetProperty("IsFailed")?.GetValue(taskResult) ?? true); + } - return Result.Failure(message ?? "Receipt resend failed."); + private static Result MapResendReceiptFailure(String? status, String? message) + { + if (string.Equals(status, "NotFound", StringComparison.OrdinalIgnoreCase)) { + return Result.NotFound(message ?? "Receipt could not be found."); } - object? data = taskResult.GetType().GetProperty("Data")?.GetValue(taskResult); - return Result.Success(MapResendReceiptResponse(data, reference)); + if (string.Equals(status, "Invalid", StringComparison.OrdinalIgnoreCase)) { + return Result.Invalid(message ?? "Receipt resend request was invalid."); + } + + return Result.Failure(message ?? "Receipt resend failed."); } private static void PopulateResendReceiptRequest(object request, @@ -686,6 +718,45 @@ private static ResendReceiptResponse MapResendReceiptResponse(object? data, Stri return property?.GetValue(instance)?.ToString(); } + private sealed class ResendReceiptInvocationState + { + public static readonly object UnableToBuildArgument = new(); + + private bool estateAssigned; + private bool merchantAssigned; + private object? request; + + public object? GetNextGuidArgument(Guid estateId, Guid merchantId) + { + if (this.estateAssigned == false) { + this.estateAssigned = true; + return estateId; + } + + if (this.merchantAssigned == false) { + this.merchantAssigned = true; + return merchantId; + } + + return Guid.Empty; + } + + public object? GetResendReceiptRequest(Type requestType, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress) + { + this.request ??= System.Activator.CreateInstance(requestType); + if (this.request == null) { + return UnableToBuildArgument; + } + + PopulateResendReceiptRequest(this.request, estateId, merchantId, reference, recipientEmailAddress); + return this.request; + } + } + private static ProcessReconciliationResponse CreateProcessReconciliationResponse(ReconciliationResponse reconciliationResponse) { return new ProcessReconciliationResponse From 6d906b925a367c10fd2ca7a8d8f96d9e7ae6ff76 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 8 Jul 2026 18:14:20 +0100 Subject: [PATCH 3/3] more codacy changes --- .../Handlers/TransactionHandlers.cs | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/TransactionProcessorACL/Handlers/TransactionHandlers.cs b/TransactionProcessorACL/Handlers/TransactionHandlers.cs index 07cc54d..cd1a8d5 100644 --- a/TransactionProcessorACL/Handlers/TransactionHandlers.cs +++ b/TransactionProcessorACL/Handlers/TransactionHandlers.cs @@ -29,8 +29,9 @@ public static async Task PerformSaleTransaction(IMediator mediator, CancellationToken cancellationToken) { Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); - if (claimsResult.IsFailed) + if (claimsResult.IsFailed) { return ResponseFactory.FromResult(Result.Forbidden()); + } TransactionCommands.ProcessSaleTransactionCommand saleCommand = CreateSaleCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); Result saleResponse = await mediator.Send(saleCommand, cancellationToken); @@ -43,63 +44,68 @@ public static async Task PerformLogonTransaction(IMediator mediator, CancellationToken cancellationToken) { Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); - if (claimsResult.IsFailed) + if (claimsResult.IsFailed) { return ResponseFactory.FromResult(Result.Forbidden()); + } TransactionCommands.ProcessLogonTransactionCommand logonCommand = CreateLogonCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); Result logonResponse = await mediator.Send(logonCommand, cancellationToken); return ResponseFactory.FromResult(logonResponse, ModelFactory.ConvertFrom); } - public static async Task PerformReconciliationTransaction(IMediator mediator, - ClaimsPrincipal user, - ReconciliationRequestMessage transactionRequest, - CancellationToken cancellationToken) + public static async Task PerformReconciliationTransaction(IMediator mediator, + ClaimsPrincipal user, + ReconciliationRequestMessage transactionRequest, + CancellationToken cancellationToken) { Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); - if (claimsResult.IsFailed) + if (claimsResult.IsFailed) { return ResponseFactory.FromResult(Result.Forbidden()); + } + + TransactionCommands.ProcessReconciliationCommand reconCommand = CreateReconciliationCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); + Result reconResponse = await mediator.Send(reconCommand, cancellationToken); + return ResponseFactory.FromResult(reconResponse, ModelFactory.ConvertFrom); + } - TransactionCommands.ProcessReconciliationCommand reconCommand = CreateReconciliationCommand(claimsResult.Data.estateId, claimsResult.Data.merchantId, transactionRequest); - Result reconResponse = await mediator.Send(reconCommand, cancellationToken); - return ResponseFactory.FromResult(reconResponse, ModelFactory.ConvertFrom); + public static async Task ResendReceipt(IMediator mediator, + ClaimsPrincipal user, + ResendReceiptRequestMessage transactionRequest, + CancellationToken cancellationToken) + { + Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); + if (claimsResult.IsFailed) { + return ResponseFactory.FromResult(Result.Forbidden()); } - public static async Task ResendReceipt(IMediator mediator, - ClaimsPrincipal user, - ResendReceiptRequestMessage transactionRequest, - CancellationToken cancellationToken) - { - Result<(Guid estateId, Guid merchantId)> claimsResult = Helpers.GetRequiredClaims(user); - if (claimsResult.IsFailed) - return ResponseFactory.FromResult(Result.Forbidden()); - - if (string.IsNullOrWhiteSpace(transactionRequest.Reference)) - return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "A transaction reference or receipt reference is required." }); - - if (string.IsNullOrWhiteSpace(transactionRequest.RecipientEmailAddress)) - return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is required." }); - - try { - _ = new MailAddress(transactionRequest.RecipientEmailAddress); - } - catch (FormatException) { - return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is not valid." }); - } - - TransactionCommands.ResendReceiptCommand resendCommand = new(claimsResult.Data.estateId, - claimsResult.Data.merchantId, - transactionRequest.Reference, - transactionRequest.RecipientEmailAddress); - Result resendResponse = await mediator.Send(resendCommand, cancellationToken); - - if (resendResponse.IsFailed && resendResponse.Status == ResultStatus.NotFound) { - return Results.NotFound(new ResendReceiptResponse { Success = false, Message = resendResponse.Message }); - } - - return ResponseFactory.FromResult(resendResponse, response => response); + if (string.IsNullOrWhiteSpace(transactionRequest.Reference)) { + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "A transaction reference or receipt reference is required." }); } + if (string.IsNullOrWhiteSpace(transactionRequest.RecipientEmailAddress)) { + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is required." }); + } + + try { + _ = new MailAddress(transactionRequest.RecipientEmailAddress); + } + catch (FormatException) { + return Results.BadRequest(new ResendReceiptResponse { Success = false, Message = "Recipient email address is not valid." }); + } + + TransactionCommands.ResendReceiptCommand resendCommand = new(claimsResult.Data.estateId, + claimsResult.Data.merchantId, + transactionRequest.Reference, + transactionRequest.RecipientEmailAddress); + Result resendResponse = await mediator.Send(resendCommand, cancellationToken); + + if (resendResponse.IsFailed && resendResponse.Status == ResultStatus.NotFound) { + return Results.NotFound(new ResendReceiptResponse { Success = false, Message = resendResponse.Message }); + } + + return ResponseFactory.FromResult(resendResponse, response => response); + } + private static TransactionCommands.ProcessLogonTransactionCommand CreateLogonCommand(Guid estateId, Guid merchantId, LogonTransactionRequestMessage msg) { return new TransactionCommands.ProcessLogonTransactionCommand(estateId, merchantId, msg.TransactionDateTime, msg.TransactionNumber, msg.DeviceIdentifier);