// 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 }); }