private void ValidateBankCard(OnlinePaymentViewModel viewModel, out IBankCard bankCard)
        {
            bankCard = _bankCardService.FindBankCard(viewModel.CardNumber);
            if (bankCard == null)
            {
                ModelState.AddModelError(nameof(viewModel.CardNumber), "Invalid card number");
            }
            else
            {
                if (!string.Equals(bankCard.CardHolder.ToUpper(), viewModel.CardHolderName, StringComparison.InvariantCulture))
                {
                    ModelState.AddModelError(nameof(viewModel.CardHolderName), "Invalid cardholder name");
                }

                if (!string.Equals(bankCard.CardNumber.ToUpper(), viewModel.CardNumber, StringComparison.InvariantCulture))
                {
                    ModelState.AddModelError(nameof(viewModel.CardNumber), "Invalid card number");
                }

                if (bankCard.ExpirationMonth != viewModel.MonthExpired)
                {
                    ModelState.AddModelError(nameof(viewModel.MonthExpired), "Invalid month");
                }

                if (bankCard.ExpirationYear != viewModel.YearExpired)
                {
                    ModelState.AddModelError(nameof(viewModel.YearExpired), "Invalid year");
                }

                if (!string.Equals(bankCard.CsvCode.ToUpper(), viewModel.SecurityCode, StringComparison.InvariantCulture))
                {
                    ModelState.AddModelError(nameof(viewModel.SecurityCode), "Invalid security code");
                }
            }
        }
        private void ValidateRequiredParameters(OnlinePaymentViewModel viewModel)
        {
            if (string.IsNullOrWhiteSpace(viewModel.CardHolderName))
            {
                ModelState.AddModelError(nameof(viewModel.CardHolderName), "Cardholder name is required");
            }

            if (string.IsNullOrWhiteSpace(viewModel.CardNumber))
            {
                ModelState.AddModelError(nameof(viewModel.CardNumber), "Card number is required");
            }

            if (string.IsNullOrWhiteSpace(viewModel.SecurityCode))
            {
                ModelState.AddModelError(nameof(viewModel.SecurityCode), "Security code is required");
            }

            if (!viewModel.Months.Contains(viewModel.MonthExpired))
            {
                ModelState.AddModelError(nameof(viewModel.MonthExpired), "Invalid month");
            }

            if (!viewModel.Years.Contains(viewModel.YearExpired))
            {
                ModelState.AddModelError(nameof(viewModel.YearExpired), "Invalid year");
            }
        }
        public void ShouldValidateViewModelOnSubmit(string merchantId, string currency, string cardHolderName, string cardNumber, string securityCode, int monthExpired, int yearExpired, string expectedError)
        {
            // arrange
            var model = new OnlinePaymentViewModel
            {
                MerchantId     = new Guid(merchantId),
                Amount         = 1000,
                Currency       = currency,
                RedirectUrl    = "http://test.com",
                CardHolderName = cardHolderName,
                CardNumber     = cardNumber,
                SecurityCode   = securityCode,
                MonthExpired   = monthExpired,
                YearExpired    = yearExpired
            };
            var bankCard = new BankCard("123", "0000", "EVGENY SHMANEV", "12345", 1, 2020);

            _bankCardService.Setup(x => x.FindBankCard("12345")).Returns(bankCard);

            var merchant = new Merchant("merchant", "test@com", "pass", new Guid("E5310F8F-117B-4D60-8239-39A42A94ADC9"), "store", "contact");
            var account  = merchant.AddAccount("9876543210", "USD");

            _merchantService.Setup(x => x.FindMerchant(merchant.MerchantId)).Returns(merchant);

            // act
            var result = _controller.Payment(model).Result;

            // assert
            result.ShouldBeOfType <ViewResult>();
            _controller.ModelState.IsValid.ShouldBeFalse();
            _controller.ModelState.ShouldContain(x => x.Value.Errors.Any(error => error.ErrorMessage == expectedError));
        }
 public ActionResult Index(Guid merchant, string currency, decimal sum, string redirectUrl)
 {
     TempData["ViewModel"] = new OnlinePaymentViewModel
     {
         RedirectUrl = redirectUrl,
         MerchantId  = merchant,
         Amount      = sum,
         Currency    = currency.ToUpper(),
         Months      = GetMonths(),
         Years       = GetYears()
     };
     return(RedirectToAction("Payment"));
 }
        public void ShouldShowView_Success()
        {
            // arrange
            var model = new OnlinePaymentViewModel();

            _controller.TempData["ViewModel"] = model;

            // act
            var result = _controller.Payment();

            // assert
            result.ShouldBeOfType <ViewResult>().Model.ShouldBe(model);
        }
 private void ValidateMerchant(OnlinePaymentViewModel viewModel, out IMerchant merchant, out IAccount merchantAccount)
 {
     merchant = _merchantService.FindMerchant(viewModel.MerchantId);
     if (merchant == null)
     {
         merchantAccount = null;
         ModelState.AddModelError(string.Empty, "Invalid merchant");
     }
     else
     {
         merchantAccount = merchant.Accounts.FirstOrDefault(x => string.Equals(x.Currency, viewModel.Currency, StringComparison.InvariantCultureIgnoreCase));
         if (merchantAccount == null)
         {
             ModelState.AddModelError(string.Empty, "Invalid merchant currency");
         }
     }
 }
        public void ShouldRedirectToStoreAfterPayment(string merchantId, string currency, string cardHolderName, string cardNumber, string securityCode, int monthExpired, int yearExpired, string expectedError)
        {
            // arrange
            var model = new OnlinePaymentViewModel
            {
                MerchantId     = new Guid(merchantId),
                Amount         = 1000,
                Currency       = currency,
                RedirectUrl    = "http://test.com",
                CardHolderName = cardHolderName,
                CardNumber     = cardNumber,
                SecurityCode   = securityCode,
                MonthExpired   = monthExpired,
                YearExpired    = yearExpired
            };

            var customer        = new Individual("customer", "test@com", "pass", "Evgeny", "Shmanev");
            var customerAccount = customer.AddAccount("11111111", "EUR");

            customerAccount.BankCard = new BankCard("123", "0000", "EVGENY SHMANEV", "12345", 1, 2020);
            _bankCardService.Setup(x => x.FindBankCard("12345")).Returns(customerAccount.BankCard);

            var merchant        = new Merchant("merchant", "test@com", "pass", new Guid("E5310F8F-117B-4D60-8239-39A42A94ADC9"), "store", "contact");
            var merchantAccount = merchant.AddAccount("222222222", "USD");

            _merchantService.Setup(x => x.FindMerchant(merchant.MerchantId)).Returns(merchant);

            // act
            var result = _controller.Payment(model).Result;

            // assert
            _controller.ModelState.IsValid.ShouldBeTrue();
            _accountService.Verify(x => x.TransferMoney(customerAccount, merchantAccount, model.Amount, AmountConversionMode.TargetToSource, $"Online payment on {merchant.MerchantName}."));
            result.ShouldBeOfType <RedirectResult>()
            .Url.ShouldBe("http://test.com?status=success");
        }
        public async Task <ActionResult> Payment(OnlinePaymentViewModel viewModel)
        {
            viewModel.Months = GetMonths();
            viewModel.Years  = GetYears();

            // these values cannot be changed by user, so if they are changed we should return Bad Request
            if (string.IsNullOrWhiteSpace(viewModel.RedirectUrl) || viewModel.MerchantId == Guid.Empty ||
                viewModel.Amount <= 0 || string.IsNullOrWhiteSpace(viewModel.Currency))
            {
                return(BadRequest());
            }

            // validate required parameters
            if (ModelState.IsValid)
            {
                ValidateRequiredParameters(viewModel);
            }

            // validate bank card
            IBankCard bankCard = null;

            if (ModelState.IsValid)
            {
                ValidateBankCard(viewModel, out bankCard);
            }

            IMerchant merchant        = null;
            IAccount  merchantAccount = null;

            if (ModelState.IsValid)
            {
                ValidateMerchant(viewModel, out merchant, out merchantAccount);
            }

            if (!ModelState.IsValid)
            {
                // Optionally we can calculate a number of failures and when a limit exceeds redirect to merchant with status=failure
                // At the moment it is out of the scope.
                // Redirect(url + "?status=failure");

                return(View(viewModel));
            }

            Debug.Assert(bankCard != null);
            Debug.Assert(merchant != null);
            Debug.Assert(merchantAccount != null);

            try
            {
                var description = $"Online payment on {merchant.MerchantName}.";
                await _accountService.TransferMoney(bankCard.Account, merchantAccount, viewModel.Amount, AmountConversionMode.TargetToSource, description);
            }
            catch (BankingServiceException ex)
            {
                ModelState.AddModelError(string.Empty, ex.Message);
                return(View(viewModel));
            }
            catch (Exception ex)
            {
                Log.Error("Unexpected error has occurred while processing online payment", ex);
                ModelState.AddModelError(string.Empty, "Unexpected error has occurred. Please try again later.");
                return(View(viewModel));
            }

            // redirect to merchant and pass Success status via query string
            var url = viewModel.RedirectUrl;

            url += url.Contains("?") ? "&status=success" : "?status=success";
            return(Redirect(url));
        }