diff --git a/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs b/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs index 812dd0d..baf944a 100644 --- a/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs +++ b/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs @@ -1,20 +1,28 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using RefactorThis.Domain.PaymentProcessor; +using RefactorThis.Domain.PaymentProcessor.Validators; using RefactorThis.Persistence; namespace RefactorThis.Domain.Tests { [TestFixture] public class InvoicePaymentProcessorTests - { + { + private readonly IPaymentProcessorValidator _paymentProcessorValidator; + public InvoicePaymentProcessorTests() + { + _paymentProcessorValidator = new PaymentProcessorValidator(new PaymentValidatorAbstractFactory()); + } + [Test] public void ProcessPayment_Should_ThrowException_When_NoInoiceFoundForPaymentReference( ) { var repo = new InvoiceRepository( ); Invoice invoice = null; - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor( repo, _paymentProcessorValidator); var payment = new Payment( ); var failureMessage = ""; @@ -45,7 +53,7 @@ public void ProcessPayment_Should_ReturnFailureMessage_When_NoPaymentNeeded( ) repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ); @@ -73,7 +81,7 @@ public void ProcessPayment_Should_ReturnFailureMessage_When_InvoiceAlreadyFullyP }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ); @@ -100,7 +108,7 @@ public void ProcessPayment_Should_ReturnFailureMessage_When_PartialPaymentExists }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { @@ -124,7 +132,7 @@ public void ProcessPayment_Should_ReturnFailureMessage_When_NoPartialPaymentExis }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { @@ -154,7 +162,7 @@ public void ProcessPayment_Should_ReturnFullyPaidMessage_When_PartialPaymentExis }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { @@ -178,7 +186,7 @@ public void ProcessPayment_Should_ReturnFullyPaidMessage_When_NoPartialPaymentEx }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { @@ -208,7 +216,7 @@ public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_PartialPayment }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { @@ -232,7 +240,7 @@ public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_NoPartialPayme }; repo.Add( invoice ); - var paymentProcessor = new InvoicePaymentProcessor( repo ); + var paymentProcessor = new InvoicePaymentProcessor(repo, _paymentProcessorValidator); var payment = new Payment( ) { diff --git a/RefactorThis.Domain/Common/ValidationModel/ValidationStatus.cs b/RefactorThis.Domain/Common/ValidationModel/ValidationStatus.cs new file mode 100644 index 0000000..a238d5e --- /dev/null +++ b/RefactorThis.Domain/Common/ValidationModel/ValidationStatus.cs @@ -0,0 +1,14 @@ +namespace RefactorThis.Domain.Common.ValidationModel +{ + public class ValidationStatus + { + public bool Success { get;} + public string Message { get;} + + public ValidationStatus(bool success, string message) + { + Success = success; + Message = message; + } + } +} diff --git a/RefactorThis.Domain/InvoicePaymentProcessor.cs b/RefactorThis.Domain/InvoicePaymentProcessor.cs deleted file mode 100644 index ea3ed64..0000000 --- a/RefactorThis.Domain/InvoicePaymentProcessor.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Linq; -using RefactorThis.Persistence; - -namespace RefactorThis.Domain -{ - public class InvoicePaymentProcessor - { - private readonly InvoiceRepository _invoiceRepository; - - public InvoicePaymentProcessor( InvoiceRepository invoiceRepository ) - { - _invoiceRepository = invoiceRepository; - } - - public string ProcessPayment( Payment payment ) - { - var inv = _invoiceRepository.GetInvoice( payment.Reference ); - - var responseMessage = string.Empty; - - if ( inv == null ) - { - throw new InvalidOperationException( "There is no invoice matching this payment" ); - } - else - { - if ( inv.Amount == 0 ) - { - if ( inv.Payments == null || !inv.Payments.Any( ) ) - { - responseMessage = "no payment needed"; - } - else - { - throw new InvalidOperationException( "The invoice is in an invalid state, it has an amount of 0 and it has payments." ); - } - } - else - { - if ( inv.Payments != null && inv.Payments.Any( ) ) - { - if ( inv.Payments.Sum( x => x.Amount ) != 0 && inv.Amount == inv.Payments.Sum( x => x.Amount ) ) - { - responseMessage = "invoice was already fully paid"; - } - else if ( inv.Payments.Sum( x => x.Amount ) != 0 && payment.Amount > ( inv.Amount - inv.AmountPaid ) ) - { - responseMessage = "the payment is greater than the partial amount remaining"; - } - else - { - if ( ( inv.Amount - inv.AmountPaid ) == payment.Amount ) - { - inv.AmountPaid += payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "final partial payment received, invoice is now fully paid"; - } - else - { - inv.AmountPaid += payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "another partial payment received, still not fully paid"; - } - } - } - else - { - if ( payment.Amount > inv.Amount ) - { - responseMessage = "the payment is greater than the invoice amount"; - } - else if ( inv.Amount == payment.Amount ) - { - inv.AmountPaid = payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "invoice is now fully paid"; - } - else - { - inv.AmountPaid = payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "invoice is now partially paid"; - } - } - } - } - - inv.Save(); - - return responseMessage; - } - } -} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/InvoicePaymentProcessor.cs b/RefactorThis.Domain/PaymentProcessor/InvoicePaymentProcessor.cs new file mode 100644 index 0000000..0c32f8e --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/InvoicePaymentProcessor.cs @@ -0,0 +1,39 @@ +using RefactorThis.Domain.PaymentProcessor.Validators; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor +{ + public class InvoicePaymentProcessor + { + private readonly InvoiceRepository _invoiceRepository; + private readonly IPaymentProcessorValidator _paymentProcessorValidator; + + public InvoicePaymentProcessor( InvoiceRepository invoiceRepository, IPaymentProcessorValidator paymentProcessorValidator ) + { + _invoiceRepository = invoiceRepository; + _paymentProcessorValidator = paymentProcessorValidator; + } + + public string ProcessPayment( Payment payment ) + { + var inv = _invoiceRepository.GetInvoice( payment.Reference ); + + var validationStatus = _paymentProcessorValidator.Validate(inv, payment); + + if (validationStatus.Success) + { + AddPayment(payment, inv); + } + + inv.Save(); + + return validationStatus.Message; + } + + private static void AddPayment(Payment payment, Invoice inv) + { + inv.AmountPaid += payment.Amount; + inv.Payments.Add(payment); + } + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/AdditionalPaymentValidator.cs b/RefactorThis.Domain/PaymentProcessor/Validators/AdditionalPaymentValidator.cs new file mode 100644 index 0000000..c3de8af --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/AdditionalPaymentValidator.cs @@ -0,0 +1,41 @@ +using System.Linq; +using RefactorThis.Domain.Common.ValidationModel; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public class AdditionalPaymentValidator : IPaymentValidator + { + public ValidationStatus Validate(Invoice invoice, Payment payment) + { + if (InvoiceIsPaid(invoice)) + { + return new ValidationStatus(false, "invoice was already fully paid"); + } + + if (PaymentGreaterThanAmountRemaining(payment, invoice)) + { + return new ValidationStatus(false, "the payment is greater than the partial amount remaining"); + } + + return IsFinalPayment(payment, invoice) ? + new ValidationStatus(true, "final partial payment received, invoice is now fully paid") : + new ValidationStatus(true, "another partial payment received, still not fully paid"); + } + + private static bool InvoiceIsPaid(Invoice invoice) + { + return invoice.Payments.Sum(x => x.Amount) != 0 && invoice.Amount == invoice.Payments.Sum(x => x.Amount); + } + + private static bool PaymentGreaterThanAmountRemaining(Payment payment, Invoice invoice) + { + return invoice.Payments.Sum(x => x.Amount) != 0 && payment.Amount > (invoice.Amount - invoice.AmountPaid); + } + + private static bool IsFinalPayment(Payment payment, Invoice invoice) + { + return (invoice.Amount - invoice.AmountPaid) == payment.Amount; + } + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/FirstPaymentValidator.cs b/RefactorThis.Domain/PaymentProcessor/Validators/FirstPaymentValidator.cs new file mode 100644 index 0000000..b93a83b --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/FirstPaymentValidator.cs @@ -0,0 +1,30 @@ +using RefactorThis.Domain.Common.ValidationModel; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public class FirstPaymentValidator : IPaymentValidator + { + public ValidationStatus Validate(Invoice invoice, Payment payment) + { + if (PaymentGreaterThanAmount(invoice, payment)) + { + return new ValidationStatus(false, "the payment is greater than the invoice amount"); + } + + return PaymentEqualsAmount(invoice, payment) ? + new ValidationStatus(true, "invoice is now fully paid") : + new ValidationStatus(true, "invoice is now partially paid"); + } + + private static bool PaymentEqualsAmount(Invoice invoice, Payment payment) + { + return invoice.Amount == payment.Amount; + } + + private static bool PaymentGreaterThanAmount(Invoice invoice, Payment payment) + { + return payment.Amount > invoice.Amount; + } + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentProcessorValidator.cs b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentProcessorValidator.cs new file mode 100644 index 0000000..5f6e4e5 --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentProcessorValidator.cs @@ -0,0 +1,10 @@ +using RefactorThis.Domain.Common.ValidationModel; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public interface IPaymentProcessorValidator + { + ValidationStatus Validate(Invoice invoice, Payment payment); + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidator.cs b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidator.cs new file mode 100644 index 0000000..8d83a86 --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidator.cs @@ -0,0 +1,10 @@ +using RefactorThis.Domain.Common.ValidationModel; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public interface IPaymentValidator + { + ValidationStatus Validate(Invoice invoice, Payment payment); + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidatorAbstractFactory.cs b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidatorAbstractFactory.cs new file mode 100644 index 0000000..cf2e591 --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/IPaymentValidatorAbstractFactory.cs @@ -0,0 +1,8 @@ +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public interface IPaymentValidatorAbstractFactory + { + IPaymentValidator GetFirstPaymentValidator(); + IPaymentValidator GetAdditionalPaymentValidator(); + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/PaymentProcessorValidator.cs b/RefactorThis.Domain/PaymentProcessor/Validators/PaymentProcessorValidator.cs new file mode 100644 index 0000000..5ac3b0a --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/PaymentProcessorValidator.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using RefactorThis.Domain.Common.ValidationModel; +using RefactorThis.Persistence; + +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public class PaymentProcessorValidator : IPaymentProcessorValidator + { + private readonly IPaymentValidator _firstPaymentValidator; + private readonly IPaymentValidator _additionalPaymentValidator; + + public PaymentProcessorValidator(IPaymentValidatorAbstractFactory paymentValidatorAbstractFactory) + { + _firstPaymentValidator = paymentValidatorAbstractFactory.GetFirstPaymentValidator(); + _additionalPaymentValidator = paymentValidatorAbstractFactory.GetAdditionalPaymentValidator(); + } + + public ValidationStatus Validate(Invoice invoice, Payment payment) + { + if (invoice == null) + { + throw new InvalidOperationException("There is no invoice matching this payment"); + } + + if (invoice.Amount == 0) + { + return NoPaymentRequired(invoice); + } + + return IsFirstPayment(invoice) ? + _firstPaymentValidator.Validate(invoice, payment) : + _additionalPaymentValidator.Validate(invoice, payment); + } + + private static ValidationStatus NoPaymentRequired(Invoice inv) + { + if (inv.Payments != null && inv.Payments.Any()) + { + throw new InvalidOperationException("The invoice is in an invalid state, it has an amount of 0 and it has payments."); + } + + return new ValidationStatus(false, "no payment needed"); + } + + private static bool IsFirstPayment(Invoice inv) + { + return inv.Payments == null || !inv.Payments.Any(); + } + } +} diff --git a/RefactorThis.Domain/PaymentProcessor/Validators/PaymentValidatorAbstractFactory.cs b/RefactorThis.Domain/PaymentProcessor/Validators/PaymentValidatorAbstractFactory.cs new file mode 100644 index 0000000..3a1061d --- /dev/null +++ b/RefactorThis.Domain/PaymentProcessor/Validators/PaymentValidatorAbstractFactory.cs @@ -0,0 +1,15 @@ +namespace RefactorThis.Domain.PaymentProcessor.Validators +{ + public class PaymentValidatorAbstractFactory : IPaymentValidatorAbstractFactory + { + public IPaymentValidator GetFirstPaymentValidator() + { + return new FirstPaymentValidator(); + } + + public IPaymentValidator GetAdditionalPaymentValidator() + { + return new AdditionalPaymentValidator(); + } + } +} \ No newline at end of file diff --git a/RefactorThis.Domain/RefactorThis.Domain.csproj b/RefactorThis.Domain/RefactorThis.Domain.csproj index e25ce77..ea360d8 100644 --- a/RefactorThis.Domain/RefactorThis.Domain.csproj +++ b/RefactorThis.Domain/RefactorThis.Domain.csproj @@ -1,59 +1,67 @@  - - - Debug - AnyCPU - {5310B2FE-E26D-414E-B656-1F74C5A70368} - Library - Properties - RefactorThis.Domain - RefactorThis.Domain - v4.7.2 - 512 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - {33cdc796-ff75-449c-9637-59c2efc46361} - RefactorThis.Persistence - - - - - - + \ No newline at end of file