Skip to content

Commit 24b4c1c

Browse files
Merge pull request #347 from TransactionProcessing/task/#340_merchantbalanceprojection
Merchant Balance projection added
2 parents 16e4ddc + d367563 commit 24b4c1c

79 files changed

Lines changed: 3493 additions & 584 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

TransactionProcessor.BusinessLogic.Tests/Services/TransactionAggregateManagerTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,25 @@ namespace TransactionProcessor.BusinessLogic.Tests.Services
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using BusinessLogic.Services;
11+
using Microsoft.Extensions.Logging.Abstractions;
1112
using Models;
1213
using Moq;
1314
using Shared.DomainDrivenDesign.EventSourcing;
1415
using Shared.EventStore.Aggregate;
1516
using Shared.EventStore.EventStore;
17+
using Shared.Logger;
1618
using Shouldly;
1719
using Testing;
1820
using TransactionAggregate;
1921
using Xunit;
22+
using NullLogger = Shared.Logger.NullLogger;
2023

2124
public class TransactionAggregateManagerTests
2225
{
26+
public TransactionAggregateManagerTests() {
27+
Logger.Initialise(new NullLogger());
28+
}
29+
2330
[Fact]
2431
public async Task TransactionAggregateManager_AuthoriseTransaction_TransactionAuthorised()
2532
{

TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace TransactionProcessor.BusinessLogic.Tests.Services
1414
using Models;
1515
using Moq;
1616
using OperatorInterfaces;
17+
using ProjectionEngine.Repository;
18+
using ProjectionEngine.State;
1719
using ReconciliationAggregate;
1820
using SecurityService.Client;
1921
using SecurityService.DataTransferObjects.Responses;
@@ -39,6 +41,8 @@ public class TransactionDomainServiceTests
3941

4042
private TransactionDomainService transactionDomainService = null;
4143

44+
private Mock<IProjectionStateRepository<MerchantBalanceState>> stateRepository;
45+
4246
private Mock<IAggregateRepository<ReconciliationAggregate, DomainEvent>> reconciliationAggregateRepository = null;
4347

4448
public TransactionDomainServiceTests() {
@@ -53,12 +57,13 @@ public TransactionDomainServiceTests() {
5357
operatorProxy = new Mock<IOperatorProxy>();
5458
reconciliationAggregateRepository = new Mock<IAggregateRepository<ReconciliationAggregate, DomainEvent>>();
5559
Func<String, IOperatorProxy> operatorProxyResolver = (operatorName) => { return operatorProxy.Object; };
56-
60+
stateRepository = new Mock<IProjectionStateRepository<MerchantBalanceState>>();
5761
transactionDomainService = new TransactionDomainService(transactionAggregateManager.Object,
5862
estateClient.Object,
5963
securityServiceClient.Object,
6064
operatorProxyResolver,
61-
reconciliationAggregateRepository.Object);
65+
reconciliationAggregateRepository.Object,
66+
stateRepository.Object);
6267
}
6368

6469
[Fact]
@@ -351,7 +356,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_SuccessfulOper
351356
TransactionId = TestData.OperatorTransactionId,
352357
ResponseCode = TestData.ResponseCode
353358
});
354-
359+
360+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
361+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
362+
355363
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
356364
TestData.EstateId,
357365
TestData.MerchantId,
@@ -430,7 +438,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MetaDataCaseTe
430438
TransactionId = TestData.OperatorTransactionId,
431439
ResponseCode = TestData.ResponseCode
432440
});
433-
441+
442+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
443+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
444+
434445
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
435446
TestData.EstateId,
436447
TestData.MerchantId,
@@ -480,7 +491,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MetaDataCaseTe
480491
TransactionId = TestData.OperatorTransactionId,
481492
ResponseCode = TestData.ResponseCode
482493
});
483-
494+
495+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
496+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
497+
484498
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
485499
TestData.EstateId,
486500
TestData.MerchantId,
@@ -525,7 +539,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_FailedOperator
525539
IsSuccessful = false,
526540
ResponseCode = TestData.DeclinedOperatorResponseCode
527541
});
528-
542+
543+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
544+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
545+
529546
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
530547
TestData.EstateId,
531548
TestData.MerchantId,
@@ -673,6 +690,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_NotEnoughCredi
673690
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
674691
.ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.MerchantDoesNotHaveEnoughCredit));
675692

693+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
694+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
695+
676696
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
677697
TestData.EstateId,
678698
TestData.MerchantId,
@@ -849,6 +869,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ContractId_Tra
849869
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
850870
.ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(expectedResponseCode));
851871

872+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
873+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
874+
852875
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
853876
TestData.EstateId,
854877
TestData.MerchantId,
@@ -882,6 +905,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ProductId_Tran
882905
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
883906
.ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(expectedResponseCode));
884907

908+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
909+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
910+
885911
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
886912
TestData.EstateId,
887913
TestData.MerchantId,
@@ -979,7 +1005,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ErrorInOperato
9791005
It.IsAny<String>(),
9801006
It.IsAny<Dictionary<String, String>>(),
9811007
It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Comms Error"));
982-
1008+
1009+
this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
1010+
.ReturnsAsync(TestData.MerchantBalanceProjectionState);
1011+
9831012
ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
9841013
TestData.EstateId,
9851014
TestData.MerchantId,

TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using EstateManagement.DataTransferObjects.Responses;
1313
using Models;
1414
using OperatorInterfaces;
15+
using ProjectionEngine.Repository;
16+
using ProjectionEngine.State;
1517
using ReconciliationAggregate;
1618
using SecurityService.Client;
1719
using SecurityService.DataTransferObjects.Responses;
@@ -44,6 +46,8 @@ public class TransactionDomainService : ITransactionDomainService
4446
/// </summary>
4547
private readonly IAggregateRepository<ReconciliationAggregate, DomainEvent> ReconciliationAggregateRepository;
4648

49+
private readonly IProjectionStateRepository<MerchantBalanceState> MerchantBalanceStateRepository;
50+
4751
/// <summary>
4852
/// The security service client
4953
/// </summary>
@@ -75,12 +79,14 @@ public TransactionDomainService(ITransactionAggregateManager transactionAggregat
7579
IEstateClient estateClient,
7680
ISecurityServiceClient securityServiceClient,
7781
Func<String, IOperatorProxy> operatorProxyResolver,
78-
IAggregateRepository<ReconciliationAggregate, DomainEvent> reconciliationAggregateRepository) {
82+
IAggregateRepository<ReconciliationAggregate, DomainEvent> reconciliationAggregateRepository,
83+
IProjectionStateRepository<MerchantBalanceState> merchantBalanceStateRepository) {
7984
this.TransactionAggregateManager = transactionAggregateManager;
8085
this.EstateClient = estateClient;
8186
this.SecurityServiceClient = securityServiceClient;
8287
this.OperatorProxyResolver = operatorProxyResolver;
8388
this.ReconciliationAggregateRepository = reconciliationAggregateRepository;
89+
this.MerchantBalanceStateRepository = merchantBalanceStateRepository;
8490
}
8591

8692
#endregion
@@ -670,10 +676,12 @@ private async Task<OperatorResponse> ProcessMessageWithOperator(MerchantResponse
670676
throw new TransactionValidationException("Transaction Amount must be greater than 0", TransactionResponseCode.InvalidSaleTransactionAmount);
671677
}
672678

679+
MerchantBalanceState merchantBalanceState = await this.MerchantBalanceStateRepository.Load(estateId, merchantId, cancellationToken);
680+
673681
// Check the merchant has enough balance to perform the sale
674-
if (merchant.AvailableBalance < transactionAmount) {
682+
if (merchantBalanceState.AvailableBalance < transactionAmount) {
675683
throw new
676-
TransactionValidationException($"Merchant [{merchant.MerchantName}] does not have enough credit available [{merchant.AvailableBalance}] to perform transaction amount [{transactionAmount}]",
684+
TransactionValidationException($"Merchant [{merchant.MerchantName}] does not have enough credit available [{merchantBalanceState.AvailableBalance}] to perform transaction amount [{transactionAmount}]",
677685
TransactionResponseCode.MerchantDoesNotHaveEnoughCredit);
678686
}
679687
}

TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<PackageReference Include="EstateManagement.Client" Version="1.1.2" />
99
<PackageReference Include="MessagingService.Client" Version="1.1.4-build6" />
1010
<PackageReference Include="SecurityService.Client" Version="1.1.1" />
11-
<PackageReference Include="Shared.DomainDrivenDesign" Version="1.3.8" />
12-
<PackageReference Include="Shared.EventStore" Version="1.3.8" />
11+
<PackageReference Include="Shared.DomainDrivenDesign" Version="1.3.9" />
12+
<PackageReference Include="Shared.EventStore" Version="1.3.9" />
1313
<PackageReference Include="MediatR" Version="10.0.1" />
1414
<PackageReference Include="System.IO.Abstractions" Version="17.0.15" />
1515
<PackageReference Include="System.ServiceModel.Duplex" Version="4.8.*" />
@@ -22,6 +22,7 @@
2222

2323
<ItemGroup>
2424
<ProjectReference Include="..\TransactionProcessor.Models\TransactionProcessor.Models.csproj" />
25+
<ProjectReference Include="..\TransactionProcessor.ProjectionEngine\TransactionProcessor.ProjectionEngine.csproj" />
2526
<ProjectReference Include="..\TransactionProcessor.ReconciliationAggregate\TransactionProcessor.ReconciliationAggregate.csproj" />
2627
<ProjectReference Include="..\TransactionProcessor.SettlementAggregates\TransactionProcessor.SettlementAggregates.csproj" />
2728
<ProjectReference Include="..\TransactionProcessor.TransactionAgrgegate\TransactionProcessor.TransactionAggregate.csproj" />

TransactionProcessor.Client/ITransactionProcessorClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ Task ResendEmailReceipt(String accessToken,
2828
Guid transactionId,
2929
CancellationToken cancellationToken);
3030

31+
Task<MerchantBalanceResponse> GetMerchantBalance(String accessToken,
32+
Guid estateId,
33+
Guid merchantId,
34+
CancellationToken cancellationToken);
35+
3136
#endregion
3237
}
3338
}

TransactionProcessor.Client/TransactionProcessor.Client.csproj

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

88
<ItemGroup>
9-
<PackageReference Include="ClientProxyBase" Version="1.3.8" />
9+
<PackageReference Include="ClientProxyBase" Version="1.3.9" />
1010
</ItemGroup>
1111

1212
<ItemGroup>

TransactionProcessor.Client/TransactionProcessorClient.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,38 @@ public async Task ResendEmailReceipt(String accessToken,
183183
}
184184
}
185185

186+
public async Task<MerchantBalanceResponse> GetMerchantBalance(String accessToken,
187+
Guid estateId,
188+
Guid merchantId,
189+
CancellationToken cancellationToken) {
190+
String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}/balance";
191+
MerchantBalanceResponse response = null;
192+
try
193+
{
194+
// Add the access token to the client headers
195+
this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
196+
197+
// Make the Http Call here
198+
HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken);
199+
200+
// Process the response
201+
String content = await this.HandleResponse(httpResponse, cancellationToken);
202+
203+
// call was successful so now deserialise the body to the response object
204+
response = JsonConvert.DeserializeObject<MerchantBalanceResponse>(content);
205+
206+
}
207+
catch (Exception ex)
208+
{
209+
// An exception has occurred, add some additional information to the message
210+
Exception exception = new Exception("Error getting merchant balance.", ex);
211+
212+
throw exception;
213+
}
214+
215+
return response;
216+
}
217+
186218
#endregion
187219
}
188220
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace TransactionProcessor.DataTransferObjects
2+
{
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using Newtonsoft.Json;
6+
7+
[ExcludeFromCodeCoverage]
8+
public class MerchantBalanceResponse
9+
{
10+
/// <summary>
11+
/// Gets or sets the estate identifier.
12+
/// </summary>
13+
/// <value>
14+
/// The estate identifier.
15+
/// </value>
16+
[JsonProperty("estate_id")]
17+
public Guid EstateId { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the merchant identifier.
21+
/// </summary>
22+
/// <value>
23+
/// The merchant identifier.
24+
/// </value>
25+
[JsonProperty("merchant_id")]
26+
public Guid MerchantId { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets the available balance.
30+
/// </summary>
31+
/// <value>
32+
/// The available balance.
33+
/// </value>
34+
[JsonProperty("available_balance")]
35+
public Decimal AvailableBalance { get; set; }
36+
37+
/// <summary>
38+
/// Gets or sets the balance.
39+
/// </summary>
40+
/// <value>
41+
/// The balance.
42+
/// </value>
43+
[JsonProperty("balance")]
44+
public Decimal Balance { get; set; }
45+
}
46+
}

0 commit comments

Comments
 (0)