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 @@ -8,18 +8,25 @@ namespace TransactionProcessor.BusinessLogic.Tests.Services
using System.Threading;
using System.Threading.Tasks;
using BusinessLogic.Services;
using Microsoft.Extensions.Logging.Abstractions;
using Models;
using Moq;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.EventStore.EventStore;
using Shared.Logger;
using Shouldly;
using Testing;
using TransactionAggregate;
using Xunit;
using NullLogger = Shared.Logger.NullLogger;

public class TransactionAggregateManagerTests
{
public TransactionAggregateManagerTests() {
Logger.Initialise(new NullLogger());
}

[Fact]
public async Task TransactionAggregateManager_AuthoriseTransaction_TransactionAuthorised()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace TransactionProcessor.BusinessLogic.Tests.Services
using Models;
using Moq;
using OperatorInterfaces;
using ProjectionEngine.Repository;
using ProjectionEngine.State;
using ReconciliationAggregate;
using SecurityService.Client;
using SecurityService.DataTransferObjects.Responses;
Expand All @@ -39,6 +41,8 @@ public class TransactionDomainServiceTests

private TransactionDomainService transactionDomainService = null;

private Mock<IProjectionStateRepository<MerchantBalanceState>> stateRepository;

private Mock<IAggregateRepository<ReconciliationAggregate, DomainEvent>> reconciliationAggregateRepository = null;

public TransactionDomainServiceTests() {
Expand All @@ -53,12 +57,13 @@ public TransactionDomainServiceTests() {
operatorProxy = new Mock<IOperatorProxy>();
reconciliationAggregateRepository = new Mock<IAggregateRepository<ReconciliationAggregate, DomainEvent>>();
Func<String, IOperatorProxy> operatorProxyResolver = (operatorName) => { return operatorProxy.Object; };

stateRepository = new Mock<IProjectionStateRepository<MerchantBalanceState>>();
transactionDomainService = new TransactionDomainService(transactionAggregateManager.Object,
estateClient.Object,
securityServiceClient.Object,
operatorProxyResolver,
reconciliationAggregateRepository.Object);
reconciliationAggregateRepository.Object,
stateRepository.Object);
}

[Fact]
Expand Down Expand Up @@ -351,7 +356,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_SuccessfulOper
TransactionId = TestData.OperatorTransactionId,
ResponseCode = TestData.ResponseCode
});


this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -430,7 +438,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MetaDataCaseTe
TransactionId = TestData.OperatorTransactionId,
ResponseCode = TestData.ResponseCode
});


this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -480,7 +491,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_MetaDataCaseTe
TransactionId = TestData.OperatorTransactionId,
ResponseCode = TestData.ResponseCode
});


this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -525,7 +539,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_FailedOperator
IsSuccessful = false,
ResponseCode = TestData.DeclinedOperatorResponseCode
});


this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -673,6 +690,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_NotEnoughCredi
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.GetDeclinedTransactionAggregate(TransactionResponseCode.MerchantDoesNotHaveEnoughCredit));

this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -849,6 +869,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ContractId_Tra
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(expectedResponseCode));

this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -882,6 +905,9 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ProductId_Tran
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(expectedResponseCode));

this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down Expand Up @@ -979,7 +1005,10 @@ public async Task TransactionDomainService_ProcessSaleTransaction_ErrorInOperato
It.IsAny<String>(),
It.IsAny<Dictionary<String, String>>(),
It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Comms Error"));


this.stateRepository.Setup(p => p.Load(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.MerchantBalanceProjectionState);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using EstateManagement.DataTransferObjects.Responses;
using Models;
using OperatorInterfaces;
using ProjectionEngine.Repository;
using ProjectionEngine.State;
using ReconciliationAggregate;
using SecurityService.Client;
using SecurityService.DataTransferObjects.Responses;
Expand Down Expand Up @@ -44,6 +46,8 @@ public class TransactionDomainService : ITransactionDomainService
/// </summary>
private readonly IAggregateRepository<ReconciliationAggregate, DomainEvent> ReconciliationAggregateRepository;

private readonly IProjectionStateRepository<MerchantBalanceState> MerchantBalanceStateRepository;

/// <summary>
/// The security service client
/// </summary>
Expand Down Expand Up @@ -75,12 +79,14 @@ public TransactionDomainService(ITransactionAggregateManager transactionAggregat
IEstateClient estateClient,
ISecurityServiceClient securityServiceClient,
Func<String, IOperatorProxy> operatorProxyResolver,
IAggregateRepository<ReconciliationAggregate, DomainEvent> reconciliationAggregateRepository) {
IAggregateRepository<ReconciliationAggregate, DomainEvent> reconciliationAggregateRepository,
IProjectionStateRepository<MerchantBalanceState> merchantBalanceStateRepository) {
this.TransactionAggregateManager = transactionAggregateManager;
this.EstateClient = estateClient;
this.SecurityServiceClient = securityServiceClient;
this.OperatorProxyResolver = operatorProxyResolver;
this.ReconciliationAggregateRepository = reconciliationAggregateRepository;
this.MerchantBalanceStateRepository = merchantBalanceStateRepository;
}

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

MerchantBalanceState merchantBalanceState = await this.MerchantBalanceStateRepository.Load(estateId, merchantId, cancellationToken);

// Check the merchant has enough balance to perform the sale
if (merchant.AvailableBalance < transactionAmount) {
if (merchantBalanceState.AvailableBalance < transactionAmount) {
throw new
TransactionValidationException($"Merchant [{merchant.MerchantName}] does not have enough credit available [{merchant.AvailableBalance}] to perform transaction amount [{transactionAmount}]",
TransactionValidationException($"Merchant [{merchant.MerchantName}] does not have enough credit available [{merchantBalanceState.AvailableBalance}] to perform transaction amount [{transactionAmount}]",
TransactionResponseCode.MerchantDoesNotHaveEnoughCredit);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<PackageReference Include="EstateManagement.Client" Version="1.1.2" />
<PackageReference Include="MessagingService.Client" Version="1.1.4-build6" />
<PackageReference Include="SecurityService.Client" Version="1.1.1" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="1.3.8" />
<PackageReference Include="Shared.EventStore" Version="1.3.8" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="1.3.9" />
<PackageReference Include="Shared.EventStore" Version="1.3.9" />
<PackageReference Include="MediatR" Version="10.0.1" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.15" />
<PackageReference Include="System.ServiceModel.Duplex" Version="4.8.*" />
Expand All @@ -22,6 +22,7 @@

<ItemGroup>
<ProjectReference Include="..\TransactionProcessor.Models\TransactionProcessor.Models.csproj" />
<ProjectReference Include="..\TransactionProcessor.ProjectionEngine\TransactionProcessor.ProjectionEngine.csproj" />
<ProjectReference Include="..\TransactionProcessor.ReconciliationAggregate\TransactionProcessor.ReconciliationAggregate.csproj" />
<ProjectReference Include="..\TransactionProcessor.SettlementAggregates\TransactionProcessor.SettlementAggregates.csproj" />
<ProjectReference Include="..\TransactionProcessor.TransactionAgrgegate\TransactionProcessor.TransactionAggregate.csproj" />
Expand Down
5 changes: 5 additions & 0 deletions TransactionProcessor.Client/ITransactionProcessorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Task ResendEmailReceipt(String accessToken,
Guid transactionId,
CancellationToken cancellationToken);

Task<MerchantBalanceResponse> GetMerchantBalance(String accessToken,
Guid estateId,
Guid merchantId,
CancellationToken cancellationToken);

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ClientProxyBase" Version="1.3.8" />
<PackageReference Include="ClientProxyBase" Version="1.3.9" />
</ItemGroup>

<ItemGroup>
Expand Down
32 changes: 32 additions & 0 deletions TransactionProcessor.Client/TransactionProcessorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,38 @@ public async Task ResendEmailReceipt(String accessToken,
}
}

public async Task<MerchantBalanceResponse> GetMerchantBalance(String accessToken,
Guid estateId,
Guid merchantId,
CancellationToken cancellationToken) {
String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}/balance";
MerchantBalanceResponse response = null;
try
{
// Add the access token to the client headers
this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

// Make the Http Call here
HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken);

// Process the response
String content = await this.HandleResponse(httpResponse, cancellationToken);

// call was successful so now deserialise the body to the response object
response = JsonConvert.DeserializeObject<MerchantBalanceResponse>(content);

}
catch (Exception ex)
{
// An exception has occurred, add some additional information to the message
Exception exception = new Exception("Error getting merchant balance.", ex);

throw exception;
}

return response;
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;

[ExcludeFromCodeCoverage]
public class MerchantBalanceResponse
{
/// <summary>
/// Gets or sets the estate identifier.
/// </summary>
/// <value>
/// The estate identifier.
/// </value>
[JsonProperty("estate_id")]
public Guid EstateId { get; set; }

/// <summary>
/// Gets or sets the merchant identifier.
/// </summary>
/// <value>
/// The merchant identifier.
/// </value>
[JsonProperty("merchant_id")]
public Guid MerchantId { get; set; }

/// <summary>
/// Gets or sets the available balance.
/// </summary>
/// <value>
/// The available balance.
/// </value>
[JsonProperty("available_balance")]
public Decimal AvailableBalance { get; set; }

/// <summary>
/// Gets or sets the balance.
/// </summary>
/// <value>
/// The balance.
/// </value>
[JsonProperty("balance")]
public Decimal Balance { get; set; }
}
}
Loading