diff --git a/org.adempiere.base/src/org/compiere/model/MBankAccount.java b/org.adempiere.base/src/org/compiere/model/MBankAccount.java index 54a6a578bc..9e7604519d 100644 --- a/org.adempiere.base/src/org/compiere/model/MBankAccount.java +++ b/org.adempiere.base/src/org/compiere/model/MBankAccount.java @@ -21,6 +21,8 @@ import java.util.Properties; import org.compiere.util.CCache; import org.compiere.util.Env; +import org.compiere.util.IBAN; +import org.compiere.util.Util; /** @@ -133,5 +135,23 @@ public class MBankAccount extends X_C_BankAccount return insert_Accounting("C_BankAccount_Acct", "C_AcctSchema_Default", null); return success; } // afterSave + + protected boolean beforeSave (boolean newRecord) + { + if (!Util.isEmpty(getIBAN())) { + setIBAN(getIBAN().trim().replace(" ", "")); + try { + if (!IBAN.isCheckDigitValid(getIBAN())) { + log.saveError("Error", "IBAN is invalid"); + return false; + } + } catch (Exception e) { + log.saveError("Error", "IBAN is invalid"); + return false; + } + } + + return true; + } } // MBankAccount diff --git a/org.adempiere.base/src/org/compiere/util/IBAN.java b/org.adempiere.base/src/org/compiere/util/IBAN.java new file mode 100644 index 0000000000..c8a58f4b0c --- /dev/null +++ b/org.adempiere.base/src/org/compiere/util/IBAN.java @@ -0,0 +1,67 @@ +package org.compiere.util; + +import java.math.BigInteger; + +import java.util.ResourceBundle; + +public class IBAN { + /** + * Determines if the given IBAN is valid based on the check digit. + * To validate the checksum: + * 1. Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid. + * 2. Move the four initial characters to the end of the string. + * 3. Replace the letters in the string with digits, expanding the string as necessary, such that A=10, B=11 and Z=35. + * 4. Convert the string to an integer and mod-97 the entire number. + * If the remainder is 1 you have a valid IBAN number. + * @param iban + * @return boolean indicating if specific IBAN has a valid check digit + */ + public static boolean isCheckDigitValid(String iban) { + if (null == iban) return false; + + int validIBANLength = getValidIBANLength(iban); + if (validIBANLength < 4) return false; + if (iban.length() != validIBANLength) return false; + + BigInteger numericIBAN = getNumericIBAN(iban, false); + + int checkDigit = numericIBAN.mod(new BigInteger("97")).intValue(); + return checkDigit == 1; + } + + /** + * Using the IBAN.properties file gets the valid fixed length value for a country code. + * Only uses the first 2 characters of the given string. + * @param countryCode + * @return + */ + public static int getValidIBANLength(String countryCode) { + String code = countryCode.substring(0,2).toUpperCase(); + String length = ResourceBundle.getBundle(IBAN.class.getCanonicalName()).getString("length."+code); + if (length == null) return -1; + return Integer.valueOf(length).intValue(); + } + + private static BigInteger getNumericIBAN(String iban, boolean isCheckDigitAtEnd) { + String endCheckDigitIBAN = iban; + if (!isCheckDigitAtEnd) { + //Move first four characters to end of string to put check digit at end + endCheckDigitIBAN = iban.substring(4) + iban.substring(0, 4); + } + StringBuffer numericIBAN = new StringBuffer(); + for (int i = 0; i < endCheckDigitIBAN.length(); i++) { + if (Character.isDigit(endCheckDigitIBAN.charAt(i))) { + numericIBAN.append(endCheckDigitIBAN.charAt(i)); + } else { + numericIBAN.append(10 + getAlphabetPosition(endCheckDigitIBAN.charAt(i))); + } + } + + return new BigInteger(numericIBAN.toString()); + } + + private static int getAlphabetPosition(char letter) { + return Character.valueOf(Character.toUpperCase(letter)).compareTo(Character.valueOf('A')); + } + +} diff --git a/org.adempiere.base/src/org/compiere/util/IBAN.properties b/org.adempiere.base/src/org/compiere/util/IBAN.properties new file mode 100644 index 0000000000..b7f8ee6107 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/util/IBAN.properties @@ -0,0 +1,45 @@ +# IBAN related properties. Includes IBAN lengths for different countries +length.AD=24 +length.AT=20 +length.BE=16 +length.BA=20 +length.BG=22 +length.CH=21 +length.CY=28 +length.CZ=24 +length.DE=22 +length.DK=18 +length.EE=20 +length.ES=24 +length.FO=18 +length.FI=18 +length.FR=27 +length.GB=22 +length.GI=23 +length.GL=18 +length.GR=27 +length.HU=28 +length.HR=21 +length.IE=22 +length.IS=26 +length.IT=27 +length.LI=21 +length.LT=20 +length.LU=20 +length.LV=21 +length.MA=24 +length.MC=27 +length.MK=19 +length.MT=31 +length.NL=18 +length.NO=15 +length.PL=28 +length.PT=25 +length.RO=24 +length.RS=22 +length.SE=24 +length.SI=19 +length.SK=24 +length.SM=27 +length.TN=24 +length.TR=26