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 @@ -35,6 +35,7 @@ public async Task SafaricomPinlessProxy_ProcessSaleMessage_TopupSuccessful_SaleM
OperatorResponse operatorResponse = await safaricomPinlessproxy.ProcessSaleMessage(TestData.TransactionId,
TestData.Merchant,
TestData.TransactionDateTime,
TestData.TransactionReference,
TestData.AdditionalTransactionMetaData,
CancellationToken.None);

Expand All @@ -61,6 +62,7 @@ public async Task SafaricomPinlessProxy_ProcessSaleMessage_TopupFailed_SaleMessa
OperatorResponse operatorResponse = await safaricomPinlessproxy.ProcessSaleMessage(TestData.TransactionId,
TestData.Merchant,
TestData.TransactionDateTime,
TestData.TransactionReference,
TestData.AdditionalTransactionMetaData,
CancellationToken.None);

Expand Down Expand Up @@ -88,11 +90,48 @@ public async Task SafaricomPinlessProxy_ProcessSaleMessage_FailedToSend_ErrorThr
await safaricomPinlessproxy.ProcessSaleMessage(TestData.TransactionId,
TestData.Merchant,
TestData.TransactionDateTime,
TestData.TransactionReference,
TestData.AdditionalTransactionMetaData,
CancellationToken.None);
});
}

[Theory]
[InlineData("", "123456789")]
[InlineData(null, "123456789")]
[InlineData("A", "123456789")]
[InlineData("1000.00", "")]
[InlineData("1000.00", null)]
public async Task SafaricomPinlessProxy_ProcessSaleMessage_InvalidData_ErrorThrown(String transactionAmount, String customerAccountNumber)
{
HttpResponseMessage responseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(TestData.SuccessfulSafaricomTopup)
};

SafaricomConfiguration safaricomConfiguration = TestData.SafaricomConfiguration;
HttpClient httpClient = SetupMockHttpClient(responseMessage);

IOperatorProxy safaricomPinlessproxy = new SafaricomPinlessProxy(safaricomConfiguration, httpClient);

Dictionary<String,String> additionalMetatdata = new Dictionary<String, String>
{
{"Amount", transactionAmount},
{"CustomerAccountNumber",customerAccountNumber }
};

Should.Throw<Exception>(async () =>
{
await safaricomPinlessproxy.ProcessSaleMessage(TestData.TransactionId,
TestData.Merchant,
TestData.TransactionDateTime,
TestData.TransactionReference,
additionalMetatdata,
CancellationToken.None);
});
}

private HttpClient SetupMockHttpClient(HttpResponseMessage responseMessage)
{
Mock<HttpMessageHandler> handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ await transactionAggregateManager.StartTransaction(TestData.TransactionId,
TestData.TransactionDateTime,
TestData.TransactionNumber,
transactionType,
TestData.TransactionReference,
TestData.EstateId,
TestData.MerchantId,
TestData.DeviceIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_SuccesfulOpera
operatorProxy.Setup(o => o.ProcessSaleMessage(It.IsAny<Guid>(),
It.IsAny<MerchantResponse>(),
It.IsAny<DateTime>(),
It.IsAny<String>(),
It.IsAny<Dictionary<String, String>>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new OperatorResponse
{
Expand Down Expand Up @@ -319,6 +320,7 @@ public async Task TransactionDomainService_ProcessSaleTransaction_FailedOperator
operatorProxy.Setup(o => o.ProcessSaleMessage(It.IsAny<Guid>(),
It.IsAny<MerchantResponse>(),
It.IsAny<DateTime>(),
It.IsAny<String>(),
It.IsAny<Dictionary<String, String>>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new OperatorResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ public interface IOperatorProxy
/// <param name="transactionId">The transaction identifier.</param>
/// <param name="merchant">The merchant.</param>
/// <param name="transactionDateTime">The transaction date time.</param>
/// <param name="transactionReference">The transaction reference.</param>
/// <param name="additionalTransactionMetadata">The additional transaction metadata.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<OperatorResponse> ProcessSaleMessage(Guid transactionId,
MerchantResponse merchant,
DateTime transactionDateTime,
String transactionReference,
Dictionary<String, String> additionalTransactionMetadata,
CancellationToken cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,39 @@ public SafaricomPinlessProxy(SafaricomConfiguration safaricomConfiguration,
/// <param name="transactionId">The transaction identifier.</param>
/// <param name="merchant">The merchant.</param>
/// <param name="transactionDateTime">The transaction date time.</param>
/// <param name="transactionReference">The transaction reference.</param>
/// <param name="additionalTransactionMetadata">The additional transaction metadata.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
/// <exception cref="Exception">
/// Amount is a required field for this transaction type
/// or
/// CustomerAccountNumber is a required field for this transaction type
/// or
/// Error sending request [{requestUrl}] to Safaricom. Status Code [{responseMessage.StatusCode}]
/// </exception>
public async Task<OperatorResponse> ProcessSaleMessage(Guid transactionId,
MerchantResponse merchant,
DateTime transactionDateTime,
String transactionReference,
Dictionary<String, String> additionalTransactionMetadata,
CancellationToken cancellationToken)
{
String requestUrl = this.BuildRequest(transactionDateTime, "123456789", "123456789", "1000");
// Extract the required fields
String transactionAmount = additionalTransactionMetadata.GetValueOrDefault("Amount");
String customerMsisdn = additionalTransactionMetadata.GetValueOrDefault("CustomerAccountNumber");

if (String.IsNullOrEmpty(transactionAmount))
{
throw new Exception("Amount is a required field for this transaction type");
}

if (String.IsNullOrEmpty(customerMsisdn))
{
throw new Exception("CustomerAccountNumber is a required field for this transaction type");
}

String requestUrl = this.BuildRequest(transactionDateTime, transactionReference, customerMsisdn, transactionAmount);

// Concatenate the request message
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(requestUrl));
Expand Down Expand Up @@ -98,6 +121,12 @@ private String BuildRequest(DateTime transactionDateTime,

StringBuilder xmlData = new StringBuilder();

// Covert the transaction amount to Decimal and remove decimal places
if (Decimal.TryParse(transactionAmount, out Decimal amountAsDecimal) == false)
{
throw new Exception("Transaction Amount is not a valid decimal value");
}

// Now build up the XML part of the message
xmlData.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
xmlData.Append("<ns0:COMMAND xmlns:ns0=\"http://safaricom.co.ke/Pinless/keyaccounts/\">");
Expand All @@ -111,7 +140,7 @@ private String BuildRequest(DateTime transactionDateTime,
xmlData.Append($"<ns0:EXTCODE>{this.SafaricomConfiguration.ExtCode}</ns0:EXTCODE>");
xmlData.Append($"<ns0:EXTREFNUM>{externalReference}</ns0:EXTREFNUM>");
xmlData.Append($"<ns0:MSISDN2>{customerMsisdn}</ns0:MSISDN2>");
xmlData.Append($"<ns0:AMOUNT>{transactionAmount}</ns0:AMOUNT>");
xmlData.Append($"<ns0:AMOUNT>{amountAsDecimal:G0}</ns0:AMOUNT>");
xmlData.Append("<ns0:LANGUAGE1>0</ns0:LANGUAGE1>");
xmlData.Append("<ns0:LANGUAGE2>0</ns0:LANGUAGE2>");
xmlData.Append("<ns0:SELECTOR>0</ns0:SELECTOR>");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ Task RecordAdditionalResponseData(Guid estateId,
/// <param name="transactionDateTime">The transaction date time.</param>
/// <param name="transactionNumber">The transaction number.</param>
/// <param name="transactionType">Type of the transaction.</param>
/// <param name="transactionReference">The transaction reference.</param>
/// <param name="estateId">The estate identifier.</param>
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
Expand All @@ -141,6 +142,7 @@ Task StartTransaction(Guid transactionId,
DateTime transactionDateTime,
String transactionNumber,
TransactionType transactionType,
String transactionReference,
Guid estateId,
Guid merchantId,
String deviceIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ public async Task RecordAdditionalResponseData(Guid estateId,
/// <param name="transactionDateTime">The transaction date time.</param>
/// <param name="transactionNumber">The transaction number.</param>
/// <param name="transactionType">Type of the transaction.</param>
/// <param name="transactionReference">The transaction reference.</param>
/// <param name="estateId">The estate identifier.</param>
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
Expand All @@ -252,6 +253,7 @@ public async Task StartTransaction(Guid transactionId,
DateTime transactionDateTime,
String transactionNumber,
TransactionType transactionType,
String transactionReference,
Guid estateId,
Guid merchantId,
String deviceIdentifier,
Expand All @@ -262,7 +264,7 @@ public async Task StartTransaction(Guid transactionId,

TransactionAggregate transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken);

transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier);
transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, transactionReference, estateId, merchantId, deviceIdentifier);

await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,14 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
{
TransactionType transactionType = TransactionType.Logon;

// Generate a transaction reference
String transactionReference = this.GenerateTransactionReference();

await this.TransactionAggregateManager.StartTransaction(transactionId,
transactionDateTime,
transactionNumber,
transactionType,
transactionReference,
estateId,
merchantId,
deviceIdentifier,
Expand Down Expand Up @@ -127,6 +131,20 @@ await this.TransactionAggregateManager.StartTransaction(transactionId,
};
}

/// <summary>
/// Generates the transaction reference.
/// </summary>
/// <returns></returns>
private String GenerateTransactionReference()
{
Int64 i = 1;
foreach (Byte b in Guid.NewGuid().ToByteArray())
{
i *= ((Int32)b + 1);
}
return $"{i - DateTime.Now.Ticks:x}";
}

/// <summary>
/// Processes the sale transaction.
/// </summary>
Expand All @@ -152,10 +170,14 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
{
TransactionType transactionType = TransactionType.Sale;

// Generate a transaction reference
String transactionReference = this.GenerateTransactionReference();

await this.TransactionAggregateManager.StartTransaction(transactionId,
transactionDateTime,
transactionNumber,
transactionType,
transactionReference,
estateId,
merchantId,
deviceIdentifier,
Expand All @@ -171,7 +193,7 @@ await this.TransactionAggregateManager.StartTransaction(transactionId,
// Do the online processing with the operator here
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);
IOperatorProxy operatorProxy = OperatorProxyResolver(operatorIdentifier);
OperatorResponse operatorResponse = await operatorProxy.ProcessSaleMessage(transactionId, merchant, transactionDateTime, additionalTransactionMetadata, cancellationToken);
OperatorResponse operatorResponse = await operatorProxy.ProcessSaleMessage(transactionId, merchant, transactionDateTime, transactionReference, additionalTransactionMetadata, cancellationToken);

if (operatorResponse.IsSuccessful)
{
Expand Down
Loading