From 15e81b921bf219cf0700c4fab3f99f0ea9a12f9a Mon Sep 17 00:00:00 2001 From: "Dev.Mo" Date: Mon, 21 Jul 2025 19:43:56 +0300 Subject: [PATCH 1/4] my first commit --- packages/utils/src/validation/invoice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/validation/invoice.ts b/packages/utils/src/validation/invoice.ts index 597d2b628..9962eb262 100644 --- a/packages/utils/src/validation/invoice.ts +++ b/packages/utils/src/validation/invoice.ts @@ -89,5 +89,5 @@ export function isValidBankNumber( bankNumber: string ): FieldError.InvalidBankAccountNumber | true { if (isNaN(Number(bankNumber))) return FieldError.InvalidBankAccountNumber; - return true; + else return true; } From 73c5ee4e6b556de2183e2c803a88e7f10c8a86ab Mon Sep 17 00:00:00 2001 From: "Dev.Mo" Date: Thu, 24 Jul 2025 13:48:24 +0300 Subject: [PATCH 2/4] impl(utils): add isValidBankNumber function --- packages/utils/src/validation/invoice.ts | 27 ++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/validation/invoice.ts b/packages/utils/src/validation/invoice.ts index 9962eb262..d20efe850 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; - else return true; + if (isNaN(Number(bankNumber))) { + return FieldError.InvalidBankAccountNumber; + } + const cleanedNumber = bankNumber.replace(/\D/g, ""); + + const isLuhnValid = + [...cleanedNumber] + .reverse() + .map((char, index) => { + const digit = parseInt(char, 10); + 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; } From 079f1e1655b4dbce37bdf90d4af5fd74e00d0efa Mon Sep 17 00:00:00 2001 From: "Dev.Mo" Date: Thu, 24 Jul 2025 16:46:01 +0300 Subject: [PATCH 3/4] fix(utils): correct bank number validation --- packages/utils/src/validation/invoice.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/validation/invoice.ts b/packages/utils/src/validation/invoice.ts index d20efe850..1ebd88677 100644 --- a/packages/utils/src/validation/invoice.ts +++ b/packages/utils/src/validation/invoice.ts @@ -1,6 +1,7 @@ import { HTML_REGEX, INSTAPAY_REGEX, PHONE_NUMBER_REGEX } from "@/constants"; import { FieldError, type Bank, BANKS, IInvoice } from "@litespace/types"; import { getSafeInnerHtmlText } from "@/utils"; +import { number } from "zod"; export function isValidInvoiceReceiver( receiver: string, @@ -91,13 +92,13 @@ export function isValidBankNumber( if (isNaN(Number(bankNumber))) { return FieldError.InvalidBankAccountNumber; } - const cleanedNumber = bankNumber.replace(/\D/g, ""); const isLuhnValid = - [...cleanedNumber] + bankNumber + .split("") .reverse() .map((char, index) => { - const digit = parseInt(char, 10); + const digit = Number(char); return index % 2 === 1 ? digit * 2 > 9 ? digit * 2 - 9 From 059b72c3070609b5db4ee012c4fb9526e3df0b4e Mon Sep 17 00:00:00 2001 From: "Dev.Mo" Date: Thu, 24 Jul 2025 16:50:29 +0300 Subject: [PATCH 4/4] Remove additional error --- packages/utils/src/validation/invoice.ts | 1 - .../utils/tests/isValidBankNumber.test.ts | 24 ++++++++++++++++ packages/utils/tests/luhnGenerator.ts | 28 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 packages/utils/tests/isValidBankNumber.test.ts create mode 100644 packages/utils/tests/luhnGenerator.ts diff --git a/packages/utils/src/validation/invoice.ts b/packages/utils/src/validation/invoice.ts index 1ebd88677..9fdec8cd2 100644 --- a/packages/utils/src/validation/invoice.ts +++ b/packages/utils/src/validation/invoice.ts @@ -1,7 +1,6 @@ import { HTML_REGEX, INSTAPAY_REGEX, PHONE_NUMBER_REGEX } from "@/constants"; import { FieldError, type Bank, BANKS, IInvoice } from "@litespace/types"; import { getSafeInnerHtmlText } from "@/utils"; -import { number } from "zod"; export function isValidInvoiceReceiver( receiver: string, 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); +}