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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> metadata = new Dictionary<String, String>
{
{ "AgencyBankingMessageType", "deposit" },
{ "AgencyBankingAccountNumber", "123456789" },
{ "Amount", "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<String, String> metadata = new Dictionary<String, String>
{
{ "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("Amount - Amount is a required field for this transaction type");
}

[Fact]
public async Task AgencyBankingProxy_ProcessSaleMessage_DepositInvalidAmount_ReturnsInvalidResult()
{
Mock<HttpMessageHandler> handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
HttpClient httpClient = new HttpClient(handlerMock.Object);
AgencyBankingConfiguration configuration = CreateConfiguration();
IOperatorProxy proxy = new AgencyBankingProxy(httpClient, configuration);

Dictionary<String, String> metadata = new Dictionary<String, String>
{
{ "AgencyBankingMessageType", "deposit" },
{ "AgencyBankingAccountNumber", "123456789" },
{ "Amount", "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("Amount - 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<String, String> metadata = new Dictionary<String, String>
{
{ "AgencyBankingMessageType", "deposit" },
{ "AgencyBankingAccountNumber", "123456789" },
{ "Amount", "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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,7 @@ public async Task<Result<OperatorResponse>> 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<String>("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<String>("AgencyBankingAccountNumber");

Expand All @@ -70,13 +62,72 @@ public async Task<Result<OperatorResponse>> ProcessSaleMessage(Guid transactionI

Logger.LogInformation($"AgencyBanking sale request metadata resolved. TransactionId=[{transactionId}], MessageType=[{messageType}], AccountNumber=[{accountNumber}]");

if (messageType == "balanceenquiry")
Result<OperatorResponse> 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<Result<OperatorResponse>> ProcessDeposit(Guid transactionId,
String agentId,
String accountNumber,
Dictionary<String, String> additionalTransactionMetadata,
CancellationToken cancellationToken) {

HttpRequestMessage request = new(HttpMethod.Post, $"{this.Configuration.Url}/transactions/deposit");
// Extract required data from metadata
String amountValue = additionalTransactionMetadata.ExtractFieldFromMetadata<String>("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<TransactionResult>(responseContent, SerialiserOptions);
Logger.LogWarning($"AgencyBanking deposit completed. TransactionId=[{transactionId}], ResponseCode=[{balanceEnquiryResponse.ResponseCode}]");

OperatorResponse operatorResponse = new()
{
IsSuccessful = true,
ResponseCode = "0000",
ResponseMessage = "SUCCESS",
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
};

return Result.Success(operatorResponse);
}

private async Task<Result<OperatorResponse>> ProcessBalanceEnquiry(Guid transactionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; } = "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ public static List<String> 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)
};

Expand Down Expand Up @@ -653,11 +653,19 @@ private static Dictionary<String, String> BuildMobileTopupMetaData(Decimal trans
};
}

private static Dictionary<String, String> BuildAgencyBankingMetaData(String messageType, String accountNumber){
return new Dictionary<String, String>{
private static Dictionary<String, String> BuildAgencyBankingMetaData(String messageType, String accountNumber, Decimal amount){

// TODO: make this a pattern match with sub functions??
var metadata = new Dictionary<String, String>{
{ "AgencyBankingMessageType", messageType },
{ "AgencyBankingAccountNumber", accountNumber }
};

if (amount > 0)
{
metadata.Add("Amount", amount.ToString());
}
return metadata;
}

private static Dictionary<String, String> BuildPataPawaPostPayMetaData(String messageType,
Expand Down
Loading
Loading