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 @@ -32,7 +32,7 @@ public void ProcessSaleTransactionRequest_CanBeCreated_IsCreated()
{
ProcessSaleTransactionRequest processSaleTransactionRequest = ProcessSaleTransactionRequest.Create(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionTypeLogon.ToString(), TestData.TransactionDateTime,
TestData.TransactionNumber,
TestData.OperatorIdentifier,
TestData.OperatorIdentifier1,
TestData.AdditionalTransactionMetaData);

processSaleTransactionRequest.ShouldNotBeNull();
Expand All @@ -43,7 +43,7 @@ public void ProcessSaleTransactionRequest_CanBeCreated_IsCreated()
processSaleTransactionRequest.TransactionDateTime.ShouldBe(TestData.TransactionDateTime);
processSaleTransactionRequest.TransactionNumber.ShouldBe(TestData.TransactionNumber);
processSaleTransactionRequest.TransactionId.ShouldBe(TestData.TransactionId);
processSaleTransactionRequest.OperatorIdentifier.ShouldBe(TestData.OperatorIdentifier);
processSaleTransactionRequest.OperatorIdentifier.ShouldBe(TestData.OperatorIdentifier1);
processSaleTransactionRequest.AdditionalTransactionMetadata.ShouldNotBeNull();
processSaleTransactionRequest.AdditionalTransactionMetadata.Count.ShouldBe(TestData.AdditionalTransactionMetaData.Count);
}
Expand Down

Large diffs are not rendered by default.

179 changes: 140 additions & 39 deletions TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier);
await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);

(String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateTransaction(estateId, merchantId, deviceIdentifier, transactionType, cancellationToken);
(String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateLogonTransaction(estateId, merchantId, deviceIdentifier, cancellationToken);

if (validationResult.responseCode == TransactionResponseCode.Success)
{
Expand Down Expand Up @@ -138,7 +138,7 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
/// <param name="transactionDateTime">The transaction date time.</param>
/// <param name="transactionNumber">The transaction number.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
/// <param name="operatorId">The operator identifier.</param>
/// <param name="operatorIdentifier">The operator identifier.</param>
/// <param name="additionalTransactionMetadata">The additional transaction metadata.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Expand All @@ -148,7 +148,7 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
DateTime transactionDateTime,
String transactionNumber,
String deviceIdentifier,
String operatorId,
String operatorIdentifier,
Dictionary<String, String> additionalTransactionMetadata,
CancellationToken cancellationToken)
{
Expand All @@ -161,13 +161,13 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier);
await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);

(String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateTransaction(estateId, merchantId, deviceIdentifier, transactionType, cancellationToken);
(String responseMessage, TransactionResponseCode responseCode) validationResult = await this.ValidateSaleTransaction(estateId, merchantId, deviceIdentifier, operatorIdentifier, cancellationToken);

if (validationResult.responseCode == TransactionResponseCode.Success)
{
// TODO: Do the online processing with the operator here
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);
IOperatorProxy operatorProxy = OperatorProxyResolver(operatorId);
IOperatorProxy operatorProxy = OperatorProxyResolver(operatorIdentifier);
await operatorProxy.ProcessSaleMessage(transactionId, merchant, transactionDateTime, additionalTransactionMetadata, cancellationToken);

// Record the successful validation
Expand Down Expand Up @@ -198,57 +198,131 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
};
}

/// <summary>
/// Validates the transaction.
/// </summary>
/// <param name="estateId">The estate identifier.</param>
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
/// <exception cref="TransactionValidationException">
/// Estate Id [{estateId}] is not a valid estate
/// or
/// Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]
/// </exception>
private async Task<(EstateResponse estate, MerchantResponse merchant)> ValidateTransaction(Guid estateId,
Guid merchantId, CancellationToken cancellationToken)
{
EstateResponse estate = null;
// Validate the Estate Record is a valid estate
try
{
estate = await this.GetEstate(estateId, cancellationToken);
}
catch (Exception ex) when (ex.InnerException != null && ex.InnerException.GetType() == typeof(KeyNotFoundException))
{
throw new TransactionValidationException($"Estate Id [{estateId}] is not a valid estate", TransactionResponseCode.InvalidEstateId);
}

// get the merchant record and validate the device
// TODO: Token
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);

// TODO: Remove this once GetMerchant returns correct response when merchant not found
if (merchant.MerchantName == null)
{
throw new TransactionValidationException($"Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]",
TransactionResponseCode.InvalidMerchantId);
}

return (estate, merchant);
}

/// <summary>
/// Validates the transaction.
/// </summary>
/// <param name="estateId">The estate identifier.</param>
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
/// <param name="transactionType">Type of the transaction.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
/// <exception cref="TransactionProcessor.BusinessLogic.Services.TransactionValidationException">Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}</exception>
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateTransaction(Guid estateId,
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateLogonTransaction(Guid estateId,
Guid merchantId,
String deviceIdentifier,
TransactionType transactionType,
CancellationToken cancellationToken)
{
try
{
EstateResponse estate = null;
// Validate the Estate Record is a valid estate
try
(EstateResponse estate, MerchantResponse merchant) validateTransactionResponse = await this.ValidateTransaction(estateId, merchantId, cancellationToken);
MerchantResponse merchant = validateTransactionResponse.merchant;

// Device Validation
if (merchant.Devices == null || merchant.Devices.Any() == false)
{
estate = await this.GetEstate(estateId, cancellationToken);
await this.AddDeviceToMerchant(estateId, merchantId, deviceIdentifier, cancellationToken);
}
catch (Exception ex) when (ex.InnerException != null && ex.InnerException.GetType() == typeof(KeyNotFoundException))
else
{
throw new TransactionValidationException($"Estate Id [{estateId}] is not a valid estate", TransactionResponseCode.InvalidEstateId);
}

// get the merchant record and validate the device
// TODO: Token
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);
// Validate the device
KeyValuePair<Guid, String> device = merchant.Devices.SingleOrDefault(d => d.Value == deviceIdentifier);

// TODO: Remove this once GetMerchant returns correct response when merchant not found
if (merchant.MerchantName == null)
{
throw new TransactionValidationException($"Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]",
TransactionResponseCode.InvalidMerchantId);
if (device.Key == Guid.Empty)
{
// Device not found,throw error
throw new TransactionValidationException($"Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}",
TransactionResponseCode.InvalidDeviceIdentifier);
}
}

// If we get here everything is good
return ("SUCCESS", TransactionResponseCode.Success);
}
catch (TransactionValidationException tvex)
{
return (tvex.Message, tvex.ResponseCode);
}
}

/// <summary>
/// Validates the sale transaction.
/// </summary>
/// <param name="estateId">The estate identifier.</param>
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
/// <param name="operatorIdentifier">The operator identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
/// <exception cref="TransactionValidationException">
/// Merchant {merchant.MerchantName} has no valid Devices for this transaction.
/// or
/// Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}
/// or
/// Estate {estate.EstateName} has no operators defined
/// or
/// Operator {operatorIdentifier} not configured for Estate [{estate.EstateName}]
/// or
/// Merchant {merchant.MerchantName} has no operators defined
/// or
/// Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]
/// </exception>
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateSaleTransaction(Guid estateId,
Guid merchantId,
String deviceIdentifier,
String operatorIdentifier,
CancellationToken cancellationToken)
{
try
{
(EstateResponse estate, MerchantResponse merchant) validateTransactionResponse = await this.ValidateTransaction(estateId, merchantId, cancellationToken);
EstateResponse estate = validateTransactionResponse.estate;
MerchantResponse merchant = validateTransactionResponse.merchant;

// Device Validation
if (merchant.Devices == null || merchant.Devices.Any() == false)
{
if (transactionType == TransactionType.Logon)
{
await this.AddDeviceToMerchant(estateId, merchantId, deviceIdentifier, cancellationToken);
}
else
{
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no valid Devices for this transaction.",
TransactionResponseCode.NoValidDevices);
}
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no valid Devices for this transaction.",
TransactionResponseCode.NoValidDevices);
}
else
{
Expand All @@ -263,19 +337,46 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
}
}

// Operator Validation (Estate)
if (estate.Operators == null || estate.Operators.Any() == false)
{
throw new TransactionValidationException($"Estate {estate.EstateName} has no operators defined",
TransactionResponseCode.NoEstateOperators);
}
else
{
// Operators have been configured for the estate
EstateOperatorResponse operatorRecord = estate.Operators.SingleOrDefault(o => o.Name == operatorIdentifier);
if (operatorRecord == null)
{
throw new TransactionValidationException($"Operator {operatorIdentifier} not configured for Estate [{estate.EstateName}]", TransactionResponseCode.OperatorNotValidForEstate);
}
}

// Operator Validation (Merchant)
if (merchant.Operators == null || merchant.Operators.Any() == false)
{
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no operators defined",
TransactionResponseCode.NoEstateOperators);
}
else
{
// Operators have been configured for the estate
MerchantOperatorResponse operatorRecord = merchant.Operators.SingleOrDefault(o => o.Name == operatorIdentifier);
if (operatorRecord == null)
{
throw new TransactionValidationException($"Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]", TransactionResponseCode.OperatorNotValidForMerchant);
}
}


// If we get here everything is good
return ("SUCCESS", TransactionResponseCode.Success);
}
catch (TransactionValidationException tvex)
{
return (tvex.Message, tvex.ResponseCode);
}
catch (Exception ex)
{
Logger.LogError(ex);
return ("Unspecified Processing Error", TransactionResponseCode.UnknownFailure);
}

}

private TokenResponse TokenResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ public enum TransactionResponseCode
InvalidEstateId = 1001,
InvalidMerchantId = 1002,
NoValidDevices = 1003,
NoEstateOperators = 1004,
OperatorNotValidForEstate = 1005,
NoMerchantOperators = 1006,
OperatorNotValidForMerchant = 1007,

// A Catch All generic Error where reason has not been identified
UnknownFailure = 9999
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="EstateManagement.Client" Version="0.0.9.1" />
<PackageReference Include="EstateManagement.Client" Version="0.0.10" />
<PackageReference Include="SecurityService.Client" Version="0.0.9" />
<PackageReference Include="Shared" Version="0.0.12" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="0.0.12" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Background:
Given I have created the following operators
| EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber |
| Test Estate 1 | Safaricom | True | True |
| Test Estate 2 | Safaricom | True | True |

Given I create the following merchants
| MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName |
Expand All @@ -34,6 +35,8 @@ Background:
Given I have assigned the following operator to the merchants
| OperatorName | MerchantName | MerchantNumber | TerminalNumber | EstateName |
| Safaricom | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 |
| Safaricom | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 |
| Safaricom | Test Merchant 3 | 00000003 | 10000003 | Test Estate 2 |

Given I have assigned the following devices to the merchants
| DeviceIdentifier | MerchantName | EstateName |
Expand Down
Loading