From 261485b6cf625bb6ef9e19bd960070c8ca8f0f53 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Tue, 21 Jul 2026 19:48:24 +0100 Subject: [PATCH 1/2] Add AgencyBanking deposit support and tests Implement deposit handling in AgencyBankingProxy with validation and response mapping. Add and update tests for deposit scenarios. Update SpecFlow extension, feature files, and generated code for deposit flows. Adjust logging to Info level. Ensure end-to-end processing and validation of deposit transactions. --- .../AgencyBankingProxyTests.cs | 145 ++++++++++++++++++ .../AgencyBanking/AgencyBankingProxy.cs | 77 ++++++++-- .../AgencyBanking/BalanceEnquiryResponse.cs | 23 +++ .../SpecflowExtensions.cs | 14 +- .../Features/SaleTransactionFeature.feature | 62 ++++---- .../SaleTransactionFeature.feature.cs | 126 ++++++++++----- TransactionProcessor/nlog.config | 2 +- 7 files changed, 361 insertions(+), 88 deletions(-) diff --git a/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs b/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs index 05ad3ec9..21e4692c 100644 --- a/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs @@ -194,6 +194,151 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_BalanceEnquirySuccess_Re requestJson.RootElement.GetProperty("accountNumber").GetString().ShouldBe("123456789"); } + [Fact] + public async Task AgencyBankingProxy_ProcessSaleMessage_DepositFailure_ReturnsInvalidResult() + { + HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("deposit rejected") + }; + + HttpRequestMessage capturedRequest = null; + HttpClient httpClient = SetupMockHttpClient(responseMessage, request => capturedRequest = request); + AgencyBankingConfiguration configuration = CreateConfiguration(); + IOperatorProxy proxy = new AgencyBankingProxy(httpClient, configuration); + + Dictionary metadata = new Dictionary + { + { "AgencyBankingMessageType", "deposit" }, + { "AgencyBankingAccountNumber", "123456789" }, + { "AgencyBankingAmount", "250.75" } + }; + + var result = await proxy.ProcessSaleMessage(TestData.TransactionId, + TestData.OperatorId, + TestData.Merchant, + TestData.TransactionDateTime, + TestData.TransactionReference, + metadata, + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + result.Status.ShouldBe(ResultStatus.Invalid); + result.Message.ShouldBe("Failed to process deposit deposit rejected"); + + capturedRequest.ShouldNotBeNull(); + capturedRequest.Method.ShouldBe(HttpMethod.Post); + capturedRequest.RequestUri.ShouldBe(new Uri("http://localhost/transactions/deposit")); + + String requestBody = await capturedRequest.Content.ReadAsStringAsync(); + using JsonDocument requestJson = JsonDocument.Parse(requestBody); + requestJson.RootElement.GetProperty("agentId").GetString().ShouldBe(TestData.Merchant.MerchantId.ToString()); + requestJson.RootElement.GetProperty("accountNumber").GetString().ShouldBe("123456789"); + requestJson.RootElement.GetProperty("amount").GetDecimal().ShouldBe(250.75m); + requestJson.RootElement.GetProperty("transactionId").GetString().ShouldBe(TestData.TransactionId.ToString()); + } + + [Fact] + public async Task AgencyBankingProxy_ProcessSaleMessage_DepositMissingAmount_ReturnsInvalidResult() + { + HttpClient httpClient = SetupMockHttpClient(new HttpResponseMessage(HttpStatusCode.OK)); + AgencyBankingConfiguration configuration = CreateConfiguration(); + IOperatorProxy proxy = new AgencyBankingProxy(httpClient, configuration); + + Dictionary metadata = new Dictionary + { + { "AgencyBankingMessageType", "deposit" }, + { "AgencyBankingAccountNumber", "123456789" } + }; + + var result = await proxy.ProcessSaleMessage(TestData.TransactionId, + TestData.OperatorId, + TestData.Merchant, + TestData.TransactionDateTime, + TestData.TransactionReference, + metadata, + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + result.Status.ShouldBe(ResultStatus.Invalid); + result.Message.ShouldBe("AgencyBankingAmount - Amount is a required field for this transaction type"); + } + + [Fact] + public async Task AgencyBankingProxy_ProcessSaleMessage_DepositInvalidAmount_ReturnsInvalidResult() + { + Mock handlerMock = new Mock(MockBehavior.Strict); + HttpClient httpClient = new HttpClient(handlerMock.Object); + AgencyBankingConfiguration configuration = CreateConfiguration(); + IOperatorProxy proxy = new AgencyBankingProxy(httpClient, configuration); + + Dictionary metadata = new Dictionary + { + { "AgencyBankingMessageType", "deposit" }, + { "AgencyBankingAccountNumber", "123456789" }, + { "AgencyBankingAmount", "not-a-number" } + }; + + var result = await proxy.ProcessSaleMessage(TestData.TransactionId, + TestData.OperatorId, + TestData.Merchant, + TestData.TransactionDateTime, + TestData.TransactionReference, + metadata, + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + result.Status.ShouldBe(ResultStatus.Invalid); + result.Message.ShouldBe("AgencyBankingAmount - Amount is not a valid decimal value"); + } + + [Fact] + public async Task AgencyBankingProxy_ProcessSaleMessage_DepositSuccess_ReturnsMappedOperatorResponse() + { + HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"ResponseCode\":\"0000\",\"TransactionId\":\"abc123\"}") + }; + + HttpRequestMessage capturedRequest = null; + HttpClient httpClient = SetupMockHttpClient(responseMessage, request => capturedRequest = request); + AgencyBankingConfiguration configuration = CreateConfiguration(); + IOperatorProxy proxy = new AgencyBankingProxy(httpClient, configuration); + + Dictionary metadata = new Dictionary + { + { "AgencyBankingMessageType", "deposit" }, + { "AgencyBankingAccountNumber", "123456789" }, + { "AgencyBankingAmount", "250.75" } + }; + + var result = await proxy.ProcessSaleMessage(TestData.TransactionId, + TestData.OperatorId, + TestData.Merchant, + TestData.TransactionDateTime, + TestData.TransactionReference, + metadata, + CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldNotBeNull(); + result.Data.IsSuccessful.ShouldBeTrue(); + result.Data.ResponseCode.ShouldBe("0000"); + result.Data.ResponseMessage.ShouldBe("SUCCESS"); + result.Data.AdditionalTransactionResponseMetadata.ShouldBeEmpty(); + + capturedRequest.ShouldNotBeNull(); + capturedRequest.Method.ShouldBe(HttpMethod.Post); + capturedRequest.RequestUri.ShouldBe(new Uri("http://localhost/transactions/deposit")); + + String requestBody = await capturedRequest.Content.ReadAsStringAsync(); + using JsonDocument requestJson = JsonDocument.Parse(requestBody); + requestJson.RootElement.GetProperty("agentId").GetString().ShouldBe(TestData.Merchant.MerchantId.ToString()); + requestJson.RootElement.GetProperty("accountNumber").GetString().ShouldBe("123456789"); + requestJson.RootElement.GetProperty("amount").GetDecimal().ShouldBe(250.75m); + requestJson.RootElement.GetProperty("transactionId").GetString().ShouldBe(TestData.TransactionId.ToString()); + } + private static AgencyBankingConfiguration CreateConfiguration() { return new AgencyBankingConfiguration diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs index 39871a0f..cfac4f48 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs @@ -50,15 +50,7 @@ public async Task> ProcessSaleMessage(Guid transactionI Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] missing AgencyBankingMessageType"); return Result.Invalid("AgencyBankingMessageType - Message Type is a required field for this transaction type"); } - - //// Check the meta data for the agent id - //String agentId = additionalTransactionMetadata.ExtractFieldFromMetadata("AgencyBankingAgentId"); - - //if (String.IsNullOrEmpty(agentId)) - //{ - // return Result.Invalid("AgencyBankingAgentId - Agent Id is a required field for this transaction type"); - //} - + // Check the meta data for the account number String accountNumber = additionalTransactionMetadata.ExtractFieldFromMetadata("AgencyBankingAccountNumber"); @@ -70,13 +62,72 @@ public async Task> ProcessSaleMessage(Guid transactionI Logger.LogInformation($"AgencyBanking sale request metadata resolved. TransactionId=[{transactionId}], MessageType=[{messageType}], AccountNumber=[{accountNumber}]"); - if (messageType == "balanceenquiry") + Result result = messageType switch { + "balanceenquiry" => await ProcessBalanceEnquiry(transactionId, merchant.MerchantId.ToString(), accountNumber, cancellationToken), + "deposit" => await ProcessDeposit(transactionId, merchant.MerchantId.ToString(), accountNumber, additionalTransactionMetadata, cancellationToken), + _ => Result.Invalid($"AgencyBankingMessageType - Message Type {messageType} is not supported for this transaction type") + }; + + + if (result.Status == ResultStatus.Invalid) { + Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] unsupported message type [{messageType}]"); + } + + return result; + } + + private async Task> ProcessDeposit(Guid transactionId, + String agentId, + String accountNumber, + Dictionary additionalTransactionMetadata, + CancellationToken cancellationToken) { + + HttpRequestMessage request = new(HttpMethod.Post, $"{this.Configuration.Url}/transactions/deposit"); + // Extract required data from metadata + String amountValue = additionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); + + if (String.IsNullOrWhiteSpace(amountValue)) + { + Logger.LogWarning($"AgencyBanking deposit request rejected. TransactionId=[{transactionId}] missing Amount"); + return Result.Invalid("Amount - Amount is a required field for this transaction type"); + } + + if (Decimal.TryParse(amountValue, out Decimal amount) == false) + { + Logger.LogWarning($"AgencyBanking deposit request rejected. TransactionId=[{transactionId}] invalid Amount [{amountValue}]"); + return Result.Invalid("Amount - Amount is not a valid decimal value"); + } + + DepositRequest body = new() { AgentId = agentId, AccountNumber = accountNumber, Amount = amount, TransactionId = transactionId.ToString() }; + + String requestBody = StringSerialiser.Serialise(body, SerialiserOptions); + request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + + Logger.LogWarning($"AgencyBanking deposit request starting. TransactionId=[{transactionId}], AgentId=[{agentId}], AccountNumber=[{accountNumber}], RequestUri=[{request.RequestUri}], RequestBody=[{requestBody}]"); + + HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); + String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); + + Logger.LogWarning($"AgencyBanking deposit response received. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ContentLength=[{responseContent?.Length ?? 0}]"); + + if (response.IsSuccessStatusCode == false) { - return await ProcessBalanceEnquiry(transactionId, merchant.MerchantId.ToString(), accountNumber, cancellationToken); + Logger.LogWarning($"AgencyBanking deposit failed. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ResponseBody=[{responseContent}]"); + return Result.Invalid($"Failed to process deposit {responseContent}"); } - Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] unsupported message type [{messageType}]"); - return Result.Invalid($"AgencyBankingMessageType - Message Type {messageType} is not supported for this transaction type"); + TransactionResult balanceEnquiryResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); + Logger.LogWarning($"AgencyBanking deposit completed. TransactionId=[{transactionId}], ResponseCode=[{balanceEnquiryResponse.ResponseCode}]"); + + OperatorResponse operatorResponse = new() + { + IsSuccessful = true, + ResponseCode = "0000", + ResponseMessage = "SUCCESS", + AdditionalTransactionResponseMetadata = new Dictionary() + }; + + return Result.Success(operatorResponse); } private async Task> ProcessBalanceEnquiry(Guid transactionId, diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/BalanceEnquiryResponse.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/BalanceEnquiryResponse.cs index 8dc58b3c..712cc974 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/BalanceEnquiryResponse.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/BalanceEnquiryResponse.cs @@ -22,4 +22,27 @@ public class BalanceEnquiryResponse public decimal AvailableBalance { get; set; } } + + public class DepositRequest + { + public string TransactionId { get; set; } + public string CustomerId { get; set; } + + public string AgentId { get; set; } + + public string AccountNumber { get; set; } + + public decimal Amount { get; set; } + public string Currency { get; set; } + public String Channel { get; set; } + public String Narration { get; set; } + public String ReferenceNumber { get; set; } + } + + public class TransactionResult + { + public string ResponseCode { get; set; } = ""; + + public string TransactionId { get; set; } = ""; + } } diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs index f4f3fc01..4247ab9e 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs @@ -574,7 +574,7 @@ public static List ToAutomaticDepositRequests(this DataTableRows tableRo "Voucher" => BuildVoucherTransactionMetaData(recipientEmail, recipientMobile, transactionAmount), "PataPawa PostPay" => BuildPataPawaPostPayMetaData(messageType, accountNumber, recipientMobile, customerName, transactionAmount), "PataPawa PrePay" => BuildPataPawaPrePayMetaData(messageType, meterNumber, customerName, transactionAmount), - "AgencyBanking" => BuildAgencyBankingMetaData(messageType, accountNumber), + "AgencyBanking" => BuildAgencyBankingMetaData(messageType, accountNumber, transactionAmount), _ => BuildMobileTopupMetaData(transactionAmount, customerAccountNumber) }; @@ -653,11 +653,19 @@ private static Dictionary BuildMobileTopupMetaData(Decimal trans }; } - private static Dictionary BuildAgencyBankingMetaData(String messageType, String accountNumber){ - return new Dictionary{ + private static Dictionary BuildAgencyBankingMetaData(String messageType, String accountNumber, Decimal amount){ + + // TODO: make this a pattern match with sub functions?? + var metadata = new Dictionary{ { "AgencyBankingMessageType", messageType }, { "AgencyBankingAccountNumber", accountNumber } }; + + if (amount > 0) + { + metadata.Add("Amount", amount.ToString()); + } + return metadata; } private static Dictionary BuildPataPawaPostPayMetaData(String messageType, diff --git a/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature b/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature index 22d9f7fc..5aa330bc 100644 --- a/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature +++ b/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature @@ -88,6 +88,7 @@ Background: | Test Estate 1 | PataPawa PostPay | PataPawa PostPay Contract | Post Pay Bill Pay | Bill Pay (Post) | | BillPayment | | Test Estate 1 | PataPawa PrePay | PataPawa PrePay Contract | Pre Pay Bill Pay | Bill Pay (Pre) | | BillPayment | | Test Estate 1 | AgencyBanking | AgencyBanking Contract | Balance Enquiry | Balance Enquiry | | MobileTopup | + | Test Estate 1 | AgencyBanking | AgencyBanking Contract | Deposit | Deposit | | MobileTopup | When I add the following Transaction Fees | EstateName | OperatorName | ContractDescription | ProductName | CalculationType | FeeDescription | Value | @@ -134,7 +135,7 @@ Background: Given I make the following manual merchant deposits | Reference | Amount | DateTime | MerchantName | EstateName | - | Deposit1 | 265.00 | Today | Test Merchant 1 | Test Estate 1 | + | Deposit1 | 365.00 | Today | Test Merchant 1 | Test Estate 1 | | Deposit1 | 110.00 | Today | Test Merchant 2 | Test Estate 1 | | Deposit1 | 110.00 | Today | Test Merchant 3 | Test Estate 1 | | Deposit1 | 100.00 | Today | Test Merchant 4 | Test Estate 1 | @@ -176,15 +177,16 @@ Scenario: Sale Transactions | Today | 9 | Sale | 2 | Test Merchant 1 | 123456780 | Test Estate 1 | PataPawa PostPay | 20.00 | | | PataPawa PostPay Contract | Post Pay Bill Pay | test@recipient.co.uk | 123456789 | ProcessBill | 12345678 | Mr Test Customer | | | Today | 10 | Sale | 2 | Test Merchant 1 | 123456780 | Test Estate 1 | PataPawa PrePay | 0.00 | | | PataPawa PrePay Contract | Pre Pay Bill Pay | test@recipient.co.uk | | meter | | | 00000001 | | Today | 11 | Sale | 2 | Test Merchant 1 | 123456780 | Test Estate 1 | PataPawa PrePay | 25.00 | | | PataPawa PrePay Contract | Pre Pay Bill Pay | test@recipient.co.uk | | vend | 00000001 | Customer 1 | 00000001 | - | Today | 12 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | AgencyBanking | 0.00 | | | AgencyBanking Contract | Balance Enquiry | balanceenquiry | 12345678 | balanceenquiry | 12345678 | | | - | Today | 13 | Sale | 1 | Test Merchant 1 | 123456781 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | - | Today | 14 | Sale | 1 | Test Merchant 1 | 123456780 | InvalidEstate | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | - | Today | 15 | Sale | 1 | InvalidMerchant | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | - | Today | 16 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | EmptyContract | Variable Topup | | | | | | | - | Today | 17 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | InvalidContract | Variable Topup | | | | | | | - | Today | 18 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | EmptyProduct | | | | | | | - | Today | 19 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | InvalidProduct | | | | | | | - | Today | 20 | Sale | 1 | Test Merchant 4 | 123456783 | Test Estate 1 | Safaricom | 300.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | + | Today | 12 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | AgencyBanking | 0.00 | | | AgencyBanking Contract | Balance Enquiry | | 12345678 | balanceenquiry | 12345678 | | | + | Today | 13 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | AgencyBanking | 100.00 | | | AgencyBanking Contract | Deposit | | 12345678 | deposit | 12345678 | | | + | Today | 100 | Sale | 1 | Test Merchant 1 | 123456781 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | + | Today | 101 | Sale | 1 | Test Merchant 1 | 123456780 | InvalidEstate | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | + | Today | 102 | Sale | 1 | InvalidMerchant | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | + | Today | 103 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | EmptyContract | Variable Topup | | | | | | | + | Today | 104 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | InvalidContract | Variable Topup | | | | | | | + | Today | 105 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | EmptyProduct | | | | | | | + | Today | 106 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | InvalidProduct | | | | | | | + | Today | 107 | Sale | 1 | Test Merchant 4 | 123456783 | Test Estate 1 | Safaricom | 300.00 | 123456789 | testcustomer@customer.co.uk | Safaricom Contract | Variable Topup | | | | | | | Then transaction response should contain the following information | EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage | @@ -200,28 +202,30 @@ Scenario: Sale Transactions | Test Estate 1 | Test Merchant 1 | 10 | 0000 | SUCCESS | | Test Estate 1 | Test Merchant 1 | 11 | 0000 | SUCCESS | | Test Estate 1 | Test Merchant 1 | 12 | 0000 | SUCCESS | - | Test Estate 1 | Test Merchant 1 | 13 | 1000 | Device Identifier 123456781 not valid for Merchant Test Merchant 1 | - | InvalidEstate | Test Merchant 1 | 14 | 1001 | Estate Id [79902550-64df-4491-b0c1-4e78943928a3] is not a valid estate | - | Test Estate 1 | InvalidMerchant | 15 | 1002 | Merchant Id [d59320fa-4c3e-4900-a999-483f6a10c69a] is not a valid merchant for estate [Test Estate 1] | - | Test Estate 1 | Test Merchant 1 | 16 | 1012 | Contract Id [00000000-0000-0000-0000-000000000000] must be set for a sale transaction | - | Test Estate 1 | Test Merchant 1 | 17 | 1015 | Contract Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test Merchant 1] | - | Test Estate 1 | Test Merchant 1 | 18 | 1013 | Product Id [00000000-0000-0000-0000-000000000000] must be set for a sale transaction | - | Test Estate 1 | Test Merchant 1 | 19 | 1016 | Product Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test Merchant 1] | - | Test Estate 1 | Test Merchant 4 | 20 | 1009 | Merchant [Test Merchant 4] does not have enough credit available [100.00] to perform transaction amount [300.00] | + | Test Estate 1 | Test Merchant 1 | 13 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 100 | 1000 | Device Identifier 123456781 not valid for Merchant Test Merchant 1 | + | InvalidEstate | Test Merchant 1 | 101 | 1001 | Estate Id [79902550-64df-4491-b0c1-4e78943928a3] is not a valid estate | + | Test Estate 1 | InvalidMerchant | 102 | 1002 | Merchant Id [d59320fa-4c3e-4900-a999-483f6a10c69a] is not a valid merchant for estate [Test Estate 1] | + | Test Estate 1 | Test Merchant 1 | 103 | 1012 | Contract Id [00000000-0000-0000-0000-000000000000] must be set for a sale transaction | + | Test Estate 1 | Test Merchant 1 | 104 | 1015 | Contract Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test Merchant 1] | + | Test Estate 1 | Test Merchant 1 | 105 | 1013 | Product Id [00000000-0000-0000-0000-000000000000] must be set for a sale transaction | + | Test Estate 1 | Test Merchant 1 | 106 | 1016 | Product Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test Merchant 1] | + | Test Estate 1 | Test Merchant 4 | 107 | 1009 | Merchant [Test Merchant 4] does not have enough credit available [100.00] to perform transaction amount [300.00] | Then the following entries appear in the merchants balance history for estate 'Test Estate 1' and merchant 'Test Merchant 1' | DateTime | Reference | EntryType | In | Out | ChangeAmount | Balance | - | Today | Merchant Deposit | C | 240.00 | 0.00 | 265.00 | 230.00 | - | Today | Transaction Completed | D | 0.00 | 110.00 | 110.00 | 130.00 | - | Today | Transaction Completed | D | 0.00 | 90.00 | 90.00 | 30.00 | - | Today | Transaction Completed | D | 0.00 | 10.00 | 10.00 | 20.00 | - | Today | Transaction Completed | D | 0.00 | 20.00 | 20.00 | 20.00 | - | Today | Transaction Completed | D | 0.00 | 20.00 | 25.00 | 20.00 | - | Today | Transaction Fee Processed | C | 0.00 | 0.55 | 0.55 | 20.00 | - | Today | Transaction Fee Processed | C | 0.00 | 0.45 | 0.45 | 20.00 | - | Today | Transaction Fee Processed | C | 0.00 | 0.01 | 0.10 | 20.00 | - | Today | Transaction Fee Processed | C | 0.00 | 0.01 | 0.10 | 20.00 | - | Today | Opening Balance | C | 0.00 | 0.00 | 0.00 | 20.00 | + | Today | Merchant Deposit | C | 365.00 | 0.00 | 365.00 | 365.00 | + | Today | Transaction Completed | D | 0.00 | 110.00 | 110.00 | 255.00 | + | Today | Transaction Completed | D | 0.00 | 90.00 | 90.00 | 165.00 | + | Today | Transaction Completed | D | 0.00 | 10.00 | 10.00 | 155.00 | + | Today | Transaction Completed | D | 0.00 | 20.00 | 20.00 | 135.00 | + | Today | Transaction Completed | D | 0.00 | 100.00 | 100.00 | 35.00 | + | Today | Transaction Completed | D | 0.00 | 25.00 | 25.00 | 35.00 | + | Today | Transaction Fee Processed | C | 0.00 | 0.55 | 0.55 | 35.55 | + | Today | Transaction Fee Processed | C | 0.00 | 0.45 | 0.45 | 36.00 | + | Today | Transaction Fee Processed | C | 0.00 | 0.01 | 0.10 | 36.10 | + | Today | Transaction Fee Processed | C | 0.00 | 0.01 | 0.10 | 36.10 | + | Today | Opening Balance | C | 0.00 | 0.00 | 0.00 | 36.10 | Then the following entries appear in the merchants balance history for estate 'Test Estate 1' and merchant 'Test Merchant 2' | DateTime | Reference | EntryType | In | Out | ChangeAmount | Balance | diff --git a/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature.cs b/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature.cs index 15df35ec..2db44efa 100644 --- a/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature.cs +++ b/TransactionProcessor.IntegrationTests/Features/SaleTransactionFeature.feature.cs @@ -397,6 +397,14 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Balance Enquiry", "", "MobileTopup"}); + table15.AddRow(new string[] { + "Test Estate 1", + "AgencyBanking", + "AgencyBanking Contract", + "Deposit", + "Deposit", + "", + "MobileTopup"}); #line 84 await testRunner.WhenAsync("I create the following Products", ((string)(null)), table15, "When "); #line hidden @@ -432,7 +440,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Percentage", "Merchant Commission", "0.50"}); -#line 92 +#line 93 await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table16, "When "); #line hidden global::Reqnroll.Table table17 = new global::Reqnroll.Table(new string[] { @@ -490,7 +498,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "testcontact4@merchant4.co.uk", "Test Estate 1", "true"}); -#line 98 +#line 99 await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table17, "Given "); #line hidden global::Reqnroll.Table table18 = new global::Reqnroll.Table(new string[] { @@ -619,7 +627,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "00000004", "10000004", "Test Estate 1"}); -#line 105 +#line 106 await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table18, "Given "); #line hidden global::Reqnroll.Table table19 = new global::Reqnroll.Table(new string[] { @@ -642,7 +650,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "123456783", "Test Merchant 4", "Test Estate 1"}); -#line 128 +#line 129 await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table19, "Given "); #line hidden global::Reqnroll.Table table20 = new global::Reqnroll.Table(new string[] { @@ -653,7 +661,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "EstateName"}); table20.AddRow(new string[] { "Deposit1", - "265.00", + "365.00", "Today", "Test Merchant 1", "Test Estate 1"}); @@ -675,7 +683,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Today", "Test Merchant 4", "Test Estate 1"}); -#line 135 +#line 136 await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table20, "Given "); #line hidden global::Reqnroll.Table table21 = new global::Reqnroll.Table(new string[] { @@ -750,7 +758,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 1", "Test Merchant 4", "PataPawa PrePay Contract"}); -#line 142 +#line 143 await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table21, "When "); #line hidden } @@ -774,7 +782,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Sale Transactions", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); string[] tagsOfRule = ((string[])(null)); global::Reqnroll.RuleInfo ruleInfo = null; -#line 164 +#line 165 this.ScenarioInitialize(scenarioInfo, ruleInfo); #line hidden if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) @@ -1041,7 +1049,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "AgencyBanking Contract", "Balance Enquiry", - "balanceenquiry", + "", "12345678", "balanceenquiry", "12345678", @@ -1053,6 +1061,26 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Sale", "1", "Test Merchant 1", + "123456780", + "Test Estate 1", + "AgencyBanking", + "100.00", + "", + "", + "AgencyBanking Contract", + "Deposit", + "", + "12345678", + "deposit", + "12345678", + "", + ""}); + table22.AddRow(new string[] { + "Today", + "100", + "Sale", + "1", + "Test Merchant 1", "123456781", "Test Estate 1", "Safaricom", @@ -1069,7 +1097,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "14", + "101", "Sale", "1", "Test Merchant 1", @@ -1089,7 +1117,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "15", + "102", "Sale", "1", "InvalidMerchant", @@ -1109,7 +1137,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "16", + "103", "Sale", "1", "Test Merchant 1", @@ -1129,7 +1157,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "17", + "104", "Sale", "1", "Test Merchant 1", @@ -1149,7 +1177,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "18", + "105", "Sale", "1", "Test Merchant 1", @@ -1169,7 +1197,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "19", + "106", "Sale", "1", "Test Merchant 1", @@ -1189,7 +1217,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and ""}); table22.AddRow(new string[] { "Today", - "20", + "107", "Sale", "1", "Test Merchant 4", @@ -1207,7 +1235,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "", ""}); -#line 166 +#line 167 await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table22, "When "); #line hidden global::Reqnroll.Table table23 = new global::Reqnroll.Table(new string[] { @@ -1292,57 +1320,63 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 1", "Test Merchant 1", "13", + "0000", + "SUCCESS"}); + table23.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "100", "1000", "Device Identifier 123456781 not valid for Merchant Test Merchant 1"}); table23.AddRow(new string[] { "InvalidEstate", "Test Merchant 1", - "14", + "101", "1001", "Estate Id [79902550-64df-4491-b0c1-4e78943928a3] is not a valid estate"}); table23.AddRow(new string[] { "Test Estate 1", "InvalidMerchant", - "15", + "102", "1002", "Merchant Id [d59320fa-4c3e-4900-a999-483f6a10c69a] is not a valid merchant for es" + "tate [Test Estate 1]"}); table23.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", - "16", + "103", "1012", "Contract Id [00000000-0000-0000-0000-000000000000] must be set for a sale transac" + "tion"}); table23.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", - "17", + "104", "1015", "Contract Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test M" + "erchant 1]"}); table23.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", - "18", + "105", "1013", "Product Id [00000000-0000-0000-0000-000000000000] must be set for a sale transact" + "ion"}); table23.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", - "19", + "106", "1016", "Product Id [934d8164-f36a-448e-b27b-4d671d41d180] not valid for Merchant [Test Me" + "rchant 1]"}); table23.AddRow(new string[] { "Test Estate 1", "Test Merchant 4", - "20", + "107", "1009", "Merchant [Test Merchant 4] does not have enough credit available [100.00] to perf" + "orm transaction amount [300.00]"}); -#line 189 +#line 191 await testRunner.ThenAsync("transaction response should contain the following information", ((string)(null)), table23, "Then "); #line hidden global::Reqnroll.Table table24 = new global::Reqnroll.Table(new string[] { @@ -1357,10 +1391,10 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Today", "Merchant Deposit", "C", - "240.00", + "365.00", "0.00", - "265.00", - "230.00"}); + "365.00", + "365.00"}); table24.AddRow(new string[] { "Today", "Transaction Completed", @@ -1368,7 +1402,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "110.00", "110.00", - "130.00"}); + "255.00"}); table24.AddRow(new string[] { "Today", "Transaction Completed", @@ -1376,7 +1410,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "90.00", "90.00", - "30.00"}); + "165.00"}); table24.AddRow(new string[] { "Today", "Transaction Completed", @@ -1384,7 +1418,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "10.00", "10.00", - "20.00"}); + "155.00"}); table24.AddRow(new string[] { "Today", "Transaction Completed", @@ -1392,15 +1426,23 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "20.00", "20.00", - "20.00"}); + "135.00"}); + table24.AddRow(new string[] { + "Today", + "Transaction Completed", + "D", + "0.00", + "100.00", + "100.00", + "35.00"}); table24.AddRow(new string[] { "Today", "Transaction Completed", "D", "0.00", - "20.00", "25.00", - "20.00"}); + "25.00", + "35.00"}); table24.AddRow(new string[] { "Today", "Transaction Fee Processed", @@ -1408,7 +1450,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.55", "0.55", - "20.00"}); + "35.55"}); table24.AddRow(new string[] { "Today", "Transaction Fee Processed", @@ -1416,7 +1458,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.45", "0.45", - "20.00"}); + "36.00"}); table24.AddRow(new string[] { "Today", "Transaction Fee Processed", @@ -1424,7 +1466,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.01", "0.10", - "20.00"}); + "36.10"}); table24.AddRow(new string[] { "Today", "Transaction Fee Processed", @@ -1432,7 +1474,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.01", "0.10", - "20.00"}); + "36.10"}); table24.AddRow(new string[] { "Today", "Opening Balance", @@ -1440,8 +1482,8 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.00", "0.00", - "20.00"}); -#line 212 + "36.10"}); +#line 215 await testRunner.ThenAsync("the following entries appear in the merchants balance history for estate \'Test Es" + "tate 1\' and merchant \'Test Merchant 1\'", ((string)(null)), table24, "Then "); #line hidden @@ -1493,7 +1535,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.00", "20.00"}); -#line 226 +#line 230 await testRunner.ThenAsync("the following entries appear in the merchants balance history for estate \'Test Es" + "tate 1\' and merchant \'Test Merchant 2\'", ((string)(null)), table25, "Then "); #line hidden @@ -1545,7 +1587,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0.00", "0.00", "20.00"}); -#line 234 +#line 238 await testRunner.ThenAsync("the following entries appear in the merchants balance history for estate \'Test Es" + "tate 1\' and merchant \'Test Merchant 3\'", ((string)(null)), table26, "Then "); #line hidden diff --git a/TransactionProcessor/nlog.config b/TransactionProcessor/nlog.config index 1ec67c78..96828f11 100644 --- a/TransactionProcessor/nlog.config +++ b/TransactionProcessor/nlog.config @@ -33,7 +33,7 @@ s - + From eb9dff91e159ada8ece7552e278bf670b2566fed Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Tue, 21 Jul 2026 19:56:03 +0100 Subject: [PATCH 2/2] fix test failures --- .../OperatorInterfaces/AgencyBankingProxyTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs b/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs index 21e4692c..d60efced 100644 --- a/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/OperatorInterfaces/AgencyBankingProxyTests.cs @@ -211,7 +211,7 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_DepositFailure_ReturnsIn { { "AgencyBankingMessageType", "deposit" }, { "AgencyBankingAccountNumber", "123456789" }, - { "AgencyBankingAmount", "250.75" } + { "Amount", "250.75" } }; var result = await proxy.ProcessSaleMessage(TestData.TransactionId, @@ -261,7 +261,7 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_DepositMissingAmount_Ret result.IsFailed.ShouldBeTrue(); result.Status.ShouldBe(ResultStatus.Invalid); - result.Message.ShouldBe("AgencyBankingAmount - Amount is a required field for this transaction type"); + result.Message.ShouldBe("Amount - Amount is a required field for this transaction type"); } [Fact] @@ -276,7 +276,7 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_DepositInvalidAmount_Ret { { "AgencyBankingMessageType", "deposit" }, { "AgencyBankingAccountNumber", "123456789" }, - { "AgencyBankingAmount", "not-a-number" } + { "Amount", "not-a-number" } }; var result = await proxy.ProcessSaleMessage(TestData.TransactionId, @@ -289,7 +289,7 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_DepositInvalidAmount_Ret result.IsFailed.ShouldBeTrue(); result.Status.ShouldBe(ResultStatus.Invalid); - result.Message.ShouldBe("AgencyBankingAmount - Amount is not a valid decimal value"); + result.Message.ShouldBe("Amount - Amount is not a valid decimal value"); } [Fact] @@ -309,7 +309,7 @@ public async Task AgencyBankingProxy_ProcessSaleMessage_DepositSuccess_ReturnsMa { { "AgencyBankingMessageType", "deposit" }, { "AgencyBankingAccountNumber", "123456789" }, - { "AgencyBankingAmount", "250.75" } + { "Amount", "250.75" } }; var result = await proxy.ProcessSaleMessage(TestData.TransactionId,