Example #1
0
        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);
        }
Example #7
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());
        }