From 544c67fc24885782bee9c8641cfcad105b085486 Mon Sep 17 00:00:00 2001
From: donalnofrixion <89344328+donalnofrixion@users.noreply.github.com>
Date: Tue, 17 Sep 2024 11:51:55 +0100
Subject: [PATCH] Validate DestinationSortCode is 6 digits and
DestinationAccountNumber is eight digits
---
.../Models/Payouts/PayoutsValidator.cs | 22 +++
.../Models/PayoutsValidatorTests.cs | 174 +++++++++++++-----
2 files changed, 145 insertions(+), 51 deletions(-)
diff --git a/src/NoFrixion.MoneyMoov/Models/Payouts/PayoutsValidator.cs b/src/NoFrixion.MoneyMoov/Models/Payouts/PayoutsValidator.cs
index abdc1990..36670bfc 100755
--- a/src/NoFrixion.MoneyMoov/Models/Payouts/PayoutsValidator.cs
+++ b/src/NoFrixion.MoneyMoov/Models/Payouts/PayoutsValidator.cs
@@ -145,6 +145,16 @@ public static class PayoutsValidator
///
public const decimal BITCOIN_CURRENCY_RESOLUTION = 0.0000_0001M;
+ ///
+ /// The required length for a SCAN account number.
+ ///
+ private const int SCAN_REQUIRED_ACCOUNT_NUMBER_LENGTH = 8;
+
+ ///
+ /// The required length for a SCAN sort code.
+ ///
+ private const int SCAN_REQUIRED_SORT_CODE_LENGTH = 6;
+
public static bool ValidateIBAN(string iban)
{
if (string.IsNullOrEmpty(iban))
@@ -389,6 +399,18 @@ public static IEnumerable Validate(Payout payout, ValidationCo
{
yield return new ValidationResult($"Currency {payout.Currency} cannot be used with SCAN destinations.", new string[] { nameof(payout.Currency) });
}
+
+ if (payout.Type == AccountIdentifierType.SCAN && payout.Destination?.Identifier != null &&
+ !string.IsNullOrEmpty(payout.Destination?.Identifier?.AccountNumber) && payout.Destination.Identifier.AccountNumber.Length != SCAN_REQUIRED_ACCOUNT_NUMBER_LENGTH)
+ {
+ yield return new ValidationResult("Destination account number must be eight digits for a SCAN payout type.", new string[] { nameof(payout.Destination.Identifier.AccountNumber) });
+ }
+
+ if (payout.Type == AccountIdentifierType.SCAN && payout.Destination?.Identifier != null &&
+ !string.IsNullOrEmpty(payout.Destination?.Identifier?.SortCode) && payout.Destination.Identifier.SortCode.Length != SCAN_REQUIRED_SORT_CODE_LENGTH)
+ {
+ yield return new ValidationResult("Destination sort code must be six digits for a SCAN payout type.", new string[] { nameof(payout.Destination.Identifier.SortCode) });
+ }
}
if (payout.Destination?.Identifier != null)
diff --git a/test/MoneyMoov.UnitTests/Models/PayoutsValidatorTests.cs b/test/MoneyMoov.UnitTests/Models/PayoutsValidatorTests.cs
index c638b6e4..1e6be309 100755
--- a/test/MoneyMoov.UnitTests/Models/PayoutsValidatorTests.cs
+++ b/test/MoneyMoov.UnitTests/Models/PayoutsValidatorTests.cs
@@ -46,8 +46,8 @@ public void PaymentsValidator_ValidateTheirReference_Success_Modulr(string their
var result = PayoutsValidator.ValidateTheirReference(theirReference, MoneyMoov.AccountIdentifierType.IBAN, PaymentProcessorsEnum.Modulr);
Assert.True(result);
- }
-
+ }
+
///
/// Tests that a payout property TheirReference is validated successfully.
///
@@ -56,12 +56,12 @@ public void PaymentsValidator_ValidateTheirReference_Success_Modulr(string their
[InlineData("?./refe-12")]
[InlineData("r12 hsd-2")]
[InlineData("s-d73/sdf.4(8) ?:,'++")]
- [InlineData("s-D7 K sdf -")]
- [InlineData("Saldo F16 + F20")]
+ [InlineData("s-D7 K sdf -")]
+ [InlineData("Saldo F16 + F20")]
[InlineData("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD")]
public void PaymentsValidator_ValidateTheirReference_Success_Banking_Circle(string theirReference)
{
- var result =
+ var result =
PayoutsValidator.ValidateTheirReference(theirReference, MoneyMoov.AccountIdentifierType.IBAN, PaymentProcessorsEnum.BankingCircle) &&
PayoutsValidator.ValidateTheirReference(theirReference, MoneyMoov.AccountIdentifierType.IBAN, PaymentProcessorsEnum.BankingCircleAgency);
@@ -83,8 +83,8 @@ public void PaymentsValidator_ValidateTheirReference_Fail(string theirReference,
var result = PayoutsValidator.ValidateTheirReference(theirReference, identifierType, PaymentProcessorsEnum.Modulr);
Assert.False(result);
- }
-
+ }
+
///
/// Tests that an invalid payout TheirReference fails the Banking Circle validation rules.
///
@@ -132,8 +132,8 @@ public void PaymentsValidator_Modulr_Validate_Account_Name_Fails(string accountN
var result = PayoutsValidator.IsValidAccountName(accountName, PaymentProcessorsEnum.Modulr);
Assert.False(result);
- }
-
+ }
+
///
/// Tests that a payout Account Name is validated successfully.
///
@@ -249,10 +249,10 @@ public enum ComparisonMethod
[InlineData(AccountIdentifierType.IBAN, "-sD7&K.sdf./", "-sD7&K.sdf./", ComparisonMethod.Equals)] // If valid, gets left alone.
[InlineData(AccountIdentifierType.IBAN, "-sD7!&K.sdf./", "sD7Ksdf", ComparisonMethod.Equals)] // If invalid, non-ASCII chars get replaced.
[InlineData(AccountIdentifierType.IBAN, "Acme™ Corporation", "Acme Corporation", ComparisonMethod.Equals)] // Replace unicode.
- [InlineData(AccountIdentifierType.SCAN, "Too long for SCAN which only likes short refs", "Too long for SCAN", ComparisonMethod.Equals)]
- [InlineData(AccountIdentifierType.SCAN, "Just rght for SCAN", "Just rght for SCAN", ComparisonMethod.Equals)]
- [InlineData(AccountIdentifierType.IBAN,
- "Just right for IBAN processing making it simple and efficient for all your international banking needs. Ensure accuracy and security always.",
+ [InlineData(AccountIdentifierType.SCAN, "Too long for SCAN which only likes short refs", "Too long for SCAN", ComparisonMethod.Equals)]
+ [InlineData(AccountIdentifierType.SCAN, "Just rght for SCAN", "Just rght for SCAN", ComparisonMethod.Equals)]
+ [InlineData(AccountIdentifierType.IBAN,
+ "Just right for IBAN processing making it simple and efficient for all your international banking needs. Ensure accuracy and security always.",
"Just right for IBAN processing making it simple and efficient for all your international banking needs. Ensure accuracy and security always.",
ComparisonMethod.Equals)]
public void PaymentsValidator_Make_Safe_TheirReference_Modulr(AccountIdentifierType accountType,
@@ -274,7 +274,7 @@ public void PaymentsValidator_Make_Safe_TheirReference_Modulr(AccountIdentifierT
{
Assert.StartsWith(expectedTheirReference, safeTheirRef);
}
- }
+ }
[Fact]
public void GetReferencesFromInvoices()
@@ -370,7 +370,7 @@ public void PayoutsValidator_Validate_GBP_Destination_Identifier_Success()
Identifier = new AccountIdentifier
{
SortCode = "123456",
- AccountNumber = "00000070629907",
+ AccountNumber = "70629907",
Currency = CurrencyTypeEnum.GBP
}
};
@@ -397,6 +397,78 @@ public void PayoutsValidator_Validate_GBP_Destination_Identifier_Success()
Assert.True(result.IsEmpty);
}
+ [Fact]
+ public void PayoutsValidator_Validate_GBP_Destination_Identifier_AccountNumber_Fail()
+ {
+ var destination = new Counterparty
+ {
+ Name = "Joe Bloggs",
+ Identifier = new AccountIdentifier
+ {
+ SortCode = "123456",
+ AccountNumber = "7062990",
+ Currency = CurrencyTypeEnum.GBP
+ }
+ };
+
+ var payout = new Payout
+ {
+ ID = Guid.NewGuid(),
+ AccountID = Guid.Parse("B2DBB4E1-5F8A-4B07-82A0-EB033E6F3421"),
+ Type = AccountIdentifierType.SCAN,
+ Description = "Xero Invoice fgfg from Demo Company (Global).",
+ Currency = CurrencyTypeEnum.GBP,
+ Amount = 11.00M,
+ YourReference = "xero-18ead957-e3bc-4b12-b5c6-d12e4bef9d24",
+ TheirReference = "Placeholder",
+ Status = PayoutStatus.PENDING_INPUT,
+ InvoiceID = "18ead957-e3bc-4b12-b5c6-d12e4bef9d24",
+ Destination = destination
+ };
+
+ var result = payout.Validate();
+
+ _logger.LogDebug(result.ToTextErrorMessage());
+
+ Assert.False(result.IsEmpty);
+ }
+
+ [Fact]
+ public void PayoutsValidator_Validate_GBP_Destination_Identifier_SortCode_Fail()
+ {
+ var destination = new Counterparty
+ {
+ Name = "Joe Bloggs",
+ Identifier = new AccountIdentifier
+ {
+ SortCode = "1234567",
+ AccountNumber = "70629907",
+ Currency = CurrencyTypeEnum.GBP
+ }
+ };
+
+ var payout = new Payout
+ {
+ ID = Guid.NewGuid(),
+ AccountID = Guid.Parse("B2DBB4E1-5F8A-4B07-82A0-EB033E6F3421"),
+ Type = AccountIdentifierType.SCAN,
+ Description = "Xero Invoice fgfg from Demo Company (Global).",
+ Currency = CurrencyTypeEnum.GBP,
+ Amount = 11.00M,
+ YourReference = "xero-18ead957-e3bc-4b12-b5c6-d12e4bef9d24",
+ TheirReference = "Placeholder",
+ Status = PayoutStatus.PENDING_INPUT,
+ InvoiceID = "18ead957-e3bc-4b12-b5c6-d12e4bef9d24",
+ Destination = destination
+ };
+
+ var result = payout.Validate();
+
+ _logger.LogDebug(result.ToTextErrorMessage());
+
+ Assert.False(result.IsEmpty);
+ }
+
[Fact]
public void PayoutsValidator_Validate_EUR_Destination_Identifier_Failure()
{
@@ -463,33 +535,33 @@ public void PayoutsValidator_Validate_GBP_Destination_Identifier_Failure()
_logger.LogDebug(result.ToTextErrorMessage());
Assert.False(result.IsEmpty);
- }
-
+ }
+
///
/// Tests that the currency resolution check is working as expected.
///
[Theory]
- [InlineData(CurrencyTypeEnum.EUR, 0.01, true)]
- [InlineData(CurrencyTypeEnum.EUR, 0.001, false)]
- [InlineData(CurrencyTypeEnum.EUR, 1.011, false)]
- [InlineData(CurrencyTypeEnum.GBP, 0.01, true)]
- [InlineData(CurrencyTypeEnum.GBP, 0.001, false)]
- [InlineData(CurrencyTypeEnum.GBP, 1.011, false)]
- [InlineData(CurrencyTypeEnum.BTC, 0.01, true)]
- [InlineData(CurrencyTypeEnum.BTC, 0.001, true)]
- [InlineData(CurrencyTypeEnum.BTC, 0.00000001, true)]
- [InlineData(CurrencyTypeEnum.BTC, 0.000000001, false)]
- [InlineData(CurrencyTypeEnum.BTC, 1.011, true)]
+ [InlineData(CurrencyTypeEnum.EUR, 0.01, true)]
+ [InlineData(CurrencyTypeEnum.EUR, 0.001, false)]
+ [InlineData(CurrencyTypeEnum.EUR, 1.011, false)]
+ [InlineData(CurrencyTypeEnum.GBP, 0.01, true)]
+ [InlineData(CurrencyTypeEnum.GBP, 0.001, false)]
+ [InlineData(CurrencyTypeEnum.GBP, 1.011, false)]
+ [InlineData(CurrencyTypeEnum.BTC, 0.01, true)]
+ [InlineData(CurrencyTypeEnum.BTC, 0.001, true)]
+ [InlineData(CurrencyTypeEnum.BTC, 0.00000001, true)]
+ [InlineData(CurrencyTypeEnum.BTC, 0.000000001, false)]
+ [InlineData(CurrencyTypeEnum.BTC, 1.011, true)]
[InlineData(CurrencyTypeEnum.BTC, 1.000000011, false)]
public void Payout_Validator_Currency_Resolution(CurrencyTypeEnum currency, decimal amount, bool isValid)
- {
- AccountIdentifier identifier = currency switch
- {
- CurrencyTypeEnum.BTC => new AccountIdentifier { Currency = currency, BitcoinAddress = "abcdefg" },
- CurrencyTypeEnum.GBP => new AccountIdentifier { Currency = currency, SortCode = "123456", AccountNumber = "00001234" } ,
- _ => new AccountIdentifier { Currency = currency, IBAN = "IE78MOCK91012352877713" }
- };
-
+ {
+ AccountIdentifier identifier = currency switch
+ {
+ CurrencyTypeEnum.BTC => new AccountIdentifier { Currency = currency, BitcoinAddress = "abcdefg" },
+ CurrencyTypeEnum.GBP => new AccountIdentifier { Currency = currency, SortCode = "123456", AccountNumber = "00001234" } ,
+ _ => new AccountIdentifier { Currency = currency, IBAN = "IE78MOCK91012352877713" }
+ };
+
var payout = new Payout
{
ID = Guid.NewGuid(),
@@ -501,24 +573,24 @@ public void Payout_Validator_Currency_Resolution(CurrencyTypeEnum currency, deci
TheirReference = "their ref",
Status = PayoutStatus.PENDING_INPUT,
InvoiceID = "18ead957-e3bc-4b12-b5c6-d12e4bef9d24",
- Destination = new Counterparty
- {
- Name = "Joe Bloggs",
- Identifier = identifier
+ Destination = new Counterparty
+ {
+ Name = "Joe Bloggs",
+ Identifier = identifier
}
};
- var result = payout.Validate();
-
- _logger.LogDebug(result.ToTextErrorMessage());
-
- if(isValid)
- {
- Assert.True(result.IsEmpty);
- }
- else
- {
- Assert.False(result.IsEmpty);
+ var result = payout.Validate();
+
+ _logger.LogDebug(result.ToTextErrorMessage());
+
+ if(isValid)
+ {
+ Assert.True(result.IsEmpty);
+ }
+ else
+ {
+ Assert.False(result.IsEmpty);
}
}
}