public async void Handle_ShouldProcessPayment() { var accountsRepo = new Mock <IAccountRepository>(); var account = new Account(Guid.NewGuid(), 100); account.AddPaymentRequest(new DateTime(2020, 3, 1), 40); accountsRepo.Setup(a => a.GetAsync(account.Id)).ReturnsAsync(account); var paymentRepo = new Mock <IPaymentRepository>(); var payment = account.PaymentRequests.First(); paymentRepo.Setup(p => p.GetAsync(payment.Id)).ReturnsAsync(payment); paymentRepo.Setup(p => p.UnitOfWork.SaveEntitiesAsync(It.IsAny <CancellationToken>())).ReturnsAsync(true); var reason = "Processed"; var command = new ProcessPaymentCommand(account.Id, payment.Id); var handler = new ProcessPaymentHandler(accountsRepo.Object, paymentRepo.Object); await handler.Handle(command, new CancellationToken()); Assert.Equal(PaymentStatus.Processed, payment.Status); Assert.Equal(reason, payment.Reason); }
public void GivenADuplicatePaymentRequest_ShouldNotTalkToBank_AndShouldThrowBadRequestException() { // arrange _paymentHistoryRepo.IsDuplicate(Arg.Any <string>()).Returns(true); var handler = new ProcessPaymentHandler(_paymentHistoryRepo, _bankApi); var paymentRequest = BuildValidTestPaymentRequest(); // act var ex = Assert.Throws <BadRequestException>(() => handler.Handle(paymentRequest)); // assert Assert.Equal("Duplicate request.", ex.Message); }
public void GivenAPaymentRequestThatSucceeds_ThenRequestLogged_AndLoggedDetailsShowFailure() { // arrange var paymentRequest = BuildValidTestPaymentRequest(); _bankApi.MakePayment(paymentRequest).Returns(false); var handler = new ProcessPaymentHandler(_paymentHistoryRepo, _bankApi); // act handler.Handle(paymentRequest); // assert _paymentHistoryRepo.Received(1).LogPayment(Arg.Is <LoggedPaymentRequest>(x => Equals(x.Success, false))); }
public void GivenAPaymentRequest_IfRejectedByBank_ShouldReturnUnsuccessful() { // arrange var paymentRequest = BuildValidTestPaymentRequest(); _bankApi.MakePayment(paymentRequest).Returns(false); var handler = new ProcessPaymentHandler(_paymentHistoryRepo, _bankApi); // act var result = handler.Handle(paymentRequest); // assert Assert.False(result); }
public async Task Returns_Error_Response_When_Cache_Already_Contains_Key() { const string CARD_NUMBER = "4111111111111111"; const string CVV = "123"; const string CURRENCY = "GBP"; const string OWNER = "owner"; const decimal AMOUNT = .1m; var EXPIRY = new MonthYear { Year = 2021, Month = 01 }; var testNow = new DateTime(2021, 02, 01); var nowProvider = new NowProvider(testNow); var request = new ProcessPaymentRequest { CardNumber = CARD_NUMBER, Amount = AMOUNT, Currency = CURRENCY, CVV = CVV, Expiry = EXPIRY, Owner = OWNER }; var acqBankMock = new Mock <IAcquiringBank>(); var memoryCacheMock = new Mock <IMemoryCache>(); memoryCacheMock .Setup(mock => mock.TryGetValue(It.IsAny <string>(), out It.Ref <object> .IsAny)) .Returns(true); using var context = Setup.CreateContext(); var handler = new ProcessPaymentHandler( acqBankMock.Object, nowProvider, memoryCacheMock.Object, _logger, _cachingOptions, context); var result = (await handler.Handle(request, CancellationToken.None)).ErrorOrDefault; Assert.AreEqual(HttpStatusCode.TooManyRequests, result.StatusCode); }
public static async Task <IActionResult> Status([HttpTrigger( AuthorizationLevel.Function, "POST", Route = "fakeBank/payment/")] HttpRequestMessage request) { // Auth checks would be done here against or at Api Gateway level to ensure // person making request has access to do so for the merchant. var bankApi = new FakeBankApi(); var paymentHistoryRepo = new PaymentHistoryRepo(); var handler = new ProcessPaymentHandler(paymentHistoryRepo, bankApi); try { var paymentRequest = DeserializeRequestFromBody(await request.Content.ReadAsStringAsync()); var validation = new ProcessPaymentValidator().Validate(paymentRequest); if (!validation.IsValid) { return(new BadRequestObjectResult(JsonConvert.SerializeObject(validation.Errors))); } var isCompleted = handler.Handle(paymentRequest); if (isCompleted) { return(new OkObjectResult(paymentRequest)); } return(new BadRequestObjectResult("Payment Request Unsuccessful")); } catch (BadRequestException ex) { return(new BadRequestObjectResult(ex.Message)); } }
public async Task Adds_Key_To_Cache() { const string GENERATED_REQUEST_KEY = "1v9ESIpTOJ1czY9SZW5oWf2DiRWrNOB4kHdPFODIYjI="; const int RETURNED_PAYMENT_ID = 1; const string CARD_NUMBER = "4111111111111111"; const string CVV = "123"; const string CURRENCY = "GBP"; const string OWNER = "owner"; const decimal AMOUNT = .1m; var EXPIRY = new MonthYear { Year = 2021, Month = 01 }; var testNow = new DateTime(2021, 02, 01); var nowProvider = new NowProvider(testNow); var request = new ProcessPaymentRequest { CardNumber = CARD_NUMBER, Amount = AMOUNT, Currency = CURRENCY, CVV = CVV, Expiry = EXPIRY, Owner = OWNER }; var acqBankMock = new Mock <IAcquiringBank>(); acqBankMock .Setup(mock => mock.SendPayment(It.IsAny <AcquiringBankRequest>())) .ReturnsAsync(new AcquiringBankResponse { Status = AcquiringBankResponseStatus.Success, PaymentId = RETURNED_PAYMENT_ID }); var memoryCacheMock = new Mock <IMemoryCache>(); var cacheEntryMock = new Mock <ICacheEntry>(); memoryCacheMock .Setup(mock => mock.TryGetValue(It.IsAny <string>(), out It.Ref <object> .IsAny)) .Returns(false); memoryCacheMock .Setup(mock => mock.CreateEntry(It.IsAny <object>())) .Returns(cacheEntryMock.Object); using var context = Setup.CreateContext(); var handler = new ProcessPaymentHandler( acqBankMock.Object, nowProvider, memoryCacheMock.Object, _logger, _cachingOptions, context); var result = await handler.Handle(request, CancellationToken.None); memoryCacheMock.Verify( mock => mock.CreateEntry(GENERATED_REQUEST_KEY), Times.Once); }
public async Task Stores_Masked_Card_Details() { const int RETURNED_PAYMENT_ID = 1; const string CARD_NUMBER = "4111111111111111"; const string CVV = "123"; const string CURRENCY = "GBP"; const string OWNER = "owner"; const decimal AMOUNT = .1m; var EXPIRY = new MonthYear { Year = 2021, Month = 01 }; var testNow = new DateTime(2021, 02, 01); var nowProvider = new NowProvider(testNow); var request = new ProcessPaymentRequest { CardNumber = CARD_NUMBER, Amount = AMOUNT, Currency = CURRENCY, CVV = CVV, Expiry = EXPIRY, Owner = OWNER }; var acqBankMock = new Mock <IAcquiringBank>(); acqBankMock .Setup(mock => mock.SendPayment(It.IsAny <AcquiringBankRequest>())) .ReturnsAsync(new AcquiringBankResponse { Status = AcquiringBankResponseStatus.Success, PaymentId = RETURNED_PAYMENT_ID }); var memoryCacheMock = new Mock <IMemoryCache>(); var cacheEntryMock = new Mock <ICacheEntry>(); memoryCacheMock .Setup(mock => mock.TryGetValue(It.IsAny <string>(), out It.Ref <object> .IsAny)) .Returns(false); memoryCacheMock .Setup(mock => mock.CreateEntry(It.IsAny <object>())) .Returns(cacheEntryMock.Object); using var context = Setup.CreateContext(); var handler = new ProcessPaymentHandler( acqBankMock.Object, nowProvider, memoryCacheMock.Object, _logger, _cachingOptions, context); var result = (await handler.Handle(request, CancellationToken.None)).SuccessOrDefault; Assert.IsTrue(result.Success); // assert that a record is in the db for this payment id, var foundPayment = context.ProcessedPayments.Find(result.PaymentId); // should be masked Assert.AreEqual("************1111", foundPayment.CardNumber); Assert.AreEqual("***", foundPayment.CVV); Assert.AreEqual(AMOUNT, foundPayment.Amount); Assert.AreEqual(CURRENCY, foundPayment.Currency); Assert.AreEqual(OWNER, foundPayment.Owner); Assert.AreEqual(EXPIRY.Year, foundPayment.Expiry.Year); Assert.AreEqual(EXPIRY.Month, foundPayment.Expiry.Month); }
public async Task Unsuccesful_Bank_Result_Does_Not_Throw() { const int RETURNED_PAYMENT_ID = 1; const string CARD_NUMBER = "4111111111111111"; const string CVV = "123"; const string CURRENCY = "GBP"; const string OWNER = "owner"; const decimal AMOUNT = .1m; var EXPIRY = new MonthYear { Year = 2021, Month = 01 }; var testNow = new DateTime(2021, 02, 01); var nowProvider = new NowProvider(testNow); var request = new ProcessPaymentRequest { CardNumber = CARD_NUMBER, Amount = AMOUNT, Currency = CURRENCY, CVV = CVV, Expiry = EXPIRY, Owner = OWNER }; using var context = Setup.CreateContext(); var acqBankMock = new Mock <IAcquiringBank>(); acqBankMock .Setup(mock => mock.SendPayment(It.IsAny <AcquiringBankRequest>())) .ReturnsAsync(new AcquiringBankResponse { Status = AcquiringBankResponseStatus.Acquiring_Bank_Unreachable, PaymentId = RETURNED_PAYMENT_ID }); var memoryCacheMock = new Mock <IMemoryCache>(); var cacheEntryMock = new Mock <ICacheEntry>(); memoryCacheMock .Setup(mock => mock.TryGetValue(It.IsAny <string>(), out It.Ref <object> .IsAny)) .Returns(false); memoryCacheMock .Setup(mock => mock.CreateEntry(It.IsAny <object>())) .Returns(cacheEntryMock.Object); // use mock of acqbank to ensure we get a failure here var handler = new ProcessPaymentHandler( acqBankMock.Object, nowProvider, memoryCacheMock.Object, _logger, _cachingOptions, context); var result = (await handler.Handle(request, CancellationToken.None)).SuccessOrDefault; // ensure payment is still saved in failed state var foundPayment = context.ProcessedPayments.Find(result.PaymentId); Assert.AreEqual(RETURNED_PAYMENT_ID, result.PaymentId); Assert.IsFalse(result.Success); Assert.AreEqual("************1111", foundPayment.CardNumber); Assert.AreEqual("***", foundPayment.CVV); Assert.AreEqual(AMOUNT, foundPayment.Amount); Assert.AreEqual(CURRENCY, foundPayment.Currency); Assert.AreEqual(OWNER, foundPayment.Owner); Assert.AreEqual(EXPIRY.Year, foundPayment.Expiry.Year); Assert.AreEqual(EXPIRY.Month, foundPayment.Expiry.Month); }