diff --git a/packages/utils/src/validation/invoice.ts b/packages/utils/src/validation/invoice.ts index 597d2b628..9fdec8cd2 100644 --- a/packages/utils/src/validation/invoice.ts +++ b/packages/utils/src/validation/invoice.ts @@ -88,6 +88,29 @@ export function isValidBankname( export function isValidBankNumber( bankNumber: string ): FieldError.InvalidBankAccountNumber | true { - if (isNaN(Number(bankNumber))) return FieldError.InvalidBankAccountNumber; + if (isNaN(Number(bankNumber))) { + return FieldError.InvalidBankAccountNumber; + } + + const isLuhnValid = + bankNumber + .split("") + .reverse() + .map((char, index) => { + const digit = Number(char); + return index % 2 === 1 + ? digit * 2 > 9 + ? digit * 2 - 9 + : digit * 2 + : digit; + }) + .reduce((sum, digit) => sum + digit, 0) % + 10 === + 0; + + if (!isLuhnValid) { + return FieldError.InvalidBankAccountNumber; + } + return true; } diff --git a/packages/utils/tests/isValidBankNumber.test.ts b/packages/utils/tests/isValidBankNumber.test.ts new file mode 100644 index 000000000..309aefc9a --- /dev/null +++ b/packages/utils/tests/isValidBankNumber.test.ts @@ -0,0 +1,24 @@ +import { isValidBankNumber } from "@/validation/invoice"; +import { + generateValidLuhnNumber, + generateInvalidLuhnNumber, +} from "./luhnGenerator"; +import { FieldError } from "@litespace/types"; + +describe("isValidBankNumber - Generated numbers", () => { + const validNumbers = Array.from({ length: 100 }, () => + generateValidLuhnNumber() + ); + + const invalidNumbers = Array.from({ length: 100 }, () => + generateInvalidLuhnNumber() + ); + + test.each(validNumbers)("✅ Valid: %s", (number) => { + expect(isValidBankNumber(number)).toBe(true); + }); + + test.each(invalidNumbers)("❌ Invalid: %s", (number) => { + expect(isValidBankNumber(number)).toBe(FieldError.InvalidBankAccountNumber); + }); +}); diff --git a/packages/utils/tests/luhnGenerator.ts b/packages/utils/tests/luhnGenerator.ts new file mode 100644 index 000000000..f9050fcd6 --- /dev/null +++ b/packages/utils/tests/luhnGenerator.ts @@ -0,0 +1,28 @@ +// Generate Valid Luhn Number Function +export function generateValidLuhnNumber(length = 16): string { + const digits = Array.from({ length: length - 1 }, () => + Math.floor(Math.random() * 10) + ); + + const sum = digits + .slice() + .reverse() + .map((digit, idx) => + idx % 2 === 0 ? (digit * 2 > 9 ? digit * 2 - 9 : digit * 2) : digit + ) + .reduce((acc, val) => acc + val, 0); + + const checkDigit = (10 - (sum % 10)) % 10; + digits.push(checkDigit); + + return digits.join(""); +} + +// Generate Invalid Luhn Number Function +export function generateInvalidLuhnNumber(length = 16): string { + const valid = generateValidLuhnNumber(length); + const index = Math.floor(Math.random() * (length - 1)); + const wrongDigit = (Number(valid[index]) + 1) % 10; + + return valid.slice(0, index) + wrongDigit + valid.slice(index + 1); +}