// TODO: Make whole thing atomic with Unit of Work
        public async Task <Result <Payment> > Handle(CreatePaymentCommand command, CancellationToken cancellationToken)
        {
            var cardDetails = new CardDetails(command.FirstName, command.Surname, command.CardNumber,
                                              command.ExpiryMonth, command.ExpiryYear, command.CVV);
            var currency = Enum.Parse <Currency>(command.Currency);
            var payment  = new Payment(Guid.NewGuid(), cardDetails, currency, command.Amount);

            payment.SetSubmitting();
            Result <Guid> acquiringBankResult = await _acquiringBankService.ProcessPayment(payment);

            if (acquiringBankResult.IsSuccess)
            {
                // TODO: Use structured logging
                _logger.LogInformation($"Acquiring bank processed payment {payment.Id} successfully");
                payment.SetSuccess(acquiringBankResult.Value);
                _domainEvent = new PaymentSuccessfulDomainEvent(payment);
            }
            else
            {
                _logger.LogWarning($"Acquiring bank would not process {payment.Id} {acquiringBankResult.Error}");
                payment.SetFailure(acquiringBankResult.Error);
                _domainEvent = new PaymentFailedDomainEvent(payment);
            }

            Result dbResult = await _paymentHistoryRepository.InsertPayment(payment);

            if (dbResult.IsFailure)
            {
                _logger.LogError($"Failed to save the Payment {payment.Id} to the DB");
            }

            var eventStoreResult = await _eventStoreClient.Write(_domainEvent);

            if (eventStoreResult.IsFailure)
            {
                _logger.LogError($"Failed to send the Domain Event for Payment {payment.Id} of type {_domainEvent.GetType()}");
            }

            if (dbResult.IsFailure)
            {
                return(Result.Failure <Payment>(CreatePaymentErrors.PaymentSaveFailed));
            }

            _metrics.Measure.Counter.Increment(MetricsRegistry.PaymentsCreatedCounter);

            return(acquiringBankResult.IsSuccess
        ? Result.Ok(payment)
        : Result.Failure <Payment>(CreatePaymentErrors.AcquiringBankRefusedPayment));
        }
        public async Task ProcessPayment(Payment paymentEntity)
        {
            var validationErrors = _validationService.Validate(paymentEntity);

            if (validationErrors.Any())
            {
                paymentEntity.StatusCode = PaymentStatusCode.ValidationFailed;
                // It would be better to return a list of error messages as well
                return;
            }

            await _repository.Save(paymentEntity);

            await _acquiringBankService.ProcessPayment(paymentEntity);

            try
            {
                await _repository.Update(paymentEntity);
            }
            catch (Exception)
            {
                // See comments for Scenario 2.8 in readme.txt
            }
        }
        public PaymentResponse ProcessPayment(ProcessPaymentRequest request, string merchantApiKey)
        {
            //Get merchant
            var merchant = _merchantDao.GetMerchantBasedOnApiKey(merchantApiKey);

            if (merchant == null)
            {
                Logger.Info("Request from an unknown merchant received");
                throw new RequestValidationException("Unknown Merchant");
            }

            if (string.IsNullOrWhiteSpace(request.CardNumber))
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Missing Card Number");
                throw new RequestValidationException("Missing Card Number");
            }

            if (!(request.CardNumber.Length == 19 || request.CardNumber.Length == 16))
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Invalid Card Number");
                throw new RequestValidationException("Invalid Card Number");
            }

            if (string.IsNullOrWhiteSpace(request.CardCvv))
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Missing Card CVV Number");
                throw new RequestValidationException("Missing Card CVV Number");
            }

            if (request.CardCvv.Length != 4)
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Invalid Card Card CVV Number");
                throw new RequestValidationException("Invalid Card Card CVV Number");
            }

            if (request.PaymentAmount == 0)
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Invalid Payment Amount");
                throw new RequestValidationException("Payment Amount should be greater than zero");
            }

            if (string.IsNullOrWhiteSpace(request.ExpiryDate))
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Missing Card Expiry Date");
                throw new RequestValidationException("Missing Card Card Expiry Date");
            }

            DateTime expiryDate;

            try
            {
                expiryDate = DateTime.ParseExact(request.ExpiryDate, _expiryDateFormat, CultureInfo.InvariantCulture);
            }
            catch (Exception e)
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Invalid ExpiryDate provided");
                throw new RequestValidationException("Invalid ExpiryDate provided");
            }

            if (string.IsNullOrWhiteSpace(request.Currency))
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Missing Currency");
                throw new RequestValidationException("Missing Currency");
            }



            var currency = GetCurrency(request);

            if (currency == null)
            {
                Logger.Info($"Bad Request from MerchantId :{merchant.ID}, Unknown currency");
                throw new RequestValidationException("Unknown Currency");
            }



            var card = GetOrCreateCard(request, expiryDate);

            var paymentRequest = new PaymentRequest
            {
                MerchantId    = merchant.ID,
                CardId        = card.ID,
                CurrencyId    = currency.ID,
                Amount        = request.PaymentAmount,
                DateTimeAdded = DateTime.UtcNow
            };

            _paymentRequestDao.InsertPaymentRequest(paymentRequest);

            paymentRequest =
                _paymentRequestDao.GetPaymentRequestBasedOnMerchantIdCardIdCurrencyIdAndAmount(paymentRequest);
            Logger.Info($"Payment request from Merchant: {merchant.ID} logged. PaymentRequestId : {paymentRequest.Id}");

            var acquiringBankRequest = new  AcquiringBankRequest
            {
                CardNumber    = request.CardNumber,
                CardCvv       = request.CardCvv,
                PaymentAmount = request.PaymentAmount,
                ExpiryDate    = request.ExpiryDate,
                Currency      = request.Currency,
                MerchantId    = merchant.ID
            };
            var response = _acquiringBankService.ProcessPayment(acquiringBankRequest);

            var bankPaymentResponse = new BankPaymentResponse
            {
                PaymentRequestId      = paymentRequest.Id,
                Status                = response.PaymentStatus,
                BankPaymentIdentifier = response.PaymentIdentifier,
                DateTimeAdded         = DateTime.UtcNow
            };

            _bankPaymentResponseDao.InsertBankPaymentResponse(bankPaymentResponse);

            return(new PaymentResponse
            {
                Status = response.PaymentStatus,
                PaymentUniqueId = response.PaymentIdentifier
            });
        }