Skip to content

Commit a917f2f

Browse files
Merge pull request #63 from StuartFerguson/task/#54_handlesalesmessage
Business Rules on operators added
2 parents c204536 + 664e77a commit a917f2f

9 files changed

Lines changed: 818 additions & 230 deletions

File tree

TransactionProcessor.BusinessLogic.Tests/Requests/RequestTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void ProcessSaleTransactionRequest_CanBeCreated_IsCreated()
3232
{
3333
ProcessSaleTransactionRequest processSaleTransactionRequest = ProcessSaleTransactionRequest.Create(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, TestData.TransactionTypeLogon.ToString(), TestData.TransactionDateTime,
3434
TestData.TransactionNumber,
35-
TestData.OperatorIdentifier,
35+
TestData.OperatorIdentifier1,
3636
TestData.AdditionalTransactionMetaData);
3737

3838
processSaleTransactionRequest.ShouldNotBeNull();
@@ -43,7 +43,7 @@ public void ProcessSaleTransactionRequest_CanBeCreated_IsCreated()
4343
processSaleTransactionRequest.TransactionDateTime.ShouldBe(TestData.TransactionDateTime);
4444
processSaleTransactionRequest.TransactionNumber.ShouldBe(TestData.TransactionNumber);
4545
processSaleTransactionRequest.TransactionId.ShouldBe(TestData.TransactionId);
46-
processSaleTransactionRequest.OperatorIdentifier.ShouldBe(TestData.OperatorIdentifier);
46+
processSaleTransactionRequest.OperatorIdentifier.ShouldBe(TestData.OperatorIdentifier1);
4747
processSaleTransactionRequest.AdditionalTransactionMetadata.ShouldNotBeNull();
4848
processSaleTransactionRequest.AdditionalTransactionMetadata.Count.ShouldBe(TestData.AdditionalTransactionMetaData.Count);
4949
}

TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs

Lines changed: 298 additions & 22 deletions
Large diffs are not rendered by default.

TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs

Lines changed: 140 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
9898
transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier);
9999
await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);
100100

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

103103
if (validationResult.responseCode == TransactionResponseCode.Success)
104104
{
@@ -138,7 +138,7 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
138138
/// <param name="transactionDateTime">The transaction date time.</param>
139139
/// <param name="transactionNumber">The transaction number.</param>
140140
/// <param name="deviceIdentifier">The device identifier.</param>
141-
/// <param name="operatorId">The operator identifier.</param>
141+
/// <param name="operatorIdentifier">The operator identifier.</param>
142142
/// <param name="additionalTransactionMetadata">The additional transaction metadata.</param>
143143
/// <param name="cancellationToken">The cancellation token.</param>
144144
/// <returns></returns>
@@ -148,7 +148,7 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
148148
DateTime transactionDateTime,
149149
String transactionNumber,
150150
String deviceIdentifier,
151-
String operatorId,
151+
String operatorIdentifier,
152152
Dictionary<String, String> additionalTransactionMetadata,
153153
CancellationToken cancellationToken)
154154
{
@@ -161,13 +161,13 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
161161
transactionAggregate.StartTransaction(transactionDateTime, transactionNumber, transactionType, estateId, merchantId, deviceIdentifier);
162162
await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);
163163

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

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

173173
// Record the successful validation
@@ -198,57 +198,131 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
198198
};
199199
}
200200

201+
/// <summary>
202+
/// Validates the transaction.
203+
/// </summary>
204+
/// <param name="estateId">The estate identifier.</param>
205+
/// <param name="merchantId">The merchant identifier.</param>
206+
/// <param name="cancellationToken">The cancellation token.</param>
207+
/// <returns></returns>
208+
/// <exception cref="TransactionValidationException">
209+
/// Estate Id [{estateId}] is not a valid estate
210+
/// or
211+
/// Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]
212+
/// </exception>
213+
private async Task<(EstateResponse estate, MerchantResponse merchant)> ValidateTransaction(Guid estateId,
214+
Guid merchantId, CancellationToken cancellationToken)
215+
{
216+
EstateResponse estate = null;
217+
// Validate the Estate Record is a valid estate
218+
try
219+
{
220+
estate = await this.GetEstate(estateId, cancellationToken);
221+
}
222+
catch (Exception ex) when (ex.InnerException != null && ex.InnerException.GetType() == typeof(KeyNotFoundException))
223+
{
224+
throw new TransactionValidationException($"Estate Id [{estateId}] is not a valid estate", TransactionResponseCode.InvalidEstateId);
225+
}
226+
227+
// get the merchant record and validate the device
228+
// TODO: Token
229+
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);
230+
231+
// TODO: Remove this once GetMerchant returns correct response when merchant not found
232+
if (merchant.MerchantName == null)
233+
{
234+
throw new TransactionValidationException($"Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]",
235+
TransactionResponseCode.InvalidMerchantId);
236+
}
237+
238+
return (estate, merchant);
239+
}
240+
201241
/// <summary>
202242
/// Validates the transaction.
203243
/// </summary>
204244
/// <param name="estateId">The estate identifier.</param>
205245
/// <param name="merchantId">The merchant identifier.</param>
206246
/// <param name="deviceIdentifier">The device identifier.</param>
207-
/// <param name="transactionType">Type of the transaction.</param>
208247
/// <param name="cancellationToken">The cancellation token.</param>
209248
/// <returns></returns>
210249
/// <exception cref="TransactionProcessor.BusinessLogic.Services.TransactionValidationException">Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}</exception>
211-
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateTransaction(Guid estateId,
250+
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateLogonTransaction(Guid estateId,
212251
Guid merchantId,
213252
String deviceIdentifier,
214-
TransactionType transactionType,
215253
CancellationToken cancellationToken)
216254
{
217255
try
218256
{
219-
EstateResponse estate = null;
220-
// Validate the Estate Record is a valid estate
221-
try
257+
(EstateResponse estate, MerchantResponse merchant) validateTransactionResponse = await this.ValidateTransaction(estateId, merchantId, cancellationToken);
258+
MerchantResponse merchant = validateTransactionResponse.merchant;
259+
260+
// Device Validation
261+
if (merchant.Devices == null || merchant.Devices.Any() == false)
222262
{
223-
estate = await this.GetEstate(estateId, cancellationToken);
263+
await this.AddDeviceToMerchant(estateId, merchantId, deviceIdentifier, cancellationToken);
224264
}
225-
catch (Exception ex) when (ex.InnerException != null && ex.InnerException.GetType() == typeof(KeyNotFoundException))
265+
else
226266
{
227-
throw new TransactionValidationException($"Estate Id [{estateId}] is not a valid estate", TransactionResponseCode.InvalidEstateId);
228-
}
229-
230-
// get the merchant record and validate the device
231-
// TODO: Token
232-
MerchantResponse merchant = await this.GetMerchant(estateId, merchantId, cancellationToken);
267+
// Validate the device
268+
KeyValuePair<Guid, String> device = merchant.Devices.SingleOrDefault(d => d.Value == deviceIdentifier);
233269

234-
// TODO: Remove this once GetMerchant returns correct response when merchant not found
235-
if (merchant.MerchantName == null)
236-
{
237-
throw new TransactionValidationException($"Merchant Id [{merchantId}] is not a valid merchant for estate [{estate.EstateName}]",
238-
TransactionResponseCode.InvalidMerchantId);
270+
if (device.Key == Guid.Empty)
271+
{
272+
// Device not found,throw error
273+
throw new TransactionValidationException($"Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}",
274+
TransactionResponseCode.InvalidDeviceIdentifier);
275+
}
239276
}
277+
278+
// If we get here everything is good
279+
return ("SUCCESS", TransactionResponseCode.Success);
280+
}
281+
catch (TransactionValidationException tvex)
282+
{
283+
return (tvex.Message, tvex.ResponseCode);
284+
}
285+
}
240286

287+
/// <summary>
288+
/// Validates the sale transaction.
289+
/// </summary>
290+
/// <param name="estateId">The estate identifier.</param>
291+
/// <param name="merchantId">The merchant identifier.</param>
292+
/// <param name="deviceIdentifier">The device identifier.</param>
293+
/// <param name="operatorIdentifier">The operator identifier.</param>
294+
/// <param name="cancellationToken">The cancellation token.</param>
295+
/// <returns></returns>
296+
/// <exception cref="TransactionValidationException">
297+
/// Merchant {merchant.MerchantName} has no valid Devices for this transaction.
298+
/// or
299+
/// Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}
300+
/// or
301+
/// Estate {estate.EstateName} has no operators defined
302+
/// or
303+
/// Operator {operatorIdentifier} not configured for Estate [{estate.EstateName}]
304+
/// or
305+
/// Merchant {merchant.MerchantName} has no operators defined
306+
/// or
307+
/// Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]
308+
/// </exception>
309+
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateSaleTransaction(Guid estateId,
310+
Guid merchantId,
311+
String deviceIdentifier,
312+
String operatorIdentifier,
313+
CancellationToken cancellationToken)
314+
{
315+
try
316+
{
317+
(EstateResponse estate, MerchantResponse merchant) validateTransactionResponse = await this.ValidateTransaction(estateId, merchantId, cancellationToken);
318+
EstateResponse estate = validateTransactionResponse.estate;
319+
MerchantResponse merchant = validateTransactionResponse.merchant;
320+
321+
// Device Validation
241322
if (merchant.Devices == null || merchant.Devices.Any() == false)
242323
{
243-
if (transactionType == TransactionType.Logon)
244-
{
245-
await this.AddDeviceToMerchant(estateId, merchantId, deviceIdentifier, cancellationToken);
246-
}
247-
else
248-
{
249-
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no valid Devices for this transaction.",
250-
TransactionResponseCode.NoValidDevices);
251-
}
324+
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no valid Devices for this transaction.",
325+
TransactionResponseCode.NoValidDevices);
252326
}
253327
else
254328
{
@@ -263,19 +337,46 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
263337
}
264338
}
265339

340+
// Operator Validation (Estate)
341+
if (estate.Operators == null || estate.Operators.Any() == false)
342+
{
343+
throw new TransactionValidationException($"Estate {estate.EstateName} has no operators defined",
344+
TransactionResponseCode.NoEstateOperators);
345+
}
346+
else
347+
{
348+
// Operators have been configured for the estate
349+
EstateOperatorResponse operatorRecord = estate.Operators.SingleOrDefault(o => o.Name == operatorIdentifier);
350+
if (operatorRecord == null)
351+
{
352+
throw new TransactionValidationException($"Operator {operatorIdentifier} not configured for Estate [{estate.EstateName}]", TransactionResponseCode.OperatorNotValidForEstate);
353+
}
354+
}
355+
356+
// Operator Validation (Merchant)
357+
if (merchant.Operators == null || merchant.Operators.Any() == false)
358+
{
359+
throw new TransactionValidationException($"Merchant {merchant.MerchantName} has no operators defined",
360+
TransactionResponseCode.NoEstateOperators);
361+
}
362+
else
363+
{
364+
// Operators have been configured for the estate
365+
MerchantOperatorResponse operatorRecord = merchant.Operators.SingleOrDefault(o => o.Name == operatorIdentifier);
366+
if (operatorRecord == null)
367+
{
368+
throw new TransactionValidationException($"Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]", TransactionResponseCode.OperatorNotValidForMerchant);
369+
}
370+
}
371+
372+
266373
// If we get here everything is good
267374
return ("SUCCESS", TransactionResponseCode.Success);
268375
}
269376
catch (TransactionValidationException tvex)
270377
{
271378
return (tvex.Message, tvex.ResponseCode);
272379
}
273-
catch (Exception ex)
274-
{
275-
Logger.LogError(ex);
276-
return ("Unspecified Processing Error", TransactionResponseCode.UnknownFailure);
277-
}
278-
279380
}
280381

281382
private TokenResponse TokenResponse;

TransactionProcessor.BusinessLogic/Services/TransactionResponseCode.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ public enum TransactionResponseCode
88
InvalidEstateId = 1001,
99
InvalidMerchantId = 1002,
1010
NoValidDevices = 1003,
11+
NoEstateOperators = 1004,
12+
OperatorNotValidForEstate = 1005,
13+
NoMerchantOperators = 1006,
14+
OperatorNotValidForMerchant = 1007,
1115

1216
// A Catch All generic Error where reason has not been identified
1317
UnknownFailure = 9999

TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="EstateManagement.Client" Version="0.0.9.1" />
8+
<PackageReference Include="EstateManagement.Client" Version="0.0.10" />
99
<PackageReference Include="SecurityService.Client" Version="0.0.9" />
1010
<PackageReference Include="Shared" Version="0.0.12" />
1111
<PackageReference Include="Shared.DomainDrivenDesign" Version="0.0.12" />

TransactionProcessor.IntegrationTests/SaleTransaction/SaleTransactionFeature.feature

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Background:
2424
Given I have created the following operators
2525
| EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber |
2626
| Test Estate 1 | Safaricom | True | True |
27+
| Test Estate 2 | Safaricom | True | True |
2728

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

3841
Given I have assigned the following devices to the merchants
3942
| DeviceIdentifier | MerchantName | EstateName |

0 commit comments

Comments
 (0)