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
138 changes: 138 additions & 0 deletions TransactionProcessor.Aggregates.Tests/MerchantBalanceAggregateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Shouldly;
using TransactionProcessor.Testing;

namespace TransactionProcessor.Aggregates.Tests
{
public class MerchantBalanceAggregateTests
{
[Fact]
public void MerchantBalanceAggregate_RecordCompletedTransaction_MerchantNotCreated_ErrorThrown()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.EmptyMerchantAggregate();
Should.Throw<InvalidOperationException>(() => {
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, true);
});
}

[Fact]
public void MerchantBalanceAggregate_RecordDeposit_MerchantNotCreated_ErrorThrown()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.EmptyMerchantAggregate();
Should.Throw<InvalidOperationException>(() => {
aggregate.RecordMerchantDeposit(merchantAggregate, TestData.DepositId, TestData.DepositAmount.Value, TestData.DepositDateTime);
});
}

[Fact]
public void MerchantBalanceAggregate_RecordMerchantWithdrawal_MerchantNotCreated_ErrorThrown()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.EmptyMerchantAggregate();
Should.Throw<InvalidOperationException>(() => {
aggregate.RecordMerchantWithdrawal(merchantAggregate, TestData.WithdrawalId, TestData.WithdrawalAmount.Value, TestData.WithdrawalDateTime);
});
}

[Fact]
public void MerchantBalanceAggregate_RecordSettledFee_MerchantNotCreated_ErrorThrown()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.EmptyMerchantAggregate();
Should.Throw<InvalidOperationException>(() => {
aggregate.RecordSettledFee(merchantAggregate, TestData.SettledFeeId1, TestData.SettledFeeAmount1, TestData.SettledFeeDateTime1);
});
}

[Fact]
public void MerchantBalanceAggregate_RecordCompletedTransaction_TransactionIsRecorded() {
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, true);
aggregate.Balance.ShouldBe(TestData.TransactionAmount * -1);
aggregate.AuthorisedSales.Count.ShouldBe(1);
aggregate.AuthorisedSales.Value.ShouldBe(TestData.TransactionAmount);
aggregate.AuthorisedSales.LastActivity.ShouldBe(TestData.TransactionDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordCompletedTransaction_MultipleTransactions_TransactionIsRecorded()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, true);
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId1, TestData.TransactionAmount, TestData.TransactionDateTime, true);
aggregate.Balance.ShouldBe((TestData.TransactionAmount * 2) * -1);
aggregate.AuthorisedSales.Count.ShouldBe(2);
aggregate.AuthorisedSales.Value.ShouldBe(TestData.TransactionAmount * 2);
aggregate.AuthorisedSales.LastActivity.ShouldBe(TestData.TransactionDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordCompletedTransaction_DuplicateTransactionIsIgnored()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, true);
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, true);
aggregate.Balance.ShouldBe(TestData.TransactionAmount * -1);
aggregate.AuthorisedSales.Count.ShouldBe(1);
aggregate.AuthorisedSales.Value.ShouldBe(TestData.TransactionAmount);
aggregate.AuthorisedSales.LastActivity.ShouldBe(TestData.TransactionDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordCompletedTransaction_TransactionDeclined_TransactionIsRecorded()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordCompletedTransaction(merchantAggregate, TestData.TransactionId, TestData.TransactionAmount, TestData.TransactionDateTime, false);
aggregate.Balance.ShouldBe(0);
aggregate.DeclinedSales.Count.ShouldBe(1);
aggregate.DeclinedSales.Value.ShouldBe(TestData.TransactionAmount);
aggregate.DeclinedSales.LastActivity.ShouldBe(TestData.TransactionDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordDeposit_DepositIsRecorded()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordMerchantDeposit(merchantAggregate, TestData.DepositId, TestData.DepositAmount.Value, TestData.DepositDateTime);
aggregate.Balance.ShouldBe(TestData.DepositAmount.Value);
aggregate.Deposits.Count.ShouldBe(1);
aggregate.Deposits.Value.ShouldBe(TestData.DepositAmount.Value);
aggregate.Deposits.LastActivity.ShouldBe(TestData.DepositDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordWithdrawal_WithdrawalIsRecorded()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordMerchantWithdrawal(merchantAggregate, TestData.WithdrawalId, TestData.WithdrawalAmount.Value, TestData.WithdrawalDateTime);
aggregate.Balance.ShouldBe(TestData.WithdrawalAmount.Value * -1);
aggregate.Withdrawals.Count.ShouldBe(1);
aggregate.Withdrawals.Value.ShouldBe(TestData.WithdrawalAmount.Value);
aggregate.Withdrawals.LastActivity.ShouldBe(TestData.WithdrawalDateTime);
}

[Fact]
public void MerchantBalanceAggregate_RecordSettledFee_SettledFeeIsRecorded()
{
MerchantBalanceAggregate aggregate = MerchantBalanceAggregate.Create(TestData.MerchantId);
MerchantAggregate merchantAggregate = TestData.Aggregates.CreatedMerchantAggregate();
aggregate.RecordSettledFee(merchantAggregate, TestData.SettledFeeId1, TestData.SettledFeeAmount1, TestData.SettledFeeDateTime1);
aggregate.Balance.ShouldBe(TestData.SettledFeeAmount1);
aggregate.Fees.Count.ShouldBe(1);
aggregate.Fees.Value.ShouldBe(TestData.SettledFeeAmount1);
aggregate.Fees.LastActivity.ShouldBe(TestData.SettledFeeDateTime1);
}
}
}
177 changes: 177 additions & 0 deletions TransactionProcessor.Aggregates/MerchantBalanceAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.General;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using TransactionProcessor.DomainEvents;
using TransactionProcessor.Models.Merchant;

namespace TransactionProcessor.Aggregates
{
public static class MerchantBalanceAggregateExtensions
{
public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.AuthorisedTransactionRecordedEvent domainEvent)
{
aggregate.Balance -= domainEvent.Amount;
aggregate.AuthorisedSales = aggregate.AuthorisedSales with { Count = aggregate.AuthorisedSales.Count + 1, Value = aggregate.AuthorisedSales.Value + domainEvent.Amount, LastActivity = domainEvent.DateTime };
}

public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.DeclinedTransactionRecordedEvent domainEvent)
{
aggregate.DeclinedSales= aggregate.AuthorisedSales with { Count = aggregate.DeclinedSales.Count + 1, Value = aggregate.DeclinedSales.Value + domainEvent.Amount, LastActivity = domainEvent.DateTime };
}

public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.MerchantDepositRecordedEvent domainEvent)
{
aggregate.Balance += domainEvent.Amount;
aggregate.Deposits = aggregate.Deposits with { Count = aggregate.Deposits.Count + 1, Value = aggregate.Deposits.Value + domainEvent.Amount, LastActivity = domainEvent.DateTime };
}

public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.MerchantWithdrawalRecordedEvent domainEvent)
{
aggregate.Balance -= domainEvent.Amount;
aggregate.Withdrawals = aggregate.Withdrawals with { Count = aggregate.Withdrawals.Count + 1, Value = aggregate.Withdrawals.Value + domainEvent.Amount, LastActivity = domainEvent.DateTime };
}

public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.SettledFeeRecordedEvent domainEvent)
{
aggregate.Balance += domainEvent.Amount;
aggregate.Fees = aggregate.Fees with { Count = aggregate.Fees.Count + 1, Value = aggregate.Fees.Value + domainEvent.Amount, LastActivity = domainEvent.DateTime };
}

public static void PlayEvent(this MerchantBalanceAggregate aggregate, MerchantBalanceDomainEvents.MerchantBalanceInitialisedEvent domainEvent)
{
aggregate.IsInitialised = true;
aggregate.EstateId = domainEvent.EstateId;
aggregate.Balance = 0;
}

private static void EnsureMerchantHasBeenCreated(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate) {
if (merchantAggregate.IsCreated == false) {
throw new InvalidOperationException("Merchant has not been created");
}
}

private static void EnsureMerchantBalanceHasBeenInitialised(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate,
DateTime dateTime)
{
if (aggregate.IsInitialised == false) {
Merchant merchant = merchantAggregate.GetMerchant();
MerchantBalanceDomainEvents.MerchantBalanceInitialisedEvent merchantBalanceInitialisedEvent = new MerchantBalanceDomainEvents.MerchantBalanceInitialisedEvent(merchant.MerchantId, merchant.EstateId,dateTime);
aggregate.ApplyAndAppend(merchantBalanceInitialisedEvent);
}
}


public static void RecordCompletedTransaction(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate,
Guid transactionId,
Decimal transactionAmount,
DateTime transactionDateTime,
Boolean isAuthorised) {
aggregate.EnsureMerchantHasBeenCreated(merchantAggregate);
aggregate.EnsureMerchantBalanceHasBeenInitialised(merchantAggregate, transactionDateTime);
DomainEvent domainEvent = isAuthorised switch {

Check notice on line 81 in TransactionProcessor.Aggregates/MerchantBalanceAggregate.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

TransactionProcessor.Aggregates/MerchantBalanceAggregate.cs#L81

Replace this 'switch' expression with a ternary conditional operator to increase readability.
true => new MerchantBalanceDomainEvents.AuthorisedTransactionRecordedEvent(aggregate.AggregateId, aggregate.EstateId, transactionId, transactionAmount, transactionDateTime),
_ => new MerchantBalanceDomainEvents.DeclinedTransactionRecordedEvent(aggregate.AggregateId, aggregate.EstateId, transactionId, transactionAmount, transactionDateTime)
};
aggregate.ApplyAndAppend(domainEvent);
}

public static void RecordMerchantDeposit(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate,
Guid depositId,
Decimal depositAmount,
DateTime depositDateTime) {
aggregate.EnsureMerchantHasBeenCreated(merchantAggregate);
aggregate.EnsureMerchantBalanceHasBeenInitialised(merchantAggregate, depositDateTime);

MerchantBalanceDomainEvents.MerchantDepositRecordedEvent domainEvent = new MerchantBalanceDomainEvents.MerchantDepositRecordedEvent(aggregate.AggregateId, aggregate.EstateId, depositId, depositAmount, depositDateTime);
aggregate.ApplyAndAppend(domainEvent);
}

public static void RecordMerchantWithdrawal(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate,
Guid withdrawalId,
Decimal withdrawalAmount,
DateTime withdrawalDateTime) {
aggregate.EnsureMerchantHasBeenCreated(merchantAggregate);
aggregate.EnsureMerchantBalanceHasBeenInitialised(merchantAggregate, withdrawalDateTime);

MerchantBalanceDomainEvents.MerchantWithdrawalRecordedEvent domainEvent = new MerchantBalanceDomainEvents.MerchantWithdrawalRecordedEvent(aggregate.AggregateId, aggregate.EstateId, withdrawalId, withdrawalAmount, withdrawalDateTime);
aggregate.ApplyAndAppend(domainEvent);
}

public static void RecordSettledFee(this MerchantBalanceAggregate aggregate,
MerchantAggregate merchantAggregate,
Guid feeId,
Decimal feeAmount,
DateTime feeDateTime) {
aggregate.EnsureMerchantHasBeenCreated(merchantAggregate);
aggregate.EnsureMerchantBalanceHasBeenInitialised(merchantAggregate, feeDateTime);

MerchantBalanceDomainEvents.SettledFeeRecordedEvent domainEvent = new MerchantBalanceDomainEvents.SettledFeeRecordedEvent(aggregate.AggregateId, aggregate.EstateId, feeId, feeAmount, feeDateTime);
aggregate.ApplyAndAppend(domainEvent);
}
}

public record ActivityType(Int32 Count, Decimal Value, DateTime LastActivity);

public record MerchantBalanceAggregate : Aggregate {

public Boolean IsInitialised { get; internal set; }
public Guid EstateId { get; internal set; }
public Decimal Balance { get; internal set; }
public ActivityType Deposits { get; internal set; }
public ActivityType Withdrawals { get; internal set; }
public ActivityType AuthorisedSales { get; internal set; }
public ActivityType DeclinedSales { get; internal set; }
public ActivityType Fees { get; internal set; }

[ExcludeFromCodeCoverage]
public MerchantBalanceAggregate()
{
// Nothing here
this.AuthorisedSales = new ActivityType(0, 0, DateTime.MinValue);
this.DeclinedSales = new ActivityType(0, 0, DateTime.MinValue);
this.Deposits = new ActivityType(0, 0, DateTime.MinValue);
this.Withdrawals = new ActivityType(0, 0, DateTime.MinValue);
this.Fees = new ActivityType(0, 0, DateTime.MinValue);
}

private MerchantBalanceAggregate(Guid aggregateId)
{
Guard.ThrowIfInvalidGuid(aggregateId, "Aggregate Id cannot be an Empty Guid");

this.AggregateId = aggregateId;
this.AuthorisedSales = new ActivityType(0, 0, DateTime.MinValue);
this.DeclinedSales = new ActivityType(0, 0, DateTime.MinValue);
this.Deposits = new ActivityType(0, 0, DateTime.MinValue);
this.Withdrawals = new ActivityType(0, 0, DateTime.MinValue);
this.Fees = new ActivityType(0, 0, DateTime.MinValue);
}

public static MerchantBalanceAggregate Create(Guid aggregateId)
{
return new MerchantBalanceAggregate(aggregateId);
}

public override void PlayEvent(IDomainEvent domainEvent) => MerchantBalanceAggregateExtensions.PlayEvent(this, (dynamic)domainEvent);

[ExcludeFromCodeCoverage]
protected override Object GetMetadata()
{
return new
{
EstateId = Guid.NewGuid() // TODO: Populate
};
}
}
}
3 changes: 2 additions & 1 deletion TransactionProcessor.Aggregates/TransactionAggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public static TransactionProcessor.Models.Transaction GetTransaction(this Transa
AdditionalRequestMetadata = aggregate.AdditionalTransactionRequestMetadata,
AdditionalResponseMetadata = aggregate.AdditionalTransactionResponseMetadata,
ResponseCode = aggregate.ResponseCode,
IsComplete = aggregate.IsCompleted
IsComplete = aggregate.IsCompleted,
IsAuthorised = aggregate.IsAuthorised || aggregate.IsLocallyAuthorised
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public MediatorTests()
this.Requests.Add(TestData.Queries.GetVoucherByVoucherCodeQuery);
this.Requests.Add(TestData.Queries.GetVoucherByTransactionIdQuery);

// Merchant Balance Commands
this.Requests.Add(TestData.Commands.RecordDepositCommand);
this.Requests.Add(TestData.Commands.RecordWithdrawalCommand);
this.Requests.Add(TestData.Commands.RecordAuthorisedSaleCommand);
this.Requests.Add(TestData.Commands.RecordDeclinedSaleCommand);
this.Requests.Add(TestData.Commands.RecordSettledFeeCommand);
}

[Fact]
Expand Down
Loading
Loading