diff --git a/.changeset/tiny-bobcats-buy.md b/.changeset/tiny-bobcats-buy.md new file mode 100644 index 0000000..d3e3117 --- /dev/null +++ b/.changeset/tiny-bobcats-buy.md @@ -0,0 +1,14 @@ +--- +"@obosbbl/validation": patch +--- + +add method `no/validateAccountNumber()` + +Validates that the input is a valid Norwegian bank account number. + +``` + import { validateAccountNumber } from "@obosbbl/validation/no"; + + validateAccountNumber('12345678903') // => true + +``` diff --git a/packages/validation/README.md b/packages/validation/README.md index d378576..93f1a90 100644 --- a/packages/validation/README.md +++ b/packages/validation/README.md @@ -157,6 +157,19 @@ import { validateObosMembershipNumber } from '@obosbbl/validation/se'; validateObosMembershipNumber('0000000') // => true ``` +### validateAccountNumber() + +Validates that the value is a valid organization number. Validates the checksum of the number. + +```js +// πŸ‡³πŸ‡΄ example +import { validateAccountNumber } from '@obosbbl/validation/no'; +validateAccountNumber('12345678903'); // => true + +// πŸ‡ΈπŸ‡ͺ example +// TODO: implement +``` + ## Example usage with Zod diff --git a/packages/validation/src/no.ts b/packages/validation/src/no.ts index fe99f4c..85e5fee 100644 --- a/packages/validation/src/no.ts +++ b/packages/validation/src/no.ts @@ -159,3 +159,29 @@ export function validateNationalIdentityNumber( return isValidDate(year, month, day); } + +type AccountNumberOptions = ValidatorOptions; + +/** + * Validates that the input value is a Norwegian bank account number (kontonummer). + * + * It validates the control digit. + * + * @example + * ``` + * validateAccountNumber('XXXXXXXXXXX') // => true + * ``` + */ +export function validateAccountNumber( + value: string, + options: AccountNumberOptions = {}, +): boolean { + if (options.allowFormatting) { + // biome-ignore lint/style/noParameterAssign: + value = stripFormatting(value); + } + + // Norwegian bank account numbers use mod 11 with one control digits. + // The first one is calculated for all 11 digits + return mod11(value, [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]); +} diff --git a/packages/validation/src/validation.test.ts b/packages/validation/src/validation.test.ts index ee8a8c5..cde2cc2 100644 --- a/packages/validation/src/validation.test.ts +++ b/packages/validation/src/validation.test.ts @@ -47,6 +47,21 @@ describe('no', () => { expect(no.validateOrganizationNumber(input, options)).toBe(expected); }); + test.each([ + ['12345678903', true, undefined], + ['12345678901', false, undefined], + ['1234 56 78903', false, undefined], + ['95230820052', false, undefined], + ['9523.08.20059', false, undefined], + + ['1234 56 78903', true, { allowFormatting: true }], + ['1234 56 78903', false, { allowFormatting: false }], + ['9523.08.20059', true, { allowFormatting: true }], + ['9523.08.20059', false, { allowFormatting: false }], + ])('validateAccountNumber(%s) -> %s', (input, expected, options) => { + expect(no.validateAccountNumber(input, options)).toBe(expected); + }); + test.each([ ['1234567', true, undefined], // too short