diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs
index f776ef24..cf971820 100644
--- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs
+++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading;
@@ -267,6 +268,7 @@ await this.TransactionAggregateManager.RecordAdditionalResponseData(estateId,
/// Name of the field.
/// The additional transaction metadata.
///
+ [ExcludeFromCodeCoverage]
private T ExtractFieldFromMetadata(String fieldName,
Dictionary additionalTransactionMetadata)
{
@@ -304,6 +306,7 @@ await this.EstateClient.AddDeviceToMerchant(this.TokenResponse.AccessToken,
/// Generates the transaction reference.
///
///
+ [ExcludeFromCodeCoverage]
private String GenerateTransactionReference()
{
Int64 i = 1;
diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs
index 8bddd476..4e2fe0c4 100644
--- a/TransactionProcessor.Testing/TestData.cs
+++ b/TransactionProcessor.Testing/TestData.cs
@@ -29,6 +29,10 @@ public class TestData
public static Guid EstateId = Guid.Parse("A522FA27-F9D0-470A-A88D-325DED3B62EE");
+ public static Guid ContractId = Guid.Parse("97A9ED00-E522-428C-B3C3-5931092DBDCE");
+
+ public static Guid ProductId = Guid.Parse("ABA0E536-4E43-4E26-8362-7FB549DDA534");
+
public static String EstateName = "Test Estate 1";
public static String FailedSafaricomTopup =
diff --git a/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs b/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs
new file mode 100644
index 00000000..3c6614bc
--- /dev/null
+++ b/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs
@@ -0,0 +1,98 @@
+namespace TransactionProcessor.Transaction.DomainEvents
+{
+ using System;
+ using Newtonsoft.Json;
+ using Shared.DomainDrivenDesign.EventSourcing;
+
+ ///
+ ///
+ ///
+ ///
+ [JsonObject]
+ public class ProductDetailsAddedToTransactionEvent : DomainEvent
+ {
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The aggregate identifier.
+ /// The event identifier.
+ /// The estate identifier.
+ /// The contract identifier.
+ /// The product identifier.
+ private ProductDetailsAddedToTransactionEvent(Guid aggregateId,
+ Guid eventId,
+ Guid estateId,
+ Guid contractId,
+ Guid productId) : base(aggregateId, eventId)
+ {
+ this.TransactionId = aggregateId;
+ this.EstateId = estateId;
+ this.ContractId = contractId;
+ this.ProductId = productId;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the contract identifier.
+ ///
+ ///
+ /// The contract identifier.
+ ///
+ [JsonProperty]
+ public Guid ContractId { get; private set; }
+
+ ///
+ /// Gets the estate identifier.
+ ///
+ ///
+ /// The estate identifier.
+ ///
+ [JsonProperty]
+ public Guid EstateId { get; private set; }
+
+ ///
+ /// Gets the product identifier.
+ ///
+ ///
+ /// The product identifier.
+ ///
+ [JsonProperty]
+ public Guid ProductId { get; private set; }
+
+ ///
+ /// Gets the transaction identifier.
+ ///
+ ///
+ /// The transaction identifier.
+ ///
+ [JsonProperty]
+ public Guid TransactionId { get; private set; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates the specified aggregate identifier.
+ ///
+ /// The aggregate identifier.
+ /// The estate identifier.
+ /// The contract identifier.
+ /// The product identifier.
+ ///
+ public static ProductDetailsAddedToTransactionEvent Create(Guid aggregateId,
+ Guid estateId,
+ Guid contractId,
+ Guid productId)
+ {
+ return new ProductDetailsAddedToTransactionEvent(aggregateId, Guid.NewGuid(), estateId, contractId, productId);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs
index cb2dcbbc..ab646489 100644
--- a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs
+++ b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs
@@ -230,5 +230,21 @@ public void CustomerEmailReceiptRequestedEvent_CanBeCreated_IsCreated()
customerEmailReceiptRequestedEvent.MerchantId.ShouldBe(TestData.MerchantId);
customerEmailReceiptRequestedEvent.CustomerEmailAddress.ShouldBe(TestData.CustomerEmailAddress);
}
+
+ [Fact]
+ public void ProductDetailsAddedToTransactionEvent_CanBeCreated_IsCreated()
+ {
+ ProductDetailsAddedToTransactionEvent productDetailsAddedToTransactionEvent = ProductDetailsAddedToTransactionEvent.Create(TestData.TransactionId, TestData.EstateId,TestData.ContractId, TestData.ProductId);
+
+ productDetailsAddedToTransactionEvent.ShouldNotBeNull();
+ productDetailsAddedToTransactionEvent.AggregateId.ShouldBe(TestData.TransactionId);
+ productDetailsAddedToTransactionEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue);
+ productDetailsAddedToTransactionEvent.EventId.ShouldNotBe(Guid.Empty);
+ productDetailsAddedToTransactionEvent.TransactionId.ShouldBe(TestData.TransactionId);
+ productDetailsAddedToTransactionEvent.EstateId.ShouldBe(TestData.EstateId);
+ productDetailsAddedToTransactionEvent.ProductId.ShouldBe(TestData.ProductId);
+ productDetailsAddedToTransactionEvent.ContractId.ShouldBe(TestData.ContractId);
+
+ }
}
}
diff --git a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs
index 82678be7..aee7b755 100644
--- a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs
+++ b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs
@@ -18,7 +18,6 @@ public void TransactionAggregate_CanBeCreated_IsCreated()
}
[Theory]
- [InlineData(TransactionType.Logon)]
[InlineData(TransactionType.Sale)]
public void TransactionAggregate_StartTransaction_TransactionIsStarted(TransactionType transactionType)
{
@@ -38,6 +37,25 @@ public void TransactionAggregate_StartTransaction_TransactionIsStarted(Transacti
transactionAggregate.TransactionAmount.ShouldBe(TestData.TransactionAmount);
}
+ [Theory]
+ [InlineData(TransactionType.Logon)]
+ public void TransactionAggregate_StartTransaction_NullAmount_TransactionIsStarted(TransactionType transactionType)
+ {
+ TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId);
+ transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType,
+ TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, null);
+
+ transactionAggregate.IsStarted.ShouldBeTrue();
+ transactionAggregate.TransactionDateTime.ShouldBe(TestData.TransactionDateTime);
+ transactionAggregate.TransactionNumber.ShouldBe(TestData.TransactionNumber);
+ transactionAggregate.TransactionType.ShouldBe(transactionType);
+ transactionAggregate.EstateId.ShouldBe(TestData.EstateId);
+ transactionAggregate.MerchantId.ShouldBe(TestData.MerchantId);
+ transactionAggregate.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier);
+ transactionAggregate.TransactionReference.ShouldBe(TestData.TransactionReference);
+ transactionAggregate.TransactionAmount.ShouldBeNull();
+ }
+
[Theory]
[InlineData(TransactionType.Logon)]
[InlineData(TransactionType.Sale)]
@@ -144,6 +162,65 @@ public void TransactionAggregate_StartTransaction_InvalidData_ErrorThrown(Boolea
});
}
+ [Theory]
+ [InlineData(TransactionType.Sale)]
+ public void TransactionAggregate_AddProductDetails_ProductDetailsAdded(TransactionType transactionType)
+ {
+ TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId);
+ transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier,
+ TestData.TransactionAmount);
+
+ transactionAggregate.AddProductDetails(TestData.ContractId,TestData.ProductId);
+
+ transactionAggregate.IsProductDetailsAdded.ShouldBeTrue();
+ transactionAggregate.ContractId.ShouldBe(TestData.ContractId);
+ transactionAggregate.ProductId.ShouldBe(TestData.ProductId);
+ }
+
+ [Theory]
+ [InlineData(TransactionType.Sale)]
+ public void TransactionAggregate_AddProductDetails_InvalidContractId_ErrorThrown(TransactionType transactionType)
+ {
+ TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId);
+ transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier,
+ TestData.TransactionAmount);
+
+ Should.Throw(() =>
+ {
+ transactionAggregate.AddProductDetails(Guid.Empty, TestData.ProductId);
+ });
+ }
+
+ [Theory]
+ [InlineData(TransactionType.Sale)]
+ public void TransactionAggregate_AddProductDetails_InvalidProductId_ErrorThrown(TransactionType transactionType)
+ {
+ TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId);
+ transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier,
+ TestData.TransactionAmount);
+
+ Should.Throw(() =>
+ {
+ transactionAggregate.AddProductDetails(TestData.ContractId, Guid.Empty);
+ });
+ }
+
+ [Theory]
+ [InlineData(TransactionType.Sale)]
+ public void TransactionAggregate_AddProductDetails_ProductDetailsAlreadyAdded_ErrorThrown(TransactionType transactionType)
+ {
+ TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId);
+ transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier,
+ TestData.TransactionAmount);
+
+ transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId);
+
+ Should.Throw(() =>
+ {
+ transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId);
+ });
+ }
+
[Theory]
[InlineData(TransactionType.Logon)]
public void TransactionAggregate_AuthoriseTransactionLocally_TransactionIsAuthorised(TransactionType transactionType)
diff --git a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs
index 9362baf5..19de8f09 100644
--- a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs
+++ b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs
@@ -88,6 +88,21 @@ private TransactionAggregate(Guid aggregateId)
///
public Guid EstateId { get; private set; }
+ ///
+ /// Gets the contract identifier.
+ ///
+ ///
+ /// The contract identifier.
+ ///
+ public Guid ContractId { get; private set; }
+ ///
+ /// Gets the product identifier.
+ ///
+ ///
+ /// The product identifier.
+ ///
+ public Guid ProductId { get; private set; }
+
///
/// Gets a value indicating whether this instance is authorised.
///
@@ -471,6 +486,27 @@ public void StartTransaction(DateTime transactionDateTime,
this.ApplyAndPend(transactionHasStartedEvent);
}
+ ///
+ /// Adds the product details.
+ ///
+ /// The contract identifier.
+ /// The product identifier.
+ public void AddProductDetails(Guid contractId,
+ Guid productId)
+ {
+ Guard.ThrowIfInvalidGuid(contractId, typeof(ArgumentException), $"Contract Id must not be [{Guid.Empty}]");
+ Guard.ThrowIfInvalidGuid(productId, typeof(ArgumentException), $"Product Id must not be [{Guid.Empty}]");
+
+ this.CheckProductDetailsNotAlreadyAdded();
+
+ ProductDetailsAddedToTransactionEvent productDetailsAddedToTransactionEvent = ProductDetailsAddedToTransactionEvent.Create(this.AggregateId,
+ this.EstateId,
+ contractId,
+ productId);
+
+ this.ApplyAndPend(productDetailsAddedToTransactionEvent);
+ }
+
///
/// Gets the metadata.
///
@@ -493,6 +529,14 @@ protected override void PlayEvent(DomainEvent domainEvent)
this.PlayEvent((dynamic)domainEvent);
}
+ private void CheckProductDetailsNotAlreadyAdded()
+ {
+ if (this.IsProductDetailsAdded)
+ {
+ throw new InvalidOperationException("Product details already added");
+ }
+ }
+
///
/// Checks the additional request data not already recorded.
///
@@ -737,6 +781,21 @@ private void PlayEvent(TransactionDeclinedByOperatorEvent domainEvent)
this.ResponseMessage = domainEvent.ResponseMessage;
}
+ private void PlayEvent(ProductDetailsAddedToTransactionEvent domainEvent)
+ {
+ this.IsProductDetailsAdded = true;
+ this.ContractId = domainEvent.ContractId;
+ this.ProductId = domainEvent.ProductId;
+ }
+
+ ///
+ /// Gets a value indicating whether this instance is product details added.
+ ///
+ ///
+ /// true if this instance is product details added; otherwise, false.
+ ///
+ public Boolean IsProductDetailsAdded { get; private set; }
+
#endregion
}
}
\ No newline at end of file