From 84ea2b866a5f9cd5e289dc3a52512a1f76b4e463 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 2 Oct 2020 09:57:26 +0100 Subject: [PATCH 1/2] WIP --- .../TransactionDomainEventHandler.cs | 31 +++++- .../Safaricom/TransactionAuthorised.html | 11 ++ .../Services/IMessageTemplateReader.cs | 38 +++++++ .../TransactionProcessor.BusinessLogic.csproj | 1 + TransactionProcessor.Models/Transaction.cs | 11 ++ .../CustomerEmailReceiptRequestedEvent.cs | 101 +++++++++++++----- 6 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html create mode 100644 TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs create mode 100644 TransactionProcessor.Models/Transaction.cs diff --git a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs index 416b375c..737b6877 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs @@ -42,6 +42,8 @@ public class TransactionDomainEventHandler : IDomainEventHandler /// private readonly ISecurityServiceClient SecurityServiceClient; + private readonly ITransactionReceiptBuilder TransactionReceiptBuilder; + /// /// The token response /// @@ -57,21 +59,24 @@ public class TransactionDomainEventHandler : IDomainEventHandler #region Constructors /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transaction aggregate manager. /// The fee calculation manager. /// The estate client. /// The security service client. + /// The transaction receipt builder. public TransactionDomainEventHandler(ITransactionAggregateManager transactionAggregateManager, IFeeCalculationManager feeCalculationManager, IEstateClient estateClient, - ISecurityServiceClient securityServiceClient) + ISecurityServiceClient securityServiceClient, + ITransactionReceiptBuilder transactionReceiptBuilder) { this.TransactionAggregateManager = transactionAggregateManager; this.FeeCalculationManager = feeCalculationManager; this.EstateClient = estateClient; this.SecurityServiceClient = securityServiceClient; + this.TransactionReceiptBuilder = transactionReceiptBuilder; } #endregion @@ -179,6 +184,28 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do } } + private async Task HandleSpecificDomainEvent(CustomerEmailReceiptRequestedEvent domainEvent, + CancellationToken cancellationToken) + { + TransactionAggregate transactionAggregate = await this.TransactionAggregateManager.GetAggregate(domainEvent.EstateId, domainEvent.TransactionId, cancellationToken); + + // TODO: Add DTO method to aggregate + // Determine the body of the email + var receiptMessage = await this.TransactionReceiptBuilder.GetEmailReceiptMessage(new Transaction(), cancellationToken); + + // Send the message + await this.SendEmailMessage("Transaction Successful", receiptMessage, domainEvent.CustomerEmailAddress, cancellationToken); + + } + + private async Task SendEmailMessage(String subject, + String body, + String emailAddress, + CancellationToken cancellationToken) + { + + } + #endregion } } \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html b/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html new file mode 100644 index 00000000..755f822e --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs b/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs new file mode 100644 index 00000000..2a52ddbe --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.BusinessLogic.Services +{ + using System.IO.Abstractions; + using System.Threading; + using System.Threading.Tasks; + using Models; + + public interface ITransactionReceiptBuilder + { + Task GetEmailReceiptMessage(Transaction transaction, CancellationToken cancellationToken); + } + + public class TransactionReceiptBuilder : ITransactionReceiptBuilder + { + private readonly IFileSystem FileSystem; + + public TransactionReceiptBuilder(IFileSystem fileSystem) + { + this.FileSystem = fileSystem; + } + + public async Task GetEmailReceiptMessage(Transaction transaction, + CancellationToken cancellationToken) + { + var fileData = await this.FileSystem.File.ReadAllTextAsync($"\\Receipts\\Email\\{transaction.OperatorIdentifier}\\TransactionAuthorised.html", cancellationToken); + + // TODO: We will do substitutions here + + return fileData; + } + } + + +} diff --git a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj index e09f289b..f4de8892 100644 --- a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj +++ b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj @@ -11,6 +11,7 @@ + diff --git a/TransactionProcessor.Models/Transaction.cs b/TransactionProcessor.Models/Transaction.cs new file mode 100644 index 00000000..5b9928b0 --- /dev/null +++ b/TransactionProcessor.Models/Transaction.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.Models +{ + public class Transaction + { + public String OperatorIdentifier { get; set; } + } +} diff --git a/TransactionProcessor.Transaction.DomainEvents/CustomerEmailReceiptRequestedEvent.cs b/TransactionProcessor.Transaction.DomainEvents/CustomerEmailReceiptRequestedEvent.cs index 0e21d3d7..dfa9a6f3 100644 --- a/TransactionProcessor.Transaction.DomainEvents/CustomerEmailReceiptRequestedEvent.cs +++ b/TransactionProcessor.Transaction.DomainEvents/CustomerEmailReceiptRequestedEvent.cs @@ -1,23 +1,76 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace TransactionProcessor.Transaction.DomainEvents +namespace TransactionProcessor.Transaction.DomainEvents { + using System; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using Shared.DomainDrivenDesign.EventSourcing; + /// + /// + /// + /// public class CustomerEmailReceiptRequestedEvent : DomainEvent { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + [ExcludeFromCodeCoverage] + public CustomerEmailReceiptRequestedEvent() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The merchant identifier. + /// The customer email address. + private CustomerEmailReceiptRequestedEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid merchantId, + String customerEmailAddress) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.MerchantId = merchantId; + this.CustomerEmailAddress = customerEmailAddress; + } + + #endregion + + #region Properties + + /// + /// Gets the customer email address. + /// + /// + /// The customer email address. + /// [JsonProperty] - public Guid EstateId { get; private set; } + public String CustomerEmailAddress { get; private set; } + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// [JsonProperty] - public Guid MerchantId { get; private set; } + public Guid EstateId { get; private set; } + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// [JsonProperty] - public String CustomerEmailAddress { get; private set; } + public Guid MerchantId { get; private set; } /// /// Gets the transaction identifier. @@ -28,30 +81,26 @@ public class CustomerEmailReceiptRequestedEvent : DomainEvent [JsonProperty] public Guid TransactionId { get; private set; } - [ExcludeFromCodeCoverage] - public CustomerEmailReceiptRequestedEvent() - { + #endregion - } - - private CustomerEmailReceiptRequestedEvent(Guid aggregateId, - Guid eventId, - Guid estateId, - Guid merchantId, - String customerEmailAddress) : base(aggregateId, eventId) - { - this.TransactionId = aggregateId; - this.EstateId = estateId; - this.MerchantId = merchantId; - this.CustomerEmailAddress = customerEmailAddress; - } + #region Methods + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The merchant identifier. + /// The customer email address. + /// public static CustomerEmailReceiptRequestedEvent Create(Guid aggregateId, Guid estateId, Guid merchantId, String customerEmailAddress) { - return new CustomerEmailReceiptRequestedEvent(aggregateId,Guid.NewGuid(), estateId,merchantId, customerEmailAddress); + return new CustomerEmailReceiptRequestedEvent(aggregateId, Guid.NewGuid(), estateId, merchantId, customerEmailAddress); } + + #endregion } -} +} \ No newline at end of file From 1369f1634825f43ffbcbdef2ff5b095f9dea85f1 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Wed, 7 Oct 2020 20:51:14 +0100 Subject: [PATCH 2/2] Receipt sending implemented --- .../TransactionDomainEventHandlerTests.cs | 64 +++++++++++-- .../TransactionReceiptBuilderTests.cs | 46 ++++++++++ ...actionProcessor.BusinessLogic.Tests.csproj | 1 + .../TransactionDomainEventHandler.cs | 53 +++++++++-- .../Safaricom/TransactionAuthorised.html | 9 ++ .../Services/IMessageTemplateReader.cs | 51 +++++------ .../Services/TransactionReceiptBuilder.cs | 77 ++++++++++++++++ .../TransactionProcessor.BusinessLogic.csproj | 7 ++ TransactionProcessor.Models/Transaction.cs | 89 +++++++++++++++++-- TransactionProcessor.Testing/TestData.cs | 3 + .../TransactionAggregateTests.cs | 6 ++ .../TransactionAggregate.cs | 25 ++++++ TransactionProcessor/Startup.cs | 6 ++ TransactionProcessor/appsettings.json | 4 + 14 files changed, 394 insertions(+), 47 deletions(-) create mode 100644 TransactionProcessor.BusinessLogic.Tests/Services/TransactionReceiptBuilderTests.cs create mode 100644 TransactionProcessor.BusinessLogic/Services/TransactionReceiptBuilder.cs diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs index 24ffbbcd..72fb028f 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs @@ -8,6 +8,7 @@ using BusinessLogic.Services; using EstateManagement.Client; using EventHandling; + using MessagingService.Client; using Microsoft.Extensions.Configuration; using Moq; using SecurityService.Client; @@ -39,6 +40,8 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet Mock securityServiceClient = new Mock(); securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); @@ -47,7 +50,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, feeCalculationManager.Object, estateClient.Object, - securityServiceClient.Object); + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -63,6 +68,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet Mock securityServiceClient = new Mock(); + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); @@ -70,7 +78,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, feeCalculationManager.Object, estateClient.Object, - securityServiceClient.Object); + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -86,6 +96,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet Mock securityServiceClient = new Mock(); + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); @@ -93,7 +106,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, feeCalculationManager.Object, estateClient.Object, - securityServiceClient.Object); + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -109,6 +124,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet Mock securityServiceClient = new Mock(); + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); @@ -116,7 +134,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, feeCalculationManager.Object, estateClient.Object, - securityServiceClient.Object); + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } @@ -132,6 +152,9 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet Mock securityServiceClient = new Mock(); + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); ConfigurationReader.Initialise(configurationRoot); Logger.Initialise(NullLogger.Instance); @@ -139,9 +162,40 @@ public async Task TransactionDomainEventHandler_Handle_TransactionHasBeenComplet TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, feeCalculationManager.Object, estateClient.Object, - securityServiceClient.Object); + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); await transactionDomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); } + + [Fact] + public async Task TransactionDomainEventHandler_Handle_CustomerEmailReceiptRequestedEvent_EventIsHandled() + { + Mock transactionAggregateManager = new Mock(); + transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(TestData.GetCompletedAuthorisedSaleTransactionAggregate); + Mock feeCalculationManager = new Mock(); + Mock estateClient = new Mock(); + + Mock securityServiceClient = new Mock(); + securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(TestData.TokenResponse); + + Mock transactionReceiptBulder = new Mock(); + Mock messagingServiceClient = new Mock(); + + IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build(); + ConfigurationReader.Initialise(configurationRoot); + Logger.Initialise(NullLogger.Instance); + + TransactionDomainEventHandler transactionDomainEventHandler = new TransactionDomainEventHandler(transactionAggregateManager.Object, + feeCalculationManager.Object, + estateClient.Object, + securityServiceClient.Object, + transactionReceiptBulder.Object, + messagingServiceClient.Object); + + await transactionDomainEventHandler.Handle(TestData.CustomerEmailReceiptRequestedEvent, CancellationToken.None); + } } } diff --git a/TransactionProcessor.BusinessLogic.Tests/Services/TransactionReceiptBuilderTests.cs b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionReceiptBuilderTests.cs new file mode 100644 index 00000000..5f9bdaa1 --- /dev/null +++ b/TransactionProcessor.BusinessLogic.Tests/Services/TransactionReceiptBuilderTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.BusinessLogic.Tests.Services +{ + using System.IO; + using System.IO.Abstractions; + using System.IO.Abstractions.TestingHelpers; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using BusinessLogic.Services; + using EstateManagement.DataTransferObjects.Responses; + using Models; + using Shouldly; + using Xunit; + + public class TransactionReceiptBuilderTests + { + [Fact] + public async Task TransactionReceiptBuilder_GetEmailReceiptMessage_MessageBuilt() + { + Transaction transaction = new Transaction + { + OperatorIdentifier = "Safaricom", + TransactionNumber = "12345" + }; + + var path = Directory.GetParent(Assembly.GetExecutingAssembly().Location); + + var fileSystem = new MockFileSystem(new Dictionary + { + { $"{path}\\Receipts\\Email\\{transaction.OperatorIdentifier}\\TransactionAuthorised.html", new MockFileData("Transaction Number: [TransactionNumber]") } + }); + + TransactionReceiptBuilder receiptBuilder = new TransactionReceiptBuilder(fileSystem); + + String receiptMessage = await receiptBuilder.GetEmailReceiptMessage(transaction, new MerchantResponse(), CancellationToken.None); + + receiptMessage.ShouldBe("Transaction Number: 12345"); + + + } + } +} diff --git a/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj b/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj index ca9e1665..6537784a 100644 --- a/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj +++ b/TransactionProcessor.BusinessLogic.Tests/TransactionProcessor.BusinessLogic.Tests.csproj @@ -10,6 +10,7 @@ + all diff --git a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs index 737b6877..d1db7d20 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs @@ -9,6 +9,8 @@ using EstateManagement.DataTransferObjects.Responses; using Manager; using MessagingService.BusinessLogic.EventHandling; + using MessagingService.Client; + using MessagingService.DataTransferObjects; using Models; using SecurityService.Client; using SecurityService.DataTransferObjects.Responses; @@ -44,6 +46,8 @@ public class TransactionDomainEventHandler : IDomainEventHandler private readonly ITransactionReceiptBuilder TransactionReceiptBuilder; + private readonly IMessagingServiceClient MessagingServiceClient; + /// /// The token response /// @@ -66,17 +70,20 @@ public class TransactionDomainEventHandler : IDomainEventHandler /// The estate client. /// The security service client. /// The transaction receipt builder. + /// The messaging service client. public TransactionDomainEventHandler(ITransactionAggregateManager transactionAggregateManager, IFeeCalculationManager feeCalculationManager, IEstateClient estateClient, ISecurityServiceClient securityServiceClient, - ITransactionReceiptBuilder transactionReceiptBuilder) + ITransactionReceiptBuilder transactionReceiptBuilder, + IMessagingServiceClient messagingServiceClient) { this.TransactionAggregateManager = transactionAggregateManager; this.FeeCalculationManager = feeCalculationManager; this.EstateClient = estateClient; this.SecurityServiceClient = securityServiceClient; this.TransactionReceiptBuilder = transactionReceiptBuilder; + this.MessagingServiceClient = messagingServiceClient; } #endregion @@ -184,26 +191,60 @@ private async Task HandleSpecificDomainEvent(TransactionHasBeenCompletedEvent do } } + /// + /// Handles the specific domain event. + /// + /// The domain event. + /// The cancellation token. private async Task HandleSpecificDomainEvent(CustomerEmailReceiptRequestedEvent domainEvent, CancellationToken cancellationToken) { + this.TokenResponse = await this.GetToken(cancellationToken); + TransactionAggregate transactionAggregate = await this.TransactionAggregateManager.GetAggregate(domainEvent.EstateId, domainEvent.TransactionId, cancellationToken); - // TODO: Add DTO method to aggregate + var merchant = await this.EstateClient.GetMerchant(this.TokenResponse.AccessToken, domainEvent.EstateId, domainEvent.MerchantId, cancellationToken); + // Determine the body of the email - var receiptMessage = await this.TransactionReceiptBuilder.GetEmailReceiptMessage(new Transaction(), cancellationToken); + String receiptMessage = await this.TransactionReceiptBuilder.GetEmailReceiptMessage(transactionAggregate.GetTransaction(), merchant, cancellationToken); // Send the message - await this.SendEmailMessage("Transaction Successful", receiptMessage, domainEvent.CustomerEmailAddress, cancellationToken); + await this.SendEmailMessage(this.TokenResponse.AccessToken, domainEvent.EstateId, "Transaction Successful", receiptMessage, domainEvent.CustomerEmailAddress, cancellationToken); } - private async Task SendEmailMessage(String subject, + /// + /// Sends the email message. + /// + /// The access token. + /// The estate identifier. + /// The subject. + /// The body. + /// The email address. + /// The cancellation token. + private async Task SendEmailMessage(String accessToken, + Guid estateId, + String subject, String body, String emailAddress, CancellationToken cancellationToken) { - + SendEmailRequest sendEmailRequest = new SendEmailRequest + { + Body = body, + ConnectionIdentifier = estateId, + FromAddress = "golfhandicapping@btinternet.com", // TODO: lookup from config + IsHtml = true, + Subject = subject, + ToAddresses = new List + { + emailAddress + } + }; + + // TODO: may decide to record the message Id againsts the Transaction Aggregate in future, but for now + // we wont do this... + await this.MessagingServiceClient.SendEmail(accessToken, sendEmailRequest, cancellationToken); } #endregion diff --git a/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html b/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html index 755f822e..577da089 100644 --- a/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html +++ b/TransactionProcessor.BusinessLogic/Receipts/Email/Safaricom/TransactionAuthorised.html @@ -6,6 +6,15 @@ +

[OperatorIdentifier] Transaction Receipt

+

Transaction Details

+

Merchant: [MerchantName]

+

Amount: [TransactionAmount]

+

Auth Code: [AuthorisationCode]

+

Message: [ResponseMessage]

+

Txn Number: [TransactionNumber]

+

Ref: [TransactionReference]

+

Operator Ref: [OperatorTransactionId]

\ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs b/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs index 2a52ddbe..9f79f616 100644 --- a/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs +++ b/TransactionProcessor.BusinessLogic/Services/IMessageTemplateReader.cs @@ -1,38 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace TransactionProcessor.BusinessLogic.Services +namespace TransactionProcessor.BusinessLogic.Services { - using System.IO.Abstractions; + using System; using System.Threading; using System.Threading.Tasks; + using EstateManagement.DataTransferObjects.Responses; using Models; + /// + /// + /// public interface ITransactionReceiptBuilder { - Task GetEmailReceiptMessage(Transaction transaction, CancellationToken cancellationToken); + #region Methods + + /// + /// Gets the email receipt message. + /// + /// The transaction. + /// The merchant. + /// The cancellation token. + /// + Task GetEmailReceiptMessage(Transaction transaction, + MerchantResponse merchant, + CancellationToken cancellationToken); + + #endregion } - - public class TransactionReceiptBuilder : ITransactionReceiptBuilder - { - private readonly IFileSystem FileSystem; - - public TransactionReceiptBuilder(IFileSystem fileSystem) - { - this.FileSystem = fileSystem; - } - - public async Task GetEmailReceiptMessage(Transaction transaction, - CancellationToken cancellationToken) - { - var fileData = await this.FileSystem.File.ReadAllTextAsync($"\\Receipts\\Email\\{transaction.OperatorIdentifier}\\TransactionAuthorised.html", cancellationToken); - - // TODO: We will do substitutions here - - return fileData; - } - } - - -} +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionReceiptBuilder.cs b/TransactionProcessor.BusinessLogic/Services/TransactionReceiptBuilder.cs new file mode 100644 index 00000000..a36656ac --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Services/TransactionReceiptBuilder.cs @@ -0,0 +1,77 @@ +namespace TransactionProcessor.BusinessLogic.Services +{ + using System; + using System.IO.Abstractions; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using EstateManagement.DataTransferObjects.Responses; + using Models; + + /// + /// + /// + /// + public class TransactionReceiptBuilder : ITransactionReceiptBuilder + { + #region Fields + + /// + /// The file system + /// + private readonly IFileSystem FileSystem; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + public TransactionReceiptBuilder(IFileSystem fileSystem) + { + this.FileSystem = fileSystem; + } + + #endregion + + #region Methods + + /// + /// Gets the email receipt message. + /// + /// The transaction. + /// The merchant. + /// The cancellation token. + /// + public async Task GetEmailReceiptMessage(Transaction transaction, + MerchantResponse merchant, + CancellationToken cancellationToken) + { + IDirectoryInfo path = this.FileSystem.Directory.GetParent(Assembly.GetExecutingAssembly().Location); + + String fileData = + await this.FileSystem.File.ReadAllTextAsync($"{path}\\Receipts\\Email\\{transaction.OperatorIdentifier}\\TransactionAuthorised.html", cancellationToken); + + PropertyInfo[] transactonProperties = transaction.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + // Do the replaces for the transaction + foreach (PropertyInfo propertyInfo in transactonProperties) + { + fileData = fileData.Replace($"[{propertyInfo.Name}]", propertyInfo.GetValue(transaction)?.ToString()); + } + + PropertyInfo[] merchantProperties = merchant.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + // Do the replaces for the merchant + foreach (PropertyInfo propertyInfo in merchantProperties) + { + fileData = fileData.Replace($"[{propertyInfo.Name}]", propertyInfo.GetValue(merchant)?.ToString()); + } + + return fileData; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj index f4de8892..58b03ea7 100644 --- a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj +++ b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj @@ -6,6 +6,7 @@ + @@ -19,4 +20,10 @@ + + + Always + + + diff --git a/TransactionProcessor.Models/Transaction.cs b/TransactionProcessor.Models/Transaction.cs index 5b9928b0..fb7eb43a 100644 --- a/TransactionProcessor.Models/Transaction.cs +++ b/TransactionProcessor.Models/Transaction.cs @@ -1,11 +1,88 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace TransactionProcessor.Models +namespace TransactionProcessor.Models { + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// + /// + [ExcludeFromCodeCoverage] public class Transaction { + #region Properties + + /// + /// Gets or sets the authorisation code. + /// + /// + /// The authorisation code. + /// + public String AuthorisationCode { get; set; } + + /// + /// Gets or sets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + public Guid MerchantId { get; set; } + + /// + /// Gets or sets the operator identifier. + /// + /// + /// The operator identifier. + /// public String OperatorIdentifier { get; set; } + + /// + /// Gets or sets the operator transaction identifier. + /// + /// + /// The operator transaction identifier. + /// + public String OperatorTransactionId { get; set; } + + /// + /// Gets or sets the response message. + /// + /// + /// The response message. + /// + public String ResponseMessage { get; set; } + + /// + /// Gets or sets the transaction amount. + /// + /// + /// The transaction amount. + /// + public Decimal TransactionAmount { get; set; } + + /// + /// Gets or sets the transaction date time. + /// + /// + /// The transaction date time. + /// + public DateTime TransactionDateTime { get; set; } + + /// + /// Gets or sets the transaction number. + /// + /// + /// The transaction number. + /// + public String TransactionNumber { get; set; } + + /// + /// Gets or sets the transaction reference. + /// + /// + /// The transaction reference. + /// + public String TransactionReference { get; set; } + + #endregion } -} +} \ No newline at end of file diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index 70712bdf..bee1835b 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -661,6 +661,9 @@ public static TokenResponse TokenResponse() return SecurityService.DataTransferObjects.Responses.TokenResponse.Create("AccessToken", string.Empty, 100); } + public static CustomerEmailReceiptRequestedEvent CustomerEmailReceiptRequestedEvent = + CustomerEmailReceiptRequestedEvent.Create(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, TestData.CustomerEmailAddress); + public static TransactionHasBeenCompletedEvent TransactionHasBeenCompletedEvent = TransactionHasBeenCompletedEvent.Create(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, diff --git a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs index 18527b77..1ce85f52 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs +++ b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs @@ -37,6 +37,9 @@ public void TransactionAggregate_StartTransaction_TransactionIsStarted(Transacti transactionAggregate.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier); transactionAggregate.TransactionReference.ShouldBe(TestData.TransactionReference); transactionAggregate.TransactionAmount.ShouldBe(TestData.TransactionAmount); + + Transaction transaction = transactionAggregate.GetTransaction(); + transaction.ShouldNotBeNull(); } [Theory] @@ -56,6 +59,9 @@ public void TransactionAggregate_StartTransaction_NullAmount_TransactionIsStarte transactionAggregate.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier); transactionAggregate.TransactionReference.ShouldBe(TestData.TransactionReference); transactionAggregate.TransactionAmount.ShouldBeNull(); + + Transaction transaction = transactionAggregate.GetTransaction(); + transaction.ShouldNotBeNull(); } [Theory] diff --git a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs index 0bff21d2..90dd84fb 100644 --- a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs +++ b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs @@ -35,6 +35,14 @@ public class TransactionAggregate : Aggregate ///
private readonly List CalculatedFees; + /// + /// Gets the operator identifier. + /// + /// + /// The operator identifier. + /// + public String OperatorIdentifier { get; private set; } + #endregion #region Constructors @@ -807,6 +815,7 @@ private void PlayEvent(CustomerEmailReceiptRequestedEvent domainEvent) private void PlayEvent(AdditionalRequestDataRecordedEvent domainEvent) { this.AdditionalTransactionRequestMetadata = domainEvent.AdditionalTransactionRequestMetadata; + this.OperatorIdentifier = domainEvent.OperatorIdentifier; } /// @@ -944,5 +953,21 @@ private void PlayEvent(ServiceProviderFeeAddedToTransactionEvent domainEvent) } #endregion + + public Transaction GetTransaction() + { + return new Transaction + { + AuthorisationCode = this.AuthorisationCode, + MerchantId = this.MerchantId, + OperatorTransactionId = this.OperatorTransactionId, + ResponseMessage = this.ResponseMessage, + TransactionAmount = this.TransactionAmount.HasValue ? this.TransactionAmount.Value : 0, + TransactionDateTime = this.TransactionDateTime, + TransactionNumber = this.TransactionNumber, + TransactionReference = this.TransactionReference, + OperatorIdentifier = this.OperatorIdentifier + }; + } } } \ No newline at end of file diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs index cff4d988..f4d5ac4f 100644 --- a/TransactionProcessor/Startup.cs +++ b/TransactionProcessor/Startup.cs @@ -14,6 +14,7 @@ namespace TransactionProcessor { using System.Diagnostics.CodeAnalysis; using System.IO; + using System.IO.Abstractions; using System.Net.Http; using System.Reflection; using BusinessLogic.EventHandling; @@ -29,6 +30,7 @@ namespace TransactionProcessor using HealthChecks.UI.Client; using MediatR; using MessagingService.BusinessLogic.EventHandling; + using MessagingService.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -124,6 +126,10 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(container => (serviceName) => { return ConfigurationReader.GetBaseServerUri(serviceName).OriginalString; diff --git a/TransactionProcessor/appsettings.json b/TransactionProcessor/appsettings.json index 4808d659..e70e7bfa 100644 --- a/TransactionProcessor/appsettings.json +++ b/TransactionProcessor/appsettings.json @@ -23,11 +23,15 @@ "UseConnectionStringConfig": false, "SecurityService": "http://192.168.1.133:5001", "EstateManagementApi": "http://192.168.1.133:5000", + "MessagingServiceApi": "http://192.168.1.133:5006", "ClientId": "serviceClient", "ClientSecret": "d192cbc46d834d0da90e8a9d50ded543", "EventHandlerConfiguration": { "TransactionProcessor.Transaction.DomainEvents.TransactionHasBeenCompletedEvent": [ "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler" + ], + "TransactionProcessor.Transaction.DomainEvents.CustomerEmailReceiptRequestedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler" ] } },