183 lines
6.3 KiB
JavaScript
183 lines
6.3 KiB
JavaScript
|
import assertString from './util/assertString';
|
||
|
/**
|
||
|
* List of country codes with
|
||
|
* corresponding IBAN regular expression
|
||
|
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
|
||
|
*/
|
||
|
|
||
|
var ibanRegexThroughCountryCode = {
|
||
|
AD: /^(AD[0-9]{2})\d{8}[A-Z0-9]{12}$/,
|
||
|
AE: /^(AE[0-9]{2})\d{3}\d{16}$/,
|
||
|
AL: /^(AL[0-9]{2})\d{8}[A-Z0-9]{16}$/,
|
||
|
AT: /^(AT[0-9]{2})\d{16}$/,
|
||
|
AZ: /^(AZ[0-9]{2})[A-Z0-9]{4}\d{20}$/,
|
||
|
BA: /^(BA[0-9]{2})\d{16}$/,
|
||
|
BE: /^(BE[0-9]{2})\d{12}$/,
|
||
|
BG: /^(BG[0-9]{2})[A-Z]{4}\d{6}[A-Z0-9]{8}$/,
|
||
|
BH: /^(BH[0-9]{2})[A-Z]{4}[A-Z0-9]{14}$/,
|
||
|
BR: /^(BR[0-9]{2})\d{23}[A-Z]{1}[A-Z0-9]{1}$/,
|
||
|
BY: /^(BY[0-9]{2})[A-Z0-9]{4}\d{20}$/,
|
||
|
CH: /^(CH[0-9]{2})\d{5}[A-Z0-9]{12}$/,
|
||
|
CR: /^(CR[0-9]{2})\d{18}$/,
|
||
|
CY: /^(CY[0-9]{2})\d{8}[A-Z0-9]{16}$/,
|
||
|
CZ: /^(CZ[0-9]{2})\d{20}$/,
|
||
|
DE: /^(DE[0-9]{2})\d{18}$/,
|
||
|
DK: /^(DK[0-9]{2})\d{14}$/,
|
||
|
DO: /^(DO[0-9]{2})[A-Z]{4}\d{20}$/,
|
||
|
EE: /^(EE[0-9]{2})\d{16}$/,
|
||
|
EG: /^(EG[0-9]{2})\d{25}$/,
|
||
|
ES: /^(ES[0-9]{2})\d{20}$/,
|
||
|
FI: /^(FI[0-9]{2})\d{14}$/,
|
||
|
FO: /^(FO[0-9]{2})\d{14}$/,
|
||
|
FR: /^(FR[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
|
||
|
GB: /^(GB[0-9]{2})[A-Z]{4}\d{14}$/,
|
||
|
GE: /^(GE[0-9]{2})[A-Z0-9]{2}\d{16}$/,
|
||
|
GI: /^(GI[0-9]{2})[A-Z]{4}[A-Z0-9]{15}$/,
|
||
|
GL: /^(GL[0-9]{2})\d{14}$/,
|
||
|
GR: /^(GR[0-9]{2})\d{7}[A-Z0-9]{16}$/,
|
||
|
GT: /^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$/,
|
||
|
HR: /^(HR[0-9]{2})\d{17}$/,
|
||
|
HU: /^(HU[0-9]{2})\d{24}$/,
|
||
|
IE: /^(IE[0-9]{2})[A-Z0-9]{4}\d{14}$/,
|
||
|
IL: /^(IL[0-9]{2})\d{19}$/,
|
||
|
IQ: /^(IQ[0-9]{2})[A-Z]{4}\d{15}$/,
|
||
|
IR: /^(IR[0-9]{2})0\d{2}0\d{18}$/,
|
||
|
IS: /^(IS[0-9]{2})\d{22}$/,
|
||
|
IT: /^(IT[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
|
||
|
JO: /^(JO[0-9]{2})[A-Z]{4}\d{22}$/,
|
||
|
KW: /^(KW[0-9]{2})[A-Z]{4}[A-Z0-9]{22}$/,
|
||
|
KZ: /^(KZ[0-9]{2})\d{3}[A-Z0-9]{13}$/,
|
||
|
LB: /^(LB[0-9]{2})\d{4}[A-Z0-9]{20}$/,
|
||
|
LC: /^(LC[0-9]{2})[A-Z]{4}[A-Z0-9]{24}$/,
|
||
|
LI: /^(LI[0-9]{2})\d{5}[A-Z0-9]{12}$/,
|
||
|
LT: /^(LT[0-9]{2})\d{16}$/,
|
||
|
LU: /^(LU[0-9]{2})\d{3}[A-Z0-9]{13}$/,
|
||
|
LV: /^(LV[0-9]{2})[A-Z]{4}[A-Z0-9]{13}$/,
|
||
|
MA: /^(MA[0-9]{26})$/,
|
||
|
MC: /^(MC[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
|
||
|
MD: /^(MD[0-9]{2})[A-Z0-9]{20}$/,
|
||
|
ME: /^(ME[0-9]{2})\d{18}$/,
|
||
|
MK: /^(MK[0-9]{2})\d{3}[A-Z0-9]{10}\d{2}$/,
|
||
|
MR: /^(MR[0-9]{2})\d{23}$/,
|
||
|
MT: /^(MT[0-9]{2})[A-Z]{4}\d{5}[A-Z0-9]{18}$/,
|
||
|
MU: /^(MU[0-9]{2})[A-Z]{4}\d{19}[A-Z]{3}$/,
|
||
|
MZ: /^(MZ[0-9]{2})\d{21}$/,
|
||
|
NL: /^(NL[0-9]{2})[A-Z]{4}\d{10}$/,
|
||
|
NO: /^(NO[0-9]{2})\d{11}$/,
|
||
|
PK: /^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$/,
|
||
|
PL: /^(PL[0-9]{2})\d{24}$/,
|
||
|
PS: /^(PS[0-9]{2})[A-Z0-9]{4}\d{21}$/,
|
||
|
PT: /^(PT[0-9]{2})\d{21}$/,
|
||
|
QA: /^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/,
|
||
|
RO: /^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$/,
|
||
|
RS: /^(RS[0-9]{2})\d{18}$/,
|
||
|
SA: /^(SA[0-9]{2})\d{2}[A-Z0-9]{18}$/,
|
||
|
SC: /^(SC[0-9]{2})[A-Z]{4}\d{20}[A-Z]{3}$/,
|
||
|
SE: /^(SE[0-9]{2})\d{20}$/,
|
||
|
SI: /^(SI[0-9]{2})\d{15}$/,
|
||
|
SK: /^(SK[0-9]{2})\d{20}$/,
|
||
|
SM: /^(SM[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
|
||
|
SV: /^(SV[0-9]{2})[A-Z0-9]{4}\d{20}$/,
|
||
|
TL: /^(TL[0-9]{2})\d{19}$/,
|
||
|
TN: /^(TN[0-9]{2})\d{20}$/,
|
||
|
TR: /^(TR[0-9]{2})\d{5}[A-Z0-9]{17}$/,
|
||
|
UA: /^(UA[0-9]{2})\d{6}[A-Z0-9]{19}$/,
|
||
|
VA: /^(VA[0-9]{2})\d{18}$/,
|
||
|
VG: /^(VG[0-9]{2})[A-Z0-9]{4}\d{16}$/,
|
||
|
XK: /^(XK[0-9]{2})\d{16}$/
|
||
|
};
|
||
|
/**
|
||
|
* Check if the country codes passed are valid using the
|
||
|
* ibanRegexThroughCountryCode as a reference
|
||
|
*
|
||
|
* @param {array} countryCodeArray
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
|
||
|
function hasOnlyValidCountryCodes(countryCodeArray) {
|
||
|
var countryCodeArrayFilteredWithObjectIbanCode = countryCodeArray.filter(function (countryCode) {
|
||
|
return !(countryCode in ibanRegexThroughCountryCode);
|
||
|
});
|
||
|
|
||
|
if (countryCodeArrayFilteredWithObjectIbanCode.length > 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* Check whether string has correct universal IBAN format
|
||
|
* The IBAN consists of up to 34 alphanumeric characters, as follows:
|
||
|
* Country Code using ISO 3166-1 alpha-2, two letters
|
||
|
* check digits, two digits and
|
||
|
* Basic Bank Account Number (BBAN), up to 30 alphanumeric characters.
|
||
|
* NOTE: Permitted IBAN characters are: digits [0-9] and the 26 latin alphabetic [A-Z]
|
||
|
*
|
||
|
* @param {string} str - string under validation
|
||
|
* @param {object} options - object to pass the countries to be either whitelisted or blacklisted
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
|
||
|
|
||
|
function hasValidIbanFormat(str, options) {
|
||
|
// Strip white spaces and hyphens
|
||
|
var strippedStr = str.replace(/[\s\-]+/gi, '').toUpperCase();
|
||
|
var isoCountryCode = strippedStr.slice(0, 2).toUpperCase();
|
||
|
var isoCountryCodeInIbanRegexCodeObject = (isoCountryCode in ibanRegexThroughCountryCode);
|
||
|
|
||
|
if (options.whitelist) {
|
||
|
if (!hasOnlyValidCountryCodes(options.whitelist)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var isoCountryCodeInWhiteList = options.whitelist.includes(isoCountryCode);
|
||
|
|
||
|
if (!isoCountryCodeInWhiteList) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (options.blacklist) {
|
||
|
var isoCountryCodeInBlackList = options.blacklist.includes(isoCountryCode);
|
||
|
|
||
|
if (isoCountryCodeInBlackList) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return isoCountryCodeInIbanRegexCodeObject && ibanRegexThroughCountryCode[isoCountryCode].test(strippedStr);
|
||
|
}
|
||
|
/**
|
||
|
* Check whether string has valid IBAN Checksum
|
||
|
* by performing basic mod-97 operation and
|
||
|
* the remainder should equal 1
|
||
|
* -- Start by rearranging the IBAN by moving the four initial characters to the end of the string
|
||
|
* -- Replace each letter in the string with two digits, A -> 10, B = 11, Z = 35
|
||
|
* -- Interpret the string as a decimal integer and
|
||
|
* -- compute the remainder on division by 97 (mod 97)
|
||
|
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
|
||
|
|
||
|
function hasValidIbanChecksum(str) {
|
||
|
var strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase(); // Keep only digits and A-Z latin alphabetic
|
||
|
|
||
|
var rearranged = strippedStr.slice(4) + strippedStr.slice(0, 4);
|
||
|
var alphaCapsReplacedWithDigits = rearranged.replace(/[A-Z]/g, function (_char) {
|
||
|
return _char.charCodeAt(0) - 55;
|
||
|
});
|
||
|
var remainder = alphaCapsReplacedWithDigits.match(/\d{1,7}/g).reduce(function (acc, value) {
|
||
|
return Number(acc + value) % 97;
|
||
|
}, '');
|
||
|
return remainder === 1;
|
||
|
}
|
||
|
|
||
|
export default function isIBAN(str) {
|
||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||
|
assertString(str);
|
||
|
return hasValidIbanFormat(str, options) && hasValidIbanChecksum(str);
|
||
|
}
|
||
|
export var locales = Object.keys(ibanRegexThroughCountryCode);
|