From 767bef676b8156a65aa5420f2c33a65a6fb7eb93 Mon Sep 17 00:00:00 2001 From: sauravmaiti22 Date: Thu, 7 Nov 2024 10:32:32 +0000 Subject: [PATCH 1/4] Invoice payment reference --- .../Mapping/PayrunInvoiceMapper.cs | 50 +++---- .../Models/Payruns/PayrunInvoice.cs | 21 ++- .../Models/PayrunInvoiceValidatorTests.cs | 132 +++++++++--------- 3 files changed, 111 insertions(+), 92 deletions(-) diff --git a/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs b/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs index 97116cb..58d346f 100644 --- a/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs +++ b/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs @@ -20,34 +20,34 @@ namespace NoFrixion.MoneyMoov; public static class PayrunInvoiceMapper { - /// - /// A safe TheirReference value that will pass validation sofr all currencies and processors. - /// Payrun invoices result in an auto-generating TheirReference when teh Payout is ultimately - /// created. + /// + /// A safe TheirReference value that will pass validation sofr all currencies and processors. + /// Payrun invoices result in an auto-generating TheirReference when teh Payout is ultimately + /// created. /// private const string SAFE_THEIR_REFERENCE = "Safe Reference"; public static Payout ToPayout(this PayrunInvoice invoice) - { - Guard.Against.Null(invoice, nameof(invoice)); - - return new Payout - { - Currency = invoice.Currency, - Type = invoice.Currency == CurrencyTypeEnum.GBP ? AccountIdentifierType.SCAN : AccountIdentifierType.IBAN, - Amount = invoice.TotalAmount, - TheirReference = SAFE_THEIR_REFERENCE, - Destination = new Counterparty - { - Name = invoice.DestinationAccountName, - Identifier = new AccountIdentifier - { - Currency = invoice.Currency, - IBAN = invoice.DestinationIban, - SortCode = invoice.DestinationSortCode, - AccountNumber = invoice.DestinationAccountNumber - }, - } - }; + { + Guard.Against.Null(invoice, nameof(invoice)); + + return new Payout + { + Currency = invoice.Currency, + Type = invoice.Currency == CurrencyTypeEnum.GBP ? AccountIdentifierType.SCAN : AccountIdentifierType.IBAN, + Amount = invoice.TotalAmount, + TheirReference = invoice.PaymentReference ?? SAFE_THEIR_REFERENCE, + Destination = new Counterparty + { + Name = invoice.DestinationAccountName, + Identifier = new AccountIdentifier + { + Currency = invoice.Currency, + IBAN = invoice.DestinationIban, + SortCode = invoice.DestinationSortCode, + AccountNumber = invoice.DestinationAccountNumber + }, + } + }; } } \ No newline at end of file diff --git a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs index 1cc534e..e315f33 100644 --- a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs +++ b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs @@ -22,6 +22,7 @@ namespace NoFrixion.MoneyMoov.Models; public class PayrunInvoice : IValidatableObject { + const int PAYRUN_INVOICE_PAYMENT_REFERENCE_MAX_LENGTH = 18; public Guid ID { get; set; } public Guid PayRunID { get; set; } @@ -31,7 +32,16 @@ public class PayrunInvoice : IValidatableObject [Obsolete("Please use Reference instead.")] public string? InvoiceNumber { get; set; } - public required string Reference { get; set; } + [Obsolete("Please use InvoiceReference instead.")] + public string? Reference + { + get => InvoiceReference; + init => InvoiceReference = value ?? string.Empty; + } + + [Required] + [MinLength(1, ErrorMessage = "InvoiceReference cannot be empty.")] + public string InvoiceReference { get; set; } = null!; public string? PaymentTerms { get; set; } @@ -74,6 +84,15 @@ public class PayrunInvoice : IValidatableObject public IEnumerable? InvoicePayments { get; set; } public bool IsEnabled { get; set; } + + /// + /// Represents the reference used in the payout created for this invoice. + /// For a single destination (e.g., multiple invoices with the same IBAN), + /// the PaymentReference should remain consistent across all invoices. + /// If the PaymentReference is not set, one will be generated automatically. + /// + [MaxLength(PAYRUN_INVOICE_PAYMENT_REFERENCE_MAX_LENGTH, ErrorMessage = "PaymentReference cannot be longer than 18 characters.")] + public string? PaymentReference { get; set; } public NoFrixionProblem Validate() => this.ToPayout().Validate(); diff --git a/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs b/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs index d6728fc..db515d6 100644 --- a/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs +++ b/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs @@ -40,13 +40,13 @@ public PayrunInvoiceValidatorTests(ITestOutputHelper testOutputHelper) [Theory] [InlineData("A")] [InlineData("1-A")] - [InlineData("1-A-2-c")] + [InlineData("1-A-2-c")] [InlineData("NFXN...LTD")] public void Validate_Account_Name_Success(string accountName) { var payrunInvoice = new PayrunInvoice { - Reference = "ref-1", + InvoiceReference = "ref-1", DestinationAccountName = accountName, Currency = CurrencyTypeEnum.EUR, TotalAmount = 11.00M, @@ -71,23 +71,23 @@ public void Validate_Account_Name_Success(string accountName) [InlineData(".-/&")] // No letter or number. [InlineData("1-A-2-c + + dfg")] // Invalid character '+'. [InlineData("Big Bucks £")]// Invalid character '£'. - [InlineData("Big Bucks €")]// Invalid character '€'. + [InlineData("Big Bucks €")]// Invalid character '€'. [InlineData(":")] // Invalid initial character, can't be ':' or '-'. - [InlineData("1-A-2-c + ! + dfg")] // Invalid character '!'. - [InlineData(".-/& a")] // BC don't support '&' in account name. - [InlineData("/:")] // BC don't support '/' in account name. + [InlineData("1-A-2-c + ! + dfg")] // Invalid character '!'. + [InlineData(".-/& a")] // BC don't support '&' in account name. + [InlineData("/:")] // BC don't support '/' in account name. [InlineData("TELECOMUNICAÇÕES S.A.")] // BC don't support unicode. - [InlineData("Ç_é")] // BC don't support unicode. + [InlineData("Ç_é")] // BC don't support unicode. [InlineData("NFXN: LTD")] // Modulr don't support ':' public void Validate_Account_Name_Fails(string accountName) { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = accountName, - Currency = CurrencyTypeEnum.EUR, - TotalAmount = 11.00M, - DestinationIban = "IE83MOCK91012396989925" + { + InvoiceReference = "ref-1", + DestinationAccountName = accountName, + Currency = CurrencyTypeEnum.EUR, + TotalAmount = 11.00M, + DestinationIban = "IE83MOCK91012396989925" }; var problem = payrunInvoice.Validate(); @@ -95,41 +95,41 @@ public void Validate_Account_Name_Fails(string accountName) _logger.LogDebug(problem.Detail); Assert.False(problem.IsEmpty); - } - + } + /// /// Validates that an invoice with a valid IBAN passes validation. /// [Fact] public void Validate_Payout_Valid_IBAN_Success() - { + { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = "Some Biz", - DestinationIban = "IE83MOCK91012396989925", - Currency = CurrencyTypeEnum.EUR, - TotalAmount = 11.00M + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + DestinationIban = "IE83MOCK91012396989925", + Currency = CurrencyTypeEnum.EUR, + TotalAmount = 11.00M }; var result = payrunInvoice.Validate(); Assert.True(result.IsEmpty); - } + } /// /// Validates that an invoice with an invalid IBAN fails validation. /// [Fact] public void Validate_Payout_Invalid_IBAN_Fails() - { + { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = "Some Biz", - DestinationIban = "IE36ULSB98501017331006", - Currency = CurrencyTypeEnum.EUR, - TotalAmount = 11.00M + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + DestinationIban = "IE36ULSB98501017331006", + Currency = CurrencyTypeEnum.EUR, + TotalAmount = 11.00M }; var result = payrunInvoice.Validate(); @@ -145,61 +145,61 @@ public void Validate_Payout_Invalid_IBAN_Fails() /// [Fact] public void Validate_Payout_Valid_SCAN_Success() - { + { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = "Some Biz", - DestinationSortCode = "123456", - DestinationAccountNumber = "12345678", - Currency = CurrencyTypeEnum.GBP, - TotalAmount = 11.00M + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + DestinationSortCode = "123456", + DestinationAccountNumber = "12345678", + Currency = CurrencyTypeEnum.GBP, + TotalAmount = 11.00M }; var result = payrunInvoice.Validate(); Assert.True(result.IsEmpty); - } - - /// - /// Tests than a EUR invoice mssing an IBAN fails validation. + } + + /// + /// Tests than a EUR invoice mssing an IBAN fails validation. /// [Fact] public void Invoice_Missing_IBAN_Failure() - { + { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = "Some Biz", - Currency = CurrencyTypeEnum.EUR, - TotalAmount = 11.00M - }; - + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + Currency = CurrencyTypeEnum.EUR, + TotalAmount = 11.00M + }; + var result = payrunInvoice.Validate(); _logger.LogDebug(result.ToJsonFormatted()); Assert.False(result.IsEmpty); - } - - /// - /// Tests than a GBP invoice mssing SCAN details fails validation. + } + + /// + /// Tests than a GBP invoice mssing SCAN details fails validation. /// [Fact] public void Invoice_Missing_SCAN_Failure() - { + { var payrunInvoice = new PayrunInvoice - { - Reference = "ref-1", - DestinationAccountName = "Some Biz", - Currency = CurrencyTypeEnum.GBP, - TotalAmount = 11.00M - }; - - var result = payrunInvoice.Validate(); - - _logger.LogDebug(result.ToJsonFormatted()); - + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + Currency = CurrencyTypeEnum.GBP, + TotalAmount = 11.00M + }; + + var result = payrunInvoice.Validate(); + + _logger.LogDebug(result.ToJsonFormatted()); + Assert.False(result.IsEmpty); } } From b4c717bb165636514cc0520a2dcaccd2731d97ad Mon Sep 17 00:00:00 2001 From: sauravmaiti22 Date: Thu, 7 Nov 2024 14:45:45 +0000 Subject: [PATCH 2/4] Added tests --- src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs index e315f33..ca637bc 100644 --- a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs +++ b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs @@ -22,7 +22,8 @@ namespace NoFrixion.MoneyMoov.Models; public class PayrunInvoice : IValidatableObject { - const int PAYRUN_INVOICE_PAYMENT_REFERENCE_MAX_LENGTH = 18; + public const int PAYRUN_INVOICE_PAYMENT_REFERENCE_MAX_LENGTH = 18; + public Guid ID { get; set; } public Guid PayRunID { get; set; } From e45196d2eaba6807f970745332597add209807d3 Mon Sep 17 00:00:00 2001 From: sauravmaiti22 Date: Thu, 7 Nov 2024 16:02:29 +0000 Subject: [PATCH 3/4] Added some more tests --- .../Models/Payruns/PayrunInvoice.cs | 11 +++- .../Models/PayrunInvoiceValidatorTests.cs | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs index ca637bc..be6ca49 100644 --- a/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs +++ b/src/NoFrixion.MoneyMoov/Models/Payruns/PayrunInvoice.cs @@ -30,7 +30,7 @@ public class PayrunInvoice : IValidatableObject public string? Name { get; set; } - [Obsolete("Please use Reference instead.")] + [Obsolete("Please use InvoiceReference instead.")] public string? InvoiceNumber { get; set; } [Obsolete("Please use InvoiceReference instead.")] @@ -96,8 +96,15 @@ public string? Reference public string? PaymentReference { get; set; } public NoFrixionProblem Validate() - => this.ToPayout().Validate(); + { + var results = new List(); + var contextInvoice = new ValidationContext(this, serviceProvider: null, items: null); + var isValid = Validator.TryValidateObject(this, contextInvoice, results, true); + + return isValid ? NoFrixionProblem.Empty : new NoFrixionProblem("The PayrunInvoice model has one or more validation errors.", results); + } + public IEnumerable Validate(ValidationContext validationContext) => this.ToPayout().Validate(validationContext); } \ No newline at end of file diff --git a/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs b/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs index db515d6..194e71b 100644 --- a/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs +++ b/test/MoneyMoov.UnitTests/Models/PayrunInvoiceValidatorTests.cs @@ -202,4 +202,61 @@ public void Invoice_Missing_SCAN_Failure() Assert.False(result.IsEmpty); } + + [Fact] + public void Invoice_With_PaymentReference_MaxLength_Validates_Success() + { + var payrunInvoice = new PayrunInvoice + { + InvoiceReference = "ref-1", + DestinationAccountName = "Some Biz", + Currency = CurrencyTypeEnum.EUR, + TotalAmount = 11.00M, + PaymentReference = "12345678901234567890" // More than 18 characters. + }; + + var result = payrunInvoice.Validate(); + + _logger.LogDebug(result.ToJsonFormatted()); + + Assert.False(result.IsEmpty); + } + + [Fact] + public void Invoice_With_Empty_InvoiceReference_Returns_Error() + { + var payrunInvoice = new PayrunInvoice + { + InvoiceReference = "", + DestinationAccountName = "Some Biz", + Currency = CurrencyTypeEnum.EUR, + DestinationIban = "IE83MOCK91012396989925", + TotalAmount = 11.00M + }; + + var result = payrunInvoice.Validate(); + + _logger.LogDebug(result.ToJsonFormatted()); + + Assert.False(result.IsEmpty); + } + + [Fact] + public void Invoice_With_Obsolete_Reference_Field_Set_Success() + { + var payrunInvoice = new PayrunInvoice + { +#pragma warning disable CS0618 // Type or member is obsolete + Reference = "ref-1", +#pragma warning restore CS0618 // Type or member is obsolete + DestinationAccountName = "Some Biz", + Currency = CurrencyTypeEnum.EUR, + DestinationIban = "IE83MOCK91012396989925", + TotalAmount = 11.00M + }; + + var result = payrunInvoice.Validate(); + + Assert.True(result.IsEmpty); + } } From fb0fb9c06257ec151a4856a47c3b32d412f78da1 Mon Sep 17 00:00:00 2001 From: sauravmaiti22 Date: Thu, 7 Nov 2024 16:05:52 +0000 Subject: [PATCH 4/4] Added nullorwhitespace check --- src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs b/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs index 58d346f..6fa9c9a 100644 --- a/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs +++ b/src/NoFrixion.MoneyMoov/Mapping/PayrunInvoiceMapper.cs @@ -36,12 +36,14 @@ public static Payout ToPayout(this PayrunInvoice invoice) Currency = invoice.Currency, Type = invoice.Currency == CurrencyTypeEnum.GBP ? AccountIdentifierType.SCAN : AccountIdentifierType.IBAN, Amount = invoice.TotalAmount, - TheirReference = invoice.PaymentReference ?? SAFE_THEIR_REFERENCE, + TheirReference = !string.IsNullOrWhiteSpace(invoice.PaymentReference) + ? invoice.PaymentReference + : SAFE_THEIR_REFERENCE, Destination = new Counterparty { Name = invoice.DestinationAccountName, - Identifier = new AccountIdentifier - { + Identifier = new AccountIdentifier + { Currency = invoice.Currency, IBAN = invoice.DestinationIban, SortCode = invoice.DestinationSortCode,