private void VerifyFile(FileAggregate source, FileDetails fileDetails) { var fileModel = source.GetFile(); fileDetails.FileId.ShouldBe(fileModel.FileId); fileDetails.FileImportLogId.ShouldBe(fileModel.FileImportLogId); fileDetails.FileLocation.ShouldBe(fileModel.FileLocation); fileDetails.FileProfileId.ShouldBe(fileModel.FileProfileId); fileDetails.MerchantId.ShouldBe(fileModel.MerchantId); fileDetails.ProcessingCompleted.ShouldBe(fileModel.ProcessingCompleted); fileDetails.UserId.ShouldBe(fileModel.UserId); fileDetails.EstateId.ShouldBe(fileModel.EstateId); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.FailedLines.ShouldBe(fileModel.ProcessingSummary.FailedLines); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(fileModel.ProcessingSummary.IgnoredLines); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(fileModel.ProcessingSummary.NotProcessedLines); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(fileModel.ProcessingSummary.SuccessfullyProcessedLines); fileDetails.ProcessingSummary.TotalLines.ShouldBe(fileModel.ProcessingSummary.TotalLines); foreach (FileLine fileModelFileLine in fileModel.FileLines) { FileLine?fileLineToVerify = fileDetails.FileLines.SingleOrDefault(f => f.LineNumber == fileModelFileLine.LineNumber); fileLineToVerify.ShouldNotBeNull(); fileLineToVerify.LineData.ShouldBe(fileModelFileLine.LineData); fileLineToVerify.TransactionId.ShouldBe(fileModelFileLine.TransactionId); fileLineToVerify.ProcessingResult.ShouldBe(fileModelFileLine.ProcessingResult); } }
public void FileAggregate_RecordFileLineAsRejected_FileLineUpdatedAsRejected() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(TestData.FileLine); fileAggregate.RecordFileLineAsRejected(TestData.LineNumber, TestData.RejectionReason); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.FileLines.ShouldNotBeNull(); fileDetails.FileLines.ShouldNotBeEmpty(); fileDetails.FileLines.ShouldHaveSingleItem(); fileDetails.FileLines.Single().LineNumber.ShouldBe(1); fileDetails.FileLines.Single().LineData.ShouldBe(TestData.FileLine); fileDetails.FileLines.Single().ProcessingResult.ShouldBe(ProcessingResult.Rejected); fileDetails.FileLines.Single().RejectedReason.ShouldBe(TestData.RejectionReason); fileDetails.ProcessingCompleted.ShouldBeTrue(); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.TotalLines.ShouldBe(1); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.FailedLines.ShouldBe(0); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(0); fileDetails.ProcessingSummary.RejectedLines.ShouldBe(1); }
public void FileAggregate_CanBeCreated_IsCreated() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.ShouldNotBeNull(); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.ShouldNotBeNull(); fileDetails.FileId.ShouldBe(TestData.FileId); fileDetails.ProcessingCompleted.ShouldBeFalse(); }
/// <summary> /// Gets the file. /// </summary> /// <param name="fileId">The file identifier.</param> /// <param name="estateId">The estate identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> public async Task <FileDetails> GetFile(Guid fileId, Guid estateId, CancellationToken cancellationToken) { FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(fileId, cancellationToken); if (fileAggregate.IsCreated == false) { throw new NotFoundException($"File with Id [{fileId}] not found"); } FileDetails fileDetails = fileAggregate.GetFile(); EstateReportingGenericContext context = await this.DbContextFactory.GetContext(estateId, cancellationToken); Merchant merchant = await context.Merchants.AsAsyncEnumerable() .SingleOrDefaultAsync(m => m.MerchantId == fileDetails.MerchantId, cancellationToken); if (merchant != null) { fileDetails.MerchantName = merchant.Name; } EstateSecurityUser userDetails = await context.EstateSecurityUsers.AsAsyncEnumerable() .SingleOrDefaultAsync(u => u.SecurityUserId == fileDetails.UserId); if (userDetails != null) { fileDetails.UserEmailAddress = userDetails.EmailAddress; } FileProfile fileProfile = await this.GetFileProfile(fileDetails.FileProfileId, cancellationToken); if (fileProfile != null) { fileDetails.FileProfileName = fileProfile.Name; } return(fileDetails); }
public void FileAggregate_CreateFile_FileIsCreated() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.IsCreated.ShouldBeTrue(); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.ShouldNotBeNull(); fileDetails.FileId.ShouldBe(TestData.FileId); fileDetails.FileImportLogId.ShouldBe(TestData.FileImportLogId); fileDetails.EstateId.ShouldBe(TestData.EstateId); fileDetails.MerchantId.ShouldBe(TestData.MerchantId); fileDetails.UserId.ShouldBe(TestData.UserId); fileDetails.FileProfileId.ShouldBe(TestData.FileProfileId); fileDetails.FileLocation.ShouldBe(TestData.FileLocation); fileDetails.FileLines.ShouldBeEmpty(); fileDetails.ProcessingCompleted.ShouldBeFalse(); }
public void FileAggregate_AddFileLine_FileLineAdded() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(TestData.FileLine); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.FileLines.ShouldNotBeNull(); fileDetails.FileLines.ShouldNotBeEmpty(); fileDetails.FileLines.ShouldHaveSingleItem(); fileDetails.FileLines.Single().LineData.ShouldBe(TestData.FileLine); fileDetails.ProcessingCompleted.ShouldBeFalse(); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.TotalLines.ShouldBe(1); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(1); fileDetails.ProcessingSummary.FailedLines.ShouldBe(0); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(0); }
/// <summary> /// Handles the specified request. /// </summary> /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> /// <exception cref="System.NotSupportedException">File Id [{request.FileId}] has no lines added</exception> /// <exception cref="NotFoundException"> /// File Line Number {request.LineNumber} not found in File Id {request.FileId} /// or /// No file profile found with Id {fileDetails.FileProfileId} /// or /// Merchant not found with Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId} /// or /// No contracts found for Merchant Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId} /// or /// No merchant contract for operator Id {fileProfile.OperatorName} found for Merchant Id {merchant.MerchantId} /// or /// No variable value product found on the merchant contract for operator Id {fileProfile.OperatorName} and Merchant Id {merchant.MerchantId} /// </exception> public async Task <Unit> Handle(ProcessTransactionForFileLineRequest request, CancellationToken cancellationToken) { // Get the file aggregate, this tells us the file profile information FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(request.FileId, cancellationToken); FileDetails fileDetails = fileAggregate.GetFile(); if (fileDetails.FileLines.Any() == false) { throw new NotSupportedException($"File Id [{request.FileId}] has no lines added"); } FileLine fileLine = fileDetails.FileLines.SingleOrDefault(f => f.LineNumber == request.LineNumber); if (fileLine == null) { throw new NotFoundException($"File Line Number {request.LineNumber} not found in File Id {request.FileId}"); } if (fileLine.ProcessingResult != ProcessingResult.NotProcessed) { // Line already processed return(new Unit()); } FileProfile fileProfile = await this.FileProcessorManager.GetFileProfile(fileDetails.FileProfileId, cancellationToken); if (fileProfile == null) { throw new NotFoundException($"No file profile found with Id {fileDetails.FileProfileId}"); } // Determine if we need to actually process this file line if (this.FileLineCanBeIgnored(fileLine.LineData, fileProfile.FileFormatHandler)) { // Write something to aggregate to say line was explicity ignored fileAggregate.RecordFileLineAsIgnored(fileLine.LineNumber); await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); } // need to now parse the line (based on the file format), this builds the metadata Dictionary <String, String> transactionMetadata = this.ParseFileLine(fileLine.LineData, fileProfile.FileFormatHandler); if (transactionMetadata == null) { // Line failed to parse so record this fileAggregate.RecordFileLineAsRejected(fileLine.LineNumber, "Invalid Format"); await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); } // Add the file data to the request metadata transactionMetadata.Add("FileId", request.FileId.ToString()); transactionMetadata.Add("FileLineNumber", fileLine.LineNumber.ToString()); String operatorName = fileProfile.OperatorName; if (transactionMetadata.ContainsKey("OperatorName")) { // extract the value operatorName = transactionMetadata["OperatorName"]; transactionMetadata = transactionMetadata.Where(x => x.Key != "OperatorName").ToDictionary(x => x.Key, x => x.Value); } this.TokenResponse = await this.GetToken(cancellationToken); Interlocked.Increment(ref FileRequestHandler.TransactionNumber); // Get the merchant details MerchantResponse merchant = await this.EstateClient.GetMerchant(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken); if (merchant == null) { throw new NotFoundException($"Merchant not found with Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId}"); } List <ContractResponse> contracts = await this.EstateClient.GetMerchantContracts(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken); if (contracts.Any() == false) { throw new NotFoundException($"No contracts found for Merchant Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId}"); } ContractResponse?contract = null; if (fileProfile.OperatorName == "Voucher") { contract = contracts.SingleOrDefault(c => c.Description.Contains(operatorName)); } else { contract = contracts.SingleOrDefault(c => c.OperatorName == operatorName); } if (contract == null) { throw new NotFoundException($"No merchant contract for operator Id {operatorName} found for Merchant Id {merchant.MerchantId}"); } ContractProduct?product = contract.Products.SingleOrDefault(p => p.Value == null); // TODO: Is this enough or should the name be used and stored in file profile?? if (product == null) { throw new NotFoundException($"No variable value product found on the merchant contract for operator Id {fileProfile.OperatorName} and Merchant Id {merchant.MerchantId}"); } // Build a transaction request message SaleTransactionRequest saleTransactionRequest = new SaleTransactionRequest { EstateId = fileDetails.EstateId, MerchantId = fileDetails.MerchantId, TransactionDateTime = fileDetails.FileReceivedDateTime, TransactionNumber = FileRequestHandler.TransactionNumber.ToString(), TransactionType = "Sale", ContractId = contract.ContractId, DeviceIdentifier = merchant.Devices.First().Value, OperatorIdentifier = contract.OperatorName, ProductId = product.ProductId, AdditionalTransactionMetadata = transactionMetadata, }; SerialisedMessage serialisedRequestMessage = new SerialisedMessage { Metadata = new Dictionary <String, String> { { "estate_id", fileDetails.EstateId.ToString() }, { "merchant_id", fileDetails.MerchantId.ToString() } }, SerialisedData = JsonConvert.SerializeObject(saleTransactionRequest, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }) }; Logger.LogInformation(serialisedRequestMessage.SerialisedData); // Send request to transaction processor SerialisedMessage serialisedResponseMessage = await this.TransactionProcessorClient.PerformTransaction(this.TokenResponse.AccessToken, serialisedRequestMessage, cancellationToken); // Get the sale transaction response SaleTransactionResponse saleTransactionResponse = JsonConvert.DeserializeObject <SaleTransactionResponse>(serialisedResponseMessage.SerialisedData); if (saleTransactionResponse.ResponseCode == "0000") { // record response against file line in file aggregate fileAggregate.RecordFileLineAsSuccessful(request.LineNumber, saleTransactionResponse.TransactionId); } else { fileAggregate.RecordFileLineAsFailed(request.LineNumber, saleTransactionResponse.TransactionId, saleTransactionResponse.ResponseCode, saleTransactionResponse.ResponseMessage); } // Save changes to file aggregate // TODO: Add retry round this save (maybe 3 retries) await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); }