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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TransactionProcessor.BusinessLogic.Tests.Extensions
{
using Common;
using Shared.Exceptions;
using Shouldly;
using Xunit;

public class ExtensionsTests
{
[Fact]
public void GuidExtensions_ToDateTime_DateTimeIsReturned()
{
Guid guid = Guid.Parse("c150c000-7c92-08d9-0000-000000000000");
DateTime dateTime = guid.ToDateTime();
dateTime.ShouldBe(new DateTime(2021, 9, 21));
}

[Fact]
public void DateExtensions_ToGuid_GuidIsReturned()
{
DateTime dateTime = new DateTime(2021, 9, 21);
Guid guid = dateTime.ToGuid();
guid.ShouldBe(Guid.Parse("c150c000-7c92-08d9-0000-000000000000"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<DebugType>Full</DebugType>
<DebugType>None</DebugType>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
21 changes: 20 additions & 1 deletion TransactionProcessor.BusinessLogic/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public static class Extensions
/// <param name="additionalTransactionMetadata">The additional transaction metadata.</param>
/// <returns></returns>
[ExcludeFromCodeCoverage]
public static T ExtractFieldFromMetadata<T>(this Dictionary<String, String> additionalTransactionMetadata, String fieldName)
public static T ExtractFieldFromMetadata<T>(this Dictionary<String, String> additionalTransactionMetadata,
String fieldName)
{
// Create a case insensitive version of the dictionary
Dictionary<String, String> caseInsensitiveDictionary = new Dictionary<String, String>(StringComparer.InvariantCultureIgnoreCase);
Expand All @@ -37,5 +38,23 @@ public static T ExtractFieldFromMetadata<T>(this Dictionary<String, String> addi
return default(T);
}
}

public static Guid ToGuid(this DateTime dt)
{
var bytes = BitConverter.GetBytes(dt.Ticks);

Array.Resize(ref bytes, 16);

return new Guid(bytes);
}

public static DateTime ToDateTime(this Guid guid)
{
var bytes = guid.ToByteArray();

Array.Resize(ref bytes, 8);

return new DateTime(BitConverter.ToInt64(bytes));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Common;
using EstateManagement.Client;
using EstateManagement.DataTransferObjects;
using EstateManagement.DataTransferObjects.Responses;
using Manager;
using MessagingService.Client;
using MessagingService.DataTransferObjects;
using Models;
using Newtonsoft.Json;
using SecurityService.Client;
using SecurityService.DataTransferObjects.Responses;
using Services;
using SettlementAggregates;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.EventStore.EventHandling;
using Shared.General;
using Shared.Logger;
using Transaction.DomainEvents;
using TransactionAggregate;
using CalculationType = Models.CalculationType;
using FeeType = Models.FeeType;

/// <summary>
///
Expand Down Expand Up @@ -55,6 +63,8 @@ public class TransactionDomainEventHandler : IDomainEventHandler
/// </summary>
private readonly IMessagingServiceClient MessagingServiceClient;

private readonly IAggregateRepository<PendingSettlementAggregate, DomainEventRecord.DomainEvent> PendingSettlementAggregateRepository;

/// <summary>
/// The token response
/// </summary>
Expand Down Expand Up @@ -83,14 +93,16 @@ public TransactionDomainEventHandler(ITransactionAggregateManager transactionAgg
IEstateClient estateClient,
ISecurityServiceClient securityServiceClient,
ITransactionReceiptBuilder transactionReceiptBuilder,
IMessagingServiceClient messagingServiceClient)
IMessagingServiceClient messagingServiceClient,
IAggregateRepository<PendingSettlementAggregate, DomainEventRecord.DomainEvent> pendingSettlementAggregateRepository)
{
this.TransactionAggregateManager = transactionAggregateManager;
this.FeeCalculationManager = feeCalculationManager;
this.EstateClient = estateClient;
this.SecurityServiceClient = securityServiceClient;
this.TransactionReceiptBuilder = transactionReceiptBuilder;
this.MessagingServiceClient = messagingServiceClient;
this.PendingSettlementAggregateRepository = pendingSettlementAggregateRepository;
}

#endregion
Expand Down Expand Up @@ -165,6 +177,7 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do
}

this.TokenResponse = await this.GetToken(cancellationToken);

// Ok we should have filtered out the not applicable transactions
// Get the fees to be calculated
List<ContractProductTransactionFee> feesForProduct = await this.EstateClient.GetTransactionFeesForProduct(this.TokenResponse.AccessToken,
Expand All @@ -191,11 +204,51 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do
// Do the fee calculation
List<CalculatedFee> resultFees = this.FeeCalculationManager.CalculateFees(feesForCalculation, transactionAggregate.TransactionAmount.Value);

foreach (CalculatedFee calculatedFee in resultFees)
// Process the non merchant fees
IEnumerable<CalculatedFee> nonMerchantFees = resultFees.Where(f => f.FeeType == FeeType.ServiceProvider);

foreach (CalculatedFee calculatedFee in nonMerchantFees)
{
// Add Fee to the Transaction
await this.TransactionAggregateManager.AddFee(transactionAggregate.EstateId, transactionAggregate.AggregateId, calculatedFee, cancellationToken);
}

// Now deal with merchant fees
IEnumerable<CalculatedFee> merchantFees = resultFees.Where(f => f.FeeType == FeeType.Merchant);

// get the merchant now to see the settlement schedule
this.TokenResponse = await this.GetToken(cancellationToken);
MerchantResponse merchant = await this.EstateClient.GetMerchant(this.TokenResponse.AccessToken, domainEvent.EstateId, domainEvent.MerchantId, cancellationToken);

// Add fees to transaction now if settlement is immediate
if (merchant.SettlementSchedule == SettlementSchedule.Immediate)
{
foreach (CalculatedFee calculatedFee in merchantFees)
{
// Add Fee to the Transaction
await this.TransactionAggregateManager.AddFee(transactionAggregate.EstateId, transactionAggregate.AggregateId, calculatedFee, cancellationToken);
}
}
else
{
foreach (CalculatedFee calculatedFee in merchantFees)
{
// Determine when the fee should be applied
Guid aggregateId = merchant.NextSettlementDueDate.ToGuid();

// We need to add the fees to a pending settlement stream (for today)
PendingSettlementAggregate aggregate = await this.PendingSettlementAggregateRepository.GetLatestVersion(aggregateId, cancellationToken);

if (aggregate.IsCreated == false)
{
aggregate.Create(transactionAggregate.EstateId, merchant.NextSettlementDueDate);
}

aggregate.AddFee(transactionAggregate.MerchantId, transactionAggregate.AggregateId, calculatedFee);

await this.PendingSettlementAggregateRepository.SaveChanges(aggregate, cancellationToken);
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class TransactionDomainService : ITransactionDomainService
/// The reconciliation aggregate repository
/// </summary>
private readonly IAggregateRepository<ReconciliationAggregate, DomainEventRecord.DomainEvent> ReconciliationAggregateRepository;

#endregion

#region Constructors
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="1.0.10.2" />
<PackageReference Include="EstateManagement.Client" Version="1.0.15-build74" />
<PackageReference Include="MessagingService.Client" Version="1.0.10.2" />
<PackageReference Include="SecurityService.Client" Version="1.0.6.2" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="1.0.12" />
Expand All @@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\TransactionProcessor.Models\TransactionProcessor.Models.csproj" />
<ProjectReference Include="..\TransactionProcessor.ReconciliationAggregate\TransactionProcessor.ReconciliationAggregate.csproj" />
<ProjectReference Include="..\TransactionProcessor.SettlementAggregates\TransactionProcessor.SettlementAggregates.csproj" />
<ProjectReference Include="..\TransactionProcessor.TransactionAgrgegate\TransactionProcessor.TransactionAggregate.csproj" />
</ItemGroup>

Expand Down
5 changes: 5 additions & 0 deletions TransactionProcessor.Client/ITransactionProcessorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Task<SerialisedMessage> PerformTransaction(String accessToken,
SerialisedMessage transactionRequest,
CancellationToken cancellationToken);

Task<PendingSettlementResponse> GetPendingSettlementByDate(String accessToken,
DateTime pendingSettlementDate,
Guid estateId,
CancellationToken cancellationToken);

#endregion
}
}
34 changes: 34 additions & 0 deletions TransactionProcessor.Client/TransactionProcessorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,40 @@ public async Task<SerialisedMessage> PerformTransaction(String accessToken,
return response;
}

public async Task<PendingSettlementResponse> GetPendingSettlementByDate(String accessToken,
DateTime pendingSettlementDate,
Guid estateId,
CancellationToken cancellationToken)
{
PendingSettlementResponse response = null;

String requestUri = $"{this.BaseAddress}/api/settlements/{pendingSettlementDate.Date:yyyy-MM-dd}/estates/{estateId}/pending";

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<PendingSettlementResponse>(content);
}
catch (Exception ex)
{
// An exception has occurred, add some additional information to the message
Exception exception = new Exception("Error getting pending settlment.", ex);

throw exception;
}

return response;
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace TransactionProcessor.DataTransferObjects
{
using System;

public class PendingSettlementResponse
{
public Guid EstateId { get; set; }

public DateTime SettlementDate { get; set; }

public Int32 NumberOfFeesPendingSettlement { get; set; }

public Int32 NumberOfFeesSettled { get; set; }

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

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.1" />
</ItemGroup>

Expand Down
55 changes: 1 addition & 54 deletions TransactionProcessor.IntegrationTests/Common/DockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,58 +380,7 @@ private static EventStoreClientSettings ConfigureEventStoreSettings(Int32 eventS
settings.DefaultCredentials = new UserCredentials("admin", "changeit");
return settings;
}

protected async Task CleanUpSubscriptionServiceConfiguration()
{
String connectionString = Setup.GetLocalConnectionString("SubscriptionServiceConfiguration");

await using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync(CancellationToken.None).ConfigureAwait(false);

// Delete the Event Store Server
await this.DeleteEventStoreServer(connection).ConfigureAwait(false);

// Delete the Subscriptions
await this.DeleteSubscriptions(connection).ConfigureAwait(false);

await connection.CloseAsync().ConfigureAwait(false);
}
}

protected async Task InsertEventStoreServer(SqlConnection openConnection, String eventStoreContainerName)
{
String esConnectionString = $"ConnectTo=tcp://admin:changeit@{eventStoreContainerName}:{DockerHelper.EventStoreTcpDockerPort};VerboseLogging=true;";
SqlCommand command = openConnection.CreateCommand();
command.CommandText = $"INSERT INTO EventStoreServer(EventStoreServerId, ConnectionString,Name) SELECT '{this.TestId}', '{esConnectionString}', 'TestEventStore'";
command.CommandType = CommandType.Text;
await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false);
}

protected async Task DeleteEventStoreServer(SqlConnection openConnection)
{
SqlCommand command = openConnection.CreateCommand();
command.CommandText = $"DELETE FROM EventStoreServer WHERE EventStoreServerId = '{this.TestId}'";
command.CommandType = CommandType.Text;
await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false);
}

protected async Task DeleteSubscriptions(SqlConnection openConnection)
{
SqlCommand command = openConnection.CreateCommand();
command.CommandText = $"DELETE FROM Subscription WHERE EventStoreId = '{this.TestId}'";
command.CommandType = CommandType.Text;
await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false);
}

protected async Task InsertSubscription(SqlConnection openConnection, String streamName, String groupName, String endPointUri)
{
SqlCommand command = openConnection.CreateCommand();
command.CommandText = $"INSERT INTO subscription(SubscriptionId, EventStoreId, StreamName, GroupName, EndPointUri, StreamPosition) SELECT '{Guid.NewGuid()}', '{this.TestId}', '{streamName}', '{groupName}', '{endPointUri}', null";
command.CommandType = CommandType.Text;
await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false);
}


private async Task RemoveEstateReadModel()
{
List<Guid> estateIdList = this.TestingContext.GetAllEstateIds();
Expand All @@ -455,8 +404,6 @@ await Retry.For(async () =>
/// </summary>
public override async Task StopContainersForScenarioRun()
{
await CleanUpSubscriptionServiceConfiguration().ConfigureAwait(false);

await RemoveEstateReadModel().ConfigureAwait(false);

if (this.Containers.Any())
Expand Down
Loading