コード例 #1
0
        public void FileAggregate_RecordFileLineAsIgnored_FileLineUpdatedAsIgnored()
        {
            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.RecordFileLineAsIgnored(TestData.LineNumber);

            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.Ignored);
            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(1);
        }
コード例 #2
0
        public void FileAggregate_RecordFileLineAsIgnored_FileNotCreated_ErrorThrown()
        {
            FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId);

            Should.Throw <InvalidOperationException>(() =>
            {
                fileAggregate.RecordFileLineAsIgnored(TestData.LineNumber);
            });
        }
コード例 #3
0
        public void FileAggregate_RecordFileLineAsIgnored_LineNotFound_ErrorThrown()
        {
            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);
            Should.Throw <NotFoundException>(() =>
            {
                fileAggregate.RecordFileLineAsIgnored(TestData.NotFoundLineNumber);
            });
        }
コード例 #4
0
        public void FileAggregate_FileAggregate_RecordFileLineAsIgnored_FileHasNoLine_ErrorThrown()
        {
            FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId);

            fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId,
                                     TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime);

            Should.Throw <InvalidOperationException>(() =>
            {
                fileAggregate.RecordFileLineAsIgnored(TestData.LineNumber);
            });
        }
コード例 #5
0
        public static FileAggregate GetFileAggregateWithLinesAlreadyProcessed()
        {
            FileAggregate fileAggregate = new FileAggregate();

            fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.OriginalFileName, TestData.FileUploadedDateTime);
            fileAggregate.AddFileLine("D,1,2");
            fileAggregate.AddFileLine("D,1,2");
            fileAggregate.AddFileLine("D,1,2");
            fileAggregate.AddFileLine("D,1,2");
            fileAggregate.RecordFileLineAsSuccessful(1, TestData.TransactionId);
            fileAggregate.RecordFileLineAsRejected(2, TestData.RejectionReason);
            fileAggregate.RecordFileLineAsFailed(3, TestData.TransactionId, "-1", "Failed");
            fileAggregate.RecordFileLineAsIgnored(4);

            return(fileAggregate);
        }
コード例 #6
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());
        }