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..8b503c8 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs @@ -492,6 +492,271 @@ 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.InvokeResendReceipt(accessTokenResult.Data.AccessToken, + estateId, + merchantId, + reference, + recipientEmailAddress, + cancellationToken); + + if (resendResult.IsFailed) { + return resendResult; + } + + return resendResult; + } + + private async Task> InvokeResendReceipt(String accessToken, + Guid estateId, + Guid merchantId, + String reference, + String recipientEmailAddress, + CancellationToken cancellationToken) + { + 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]; + ResendReceiptInvocationState state = new(); + + for (int index = 0; index < parameters.Length; index++) { + object? argument = BuildResendReceiptArgument(parameters[index], + state, + accessToken, + estateId, + merchantId, + reference, + recipientEmailAddress, + cancellationToken); + if (argument == ResendReceiptInvocationState.UnableToBuildArgument) { + return null; + } + + args[index] = argument; + } + + return args; + } + + 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; + } + + if (parameter.ParameterType == typeof(Guid)) { + return state.GetNextGuidArgument(estateId, merchantId); + } + + if (parameter.ParameterType == typeof(CancellationToken)) { + return cancellationToken; + } + + 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."); + } + + if (IsFailedResult(taskResult, out String? message, out String? status)) { + return MapResendReceiptFailure(status, message); + } + + object? data = taskResult.GetType().GetProperty("Data")?.GetValue(taskResult); + return Result.Success(MapResendReceiptResponse(data, reference)); + } + + 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); + } + + private static Result MapResendReceiptFailure(String? status, String? message) + { + 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."); + } + + 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 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 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..cd1a8d5 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, @@ -28,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); @@ -42,8 +44,9 @@ 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); @@ -56,19 +59,58 @@ public static async Task PerformReconciliationTransaction(IMediator med 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); } - private static TransactionCommands.ProcessLogonTransactionCommand CreateLogonCommand(Guid estateId, Guid merchantId, LogonTransactionRequestMessage msg) + public static async Task ResendReceipt(IMediator mediator, + ClaimsPrincipal user, + ResendReceiptRequestMessage transactionRequest, + CancellationToken cancellationToken) { - return new TransactionCommands.ProcessLogonTransactionCommand(estateId, merchantId, msg.TransactionDateTime, msg.TransactionNumber, msg.DeviceIdentifier); + 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) { return new TransactionCommands.ProcessSaleTransactionCommand(