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 @@ -506,7 +506,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsNotAuthorised
transactionAggregate.DeclineTransaction(TestData.OperatorId, "111", "SUCCESS", "0000", "SUCCESS");

// TODO: maybe move this to an extension on aggregate
var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeFalse();
}

Expand All @@ -520,7 +520,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsNotCompelted_
TestData.TransactionAmount);
transactionAggregate.AuthoriseTransaction(TestData.OperatorId, "111", "111", "SUCCESS", "1234", "0000", "SUCCESS");

var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeFalse();
}

Expand All @@ -536,7 +536,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_IsALogon_Return
transactionAggregate.CompleteTransaction();


var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeFalse();
}

Expand All @@ -552,7 +552,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_NoContractId_Re
transactionAggregate.CompleteTransaction();


var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeFalse();
}

Expand All @@ -569,7 +569,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_NullAmount_Retu
transactionAggregate.CompleteTransaction();


var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeFalse();
}

Expand All @@ -586,7 +586,7 @@ public async Task TransactionDomainService_RequireFeeCalculation_ReturnsTrue()
transactionAggregate.CompleteTransaction();


var result = TransactionDomainService.RequireFeeCalculation(transactionAggregate);
var result = TransactionHelpers.RequireFeeCalculation(transactionAggregate);
result.ShouldBeTrue();
}

Expand All @@ -599,7 +599,7 @@ public async Task TransactionDomainService_CalculateSettlementDate_CorrectDateRe

DateTime completedDate = DateTime.ParseExact(completedDateString, "yyyy-MM-dd", null);
DateTime expectedDate = DateTime.ParseExact(expectedDateString, "yyyy-MM-dd", null);
DateTime result = TransactionDomainService.CalculateSettlementDate(settlementSchedule, completedDate);
DateTime result = TransactionHelpers.CalculateSettlementDate(settlementSchedule, completedDate);
result.Date.ShouldBe(expectedDate.Date);
}

Expand Down
60 changes: 60 additions & 0 deletions TransactionProcessor.BusinessLogic/Common/TransactionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using TransactionProcessor.Aggregates;
using TransactionProcessor.Models;

namespace TransactionProcessor.BusinessLogic.Common;

public static class TransactionHelpers {
[ExcludeFromCodeCoverage]
public static String GenerateTransactionReference()
{
Int64 i = 1;
foreach (Byte b in Guid.NewGuid().ToByteArray())
{
i *= (b + 1);
}

return $"{i - DateTime.Now.Ticks:x}";
}

[ExcludeFromCodeCoverage]
public static string GenerateAuthCode()
{
// 8 hex characters == 4 random bytes
byte[] bytes = RandomNumberGenerator.GetBytes(4); // .NET 6+; for older frameworks use RandomNumberGenerator.Create().GetBytes(bytes)
// Convert to uppercase hex string (0-9, A-F). Convert.ToHexString returns uppercase.
string hex = Convert.ToHexString(bytes);
return hex.Substring(0, 8);
}

public static DateTime CalculateSettlementDate(Models.Merchant.SettlementSchedule merchantSettlementSchedule,
DateTime completeDateTime)
{
if (merchantSettlementSchedule == Models.Merchant.SettlementSchedule.Weekly)
{
return completeDateTime.Date.AddDays(7).Date;
}

if (merchantSettlementSchedule == Models.Merchant.SettlementSchedule.Monthly)
{
return completeDateTime.Date.AddMonths(1).Date;
}

return completeDateTime.Date;
}

public static Boolean RequireFeeCalculation(TransactionAggregate transactionAggregate)
{
return transactionAggregate switch
{
_ when transactionAggregate.TransactionType == TransactionType.Logon => false,
_ when transactionAggregate.IsAuthorised == false => false,
_ when transactionAggregate.IsCompleted == false => false,
_ when transactionAggregate.ContractId == Guid.Empty => false,
_ when transactionAggregate.TransactionAmount == null => false,
_ => true
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Shared.Exceptions;
using Shared.General;
using Shared.Results;
using SimpleResults;
using TransactionProcessor.Aggregates;
Expand Down Expand Up @@ -90,7 +91,6 @@ public TransactionDomainService(Func<IAggregateService> aggregateService,

public async Task<Result<ProcessLogonTransactionResponse>> ProcessLogonTransaction(TransactionCommands.ProcessLogonTransactionCommand command,
CancellationToken cancellationToken) {

try {
Result<TransactionAggregate> transactionResult = await DomainServiceHelper.GetAggregateOrFailure(ct => this.AggregateService.GetLatest<TransactionAggregate>(command.TransactionId, ct), command.TransactionId, cancellationToken, false);
if (transactionResult.IsFailed)
Expand All @@ -100,7 +100,7 @@ public async Task<Result<ProcessLogonTransactionResponse>> ProcessLogonTransacti
TransactionType transactionType = TransactionType.Logon;

// Generate a transaction reference
String transactionReference = this.GenerateTransactionReference();
String transactionReference = TransactionHelpers.GenerateTransactionReference();

Result stateResult = transactionAggregate.StartTransaction(command.TransactionDateTime, command.TransactionNumber, transactionType, transactionReference, command.EstateId, command.MerchantId, command.DeviceIdentifier, null); // Logon transaction has no amount
if (stateResult.IsFailed)
Expand All @@ -114,8 +114,7 @@ public async Task<Result<ProcessLogonTransactionResponse>> ProcessLogonTransacti
}

// Record the successful validation
// TODO: Generate local authcode
stateResult = transactionAggregate.AuthoriseTransactionLocally("ABCD1234", ((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage);
stateResult = transactionAggregate.AuthoriseTransactionLocally(TransactionHelpers.GenerateAuthCode(), ((Int32)validationResult.Data.ResponseCode).ToString().PadLeft(4, '0'), validationResult.Data.ResponseMessage);
if (stateResult.IsFailed)
return ResultHelpers.CreateFailure(stateResult);
}
Expand Down Expand Up @@ -149,7 +148,7 @@ public async Task<Result<ProcessLogonTransactionResponse>> ProcessLogonTransacti
return Result.Failure(ex.GetExceptionMessages());
}
}

public async Task<Result<ProcessReconciliationTransactionResponse>> ProcessReconciliationTransaction(TransactionCommands.ProcessReconciliationCommand command,
CancellationToken cancellationToken) {
try{
Expand Down Expand Up @@ -209,7 +208,6 @@ public async Task<Result<ProcessSaleTransactionResponse>> ProcessSaleTransaction
CancellationToken cancellationToken) {
try
{

Result<TransactionAggregate> transactionResult = await DomainServiceHelper.GetAggregateOrFailure(ct => this.AggregateService.GetLatest<TransactionAggregate>(command.TransactionId, ct), command.TransactionId, cancellationToken, false);
if (transactionResult.IsFailed)
return ResultHelpers.CreateFailure(transactionResult);
Expand All @@ -220,7 +218,7 @@ public async Task<Result<ProcessSaleTransactionResponse>> ProcessSaleTransaction
TransactionSource transactionSourceValue = (TransactionSource)command.TransactionSource;

// Generate a transaction reference
String transactionReference = this.GenerateTransactionReference();
String transactionReference = TransactionHelpers.GenerateTransactionReference();

// Extract the transaction amount from the metadata
Decimal? transactionAmount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata<Decimal?>("Amount");
Expand Down Expand Up @@ -369,16 +367,7 @@ public async Task<Result> ResendTransactionReceipt(TransactionCommands.ResendTra
}
}

internal static Boolean RequireFeeCalculation(TransactionAggregate transactionAggregate) {
return transactionAggregate switch {
_ when transactionAggregate.TransactionType == TransactionType.Logon => false,
_ when transactionAggregate.IsAuthorised == false => false,
_ when transactionAggregate.IsCompleted == false => false,
_ when transactionAggregate.ContractId == Guid.Empty => false,
_ when transactionAggregate.TransactionAmount == null => false,
_ => true
};
}


public async Task<Result> CalculateFeesForTransaction(TransactionCommands.CalculateFeesForTransactionCommand command,
CancellationToken cancellationToken) {
Expand All @@ -390,7 +379,7 @@ public async Task<Result> CalculateFeesForTransaction(TransactionCommands.Calcul

TransactionAggregate transactionAggregate = transactionResult.Data;

if (RequireFeeCalculation(transactionAggregate) == false)
if (TransactionHelpers.RequireFeeCalculation(transactionAggregate) == false)
return Result.Success();

Result<List<TransactionFeeToCalculate>> feesForCalculationResult = await this.GetTransactionFeesForCalculation(transactionAggregate, cancellationToken);
Expand All @@ -414,13 +403,12 @@ public async Task<Result> CalculateFeesForTransaction(TransactionCommands.Calcul
if (merchantResult.IsFailed)
return ResultHelpers.CreateFailure(merchantResult);
if (merchantResult.Data.SettlementSchedule == Models.Merchant.SettlementSchedule.NotSet) {
// TODO: Result
//throw new NotSupportedException($"Merchant {merchant.MerchantId} does not have a settlement schedule configured");
return Result.Failure($"Merchant {merchantResult.Data.MerchantId} does not have a settlement schedule configured");
}

foreach (CalculatedFee calculatedFee in merchantFees) {
// Determine when the fee should be applied
DateTime settlementDate = CalculateSettlementDate(merchantResult.Data.SettlementSchedule, command.CompletedDateTime);
DateTime settlementDate = TransactionHelpers.CalculateSettlementDate(merchantResult.Data.SettlementSchedule, command.CompletedDateTime);

Result stateResult = transactionAggregate.AddFeePendingSettlement(calculatedFee, settlementDate);
if (stateResult.IsFailed)
Expand Down Expand Up @@ -532,20 +520,7 @@ public async Task<Result> ResendCustomerEmailReceipt(TransactionCommands.ResendC

return await this.ResendEmailMessage(this.TokenResponse.AccessToken, transactionAggregate.ReceiptMessageId, command.EstateId, cancellationToken);
}

internal static DateTime CalculateSettlementDate(Models.Merchant.SettlementSchedule merchantSettlementSchedule,
DateTime completeDateTime) {
if (merchantSettlementSchedule == Models.Merchant.SettlementSchedule.Weekly) {
return completeDateTime.Date.AddDays(7).Date;
}

if (merchantSettlementSchedule == Models.Merchant.SettlementSchedule.Monthly) {
return completeDateTime.Date.AddMonths(1).Date;
}

return completeDateTime.Date;
}


private async Task<Result<List<TransactionFeeToCalculate>>> GetTransactionFeesForCalculation(TransactionAggregate transactionAggregate,
CancellationToken cancellationToken) {
// Get the fees to be calculated
Expand All @@ -572,7 +547,6 @@ private async Task<Result<List<TransactionFeeToCalculate>>> GetTransactionFeesFo
private async Task<Result> AddDeviceToMerchant(Guid merchantId,
String deviceIdentifier,
CancellationToken cancellationToken) {
// TODO: Should this be firing a command to add the device??
// Add the device to the merchant
Result<MerchantAggregate> merchantAggregate = await this.AggregateService.GetLatest<MerchantAggregate>(merchantId, cancellationToken);
if (merchantAggregate.IsFailed)
Expand All @@ -583,15 +557,7 @@ private async Task<Result> AddDeviceToMerchant(Guid merchantId,
return await this.AggregateService.Save(merchantAggregate.Data, cancellationToken);
}

[ExcludeFromCodeCoverage]
private String GenerateTransactionReference() {
Int64 i = 1;
foreach (Byte b in Guid.NewGuid().ToByteArray()) {
i *= (b + 1);
}

return $"{i - DateTime.Now.Ticks:x}";
}


private async Task<Result<Merchant>> GetMerchant(Guid merchantId,
CancellationToken cancellationToken) {
Expand Down Expand Up @@ -622,21 +588,17 @@ private async Task<Result<OperatorResponse>> ProcessMessageWithOperator(Models.M
try {
Result<OperatorResponse> saleResult = await operatorProxy.ProcessSaleMessage(transactionId, operatorId, merchant, transactionDateTime, transactionReference, additionalTransactionMetadata, cancellationToken);
if (saleResult.IsFailed) {
return CreateFailedResult(new OperatorResponse { IsSuccessful = false, ResponseCode = "9999", ResponseMessage = saleResult.Message });
return ResultHelpers.CreateFailedResult(new OperatorResponse { IsSuccessful = false, ResponseCode = "9999", ResponseMessage = saleResult.Message });
}

return saleResult;
}
catch (Exception e) {
// Log out the error
Logger.LogError(e);
return CreateFailedResult(new OperatorResponse { IsSuccessful = false, ResponseCode = "9999", ResponseMessage = e.GetCombinedExceptionMessages() });
return ResultHelpers.CreateFailedResult(new OperatorResponse { IsSuccessful = false, ResponseCode = "9999", ResponseMessage = e.GetCombinedExceptionMessages() });
}
}

internal static Result<T> CreateFailedResult<T>(T resultData) {
return new Result<T> { IsSuccess = false, Data = resultData };
}

[ExcludeFromCodeCoverage]
private async Task<Result> SendEmailMessage(String accessToken,
Expand All @@ -650,14 +612,12 @@ private async Task<Result> SendEmailMessage(String accessToken,
MessageId = messageId,
Body = body,
ConnectionIdentifier = estateId,
FromAddress = "golfhandicapping@btinternet.com", // TODO: lookup from config
FromAddress = ConfigurationReader.GetValueOrDefault("AppSettings", "FromEmailAddress", "golfhandicapping@btinternet.com"),
IsHtml = true,
Subject = subject,
ToAddresses = new List<String> { emailAddress }
};

// TODO: may decide to record the message Id againsts the Transaction Aggregate in future, but for now
// we wont do this...
try {
return await this.MessagingServiceClient.SendEmail(accessToken, sendEmailRequest, cancellationToken);
}
Expand Down Expand Up @@ -687,7 +647,6 @@ private async Task<Result> ResendEmailMessage(String accessToken,

return Result.Success("Duplicate message send");
}

}

private async Task<Result<List<Models.Contract.ContractProductTransactionFee>>> GetTransactionFeesForProduct(Guid contractId,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading