;;;; -*- coding:utf-8 -*- ;;;;**************************************************************************** ;;;;FILE: iban.lisp ;;;;LANGUAGE: Common-Lisp ;;;;SYSTEM: Common-Lisp ;;;;USER-INTERFACE: NONE ;;;;DESCRIPTION ;;;; ;;;; This class is an Internationnal Bank Account Number, ;;;; according to European standard. ;;;; ;;;; IBAN Format ;;;; ;;;;AUTHORS ;;;; Pascal J. Bourguignon ;;;;MODIFICATIONS ;;;; 2004-10-10 Created. ;;;;BUGS ;;;; The verification of the country code accepts all existing countries ;;;; as defined by iso-3166. Some of these country code are not used ;;;; (GP --> FR for example). So an incorrect use of GP is not detected. ;;;;LEGAL ;;;; GPL ;;;; ;;;; Copyright Pascal J. Bourguignon 2004 - 2004 ;;;; ;;;; This program is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU General Public License ;;;; as published by the Free Software Foundation; either version ;;;; 2 of the License, or (at your option) any later version. ;;;; ;;;; This program is distributed in the hope that it will be ;;;; useful, but WITHOUT ANY WARRANTY; without even the implied ;;;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;;;; PURPOSE. See the GNU General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU General Public ;;;; License along with this program; if not, write to the Free ;;;; Software Foundation, Inc., 59 Temple Place, Suite 330, ;;;; Boston, MA 02111-1307 USA ;;;;**************************************************************************** (IN-PACKAGE "COMMON-LISP-USER") (DECLAIM (DECLARATION ALSO-USE-PACKAGES) (ALSO-USE-PACKAGES "COM.INFORMATIMAGO.COMMON-LISP.ISO3166")) (DEFPACKAGE "COM.INFORMATIMAGO.COMMON-LISP.IBAN" (:USE "COM.INFORMATIMAGO.COMMON-LISP.LIST" "COM.INFORMATIMAGO.COMMON-LISP.STRING" "COM.INFORMATIMAGO.COMMON-LISP.UTILITY" "COMMON-LISP") (:EXPORT "GET-AND-CHECK-ALPHANUM" "COMPUTE-IBAN-KEY" "CHECK-IBAN-KEY" "GET-IBAN" "GET-KEY" "GET-COUNTRY-CODE" "SET-IBAN" "GET-IBAN" "GET-KEY" "GET-COUNTRY-CODE" "CHECK-COUNTRY" "BASIC-FORM" "IBAN" "IBAN-ERROR") (:DOCUMENTATION "")) (IN-PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.IBAN") (DEFINE-CONDITION IBAN-ERROR (ERROR) () (:DOCUMENTATION "An IBAN error.")) (DEFCLASS IBAN () ((BASIC-FORM :READER BASIC-FORM :INITFORM "FR00000000000000000000000" :INITARG :BASIC-FORM :TYPE STRING)) ) ;;IBAN (DEFGENERIC GET-AND-CHECK-ALPHANUM (SELF STRING &OPTIONAL LENGTH)) (DEFGENERIC CHECK-COUNTRY (SELF)) (DEFGENERIC GET-COUNTRY-CODE (SELF)) (DEFGENERIC GET-KEY (SELF)) (DEFGENERIC GET-IBAN (SELF &KEY WITH-SPACES)) (DEFGENERIC SET-IBAN (SELF IBAN &KEY WITH-KEY)) (DEFMETHOD INITIALIZE-INSTANCE ((SELF IBAN) &REST ARGS) (DECLARE (IGNORE ARGS)) (CALL-NEXT-METHOD) (WHEN (BASIC-FORM SELF) (SET-IBAN SELF (BASIC-FORM SELF))) SELF) ;;INITIALIZE-INSTANCE (DEFMETHOD GET-COUNTRY-CODE ((SELF IBAN)) " RETURN: The country code in the IBAN. " (SUBSEQ (BASIC-FORM SELF) 0 2)) ;;GET-COUNTRY-CODE (DEFMETHOD GET-KEY ((SELF IBAN)) " RETURN: The computed key of the IBAN. " (SUBSEQ (SLOT-VALUE SELF 'BASIC-FORM) 2 4)) ;;GET-KEY (DEFMETHOD GET-IBAN ((SELF IBAN) &KEY (WITH-SPACES NIL)) " RETURN: The IBAN, with spaces inserted when WITH-SPACES is true, else in basic form. " (IF WITH-SPACES (DO ((IBAN (BASIC-FORM SELF)) (RES '()) (I 0 (+ I 4))) ((>= (+ I 4) (LENGTH IBAN)) (PROGN (PUSH (SUBSEQ IBAN I) RES) (APPLY (FUNCTION CONCATENATE) 'STRING (NREVERSE RES)))) (PUSH (SUBSEQ IBAN I (+ I 4)) RES) (PUSH " " RES)) (COPY-SEQ (BASIC-FORM SELF)))) ;;GET-IBAN ;; We test and convert to upper case letters, because ;; the RIB and IBAN may contain only the following characters: ;; 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ (DEFPARAMETER +ALPHABET-FROM+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") (DEFMETHOD GET-AND-CHECK-ALPHANUM ((SELF IBAN) STRING &OPTIONAL LENGTH) (WHEN (AND LENGTH (/= LENGTH (LENGTH STRING))) (SIGNAL 'IBAN-ERROR "For IBAN ~S:~% Bad length, expected ~D, got ~D: ~S" SELF LENGTH (LENGTH STRING) STRING)) (MAP 'STRING (LAMBDA (CH) (LET ((INDEX (POSITION CH +ALPHABET-FROM+))) (UNLESS INDEX (SIGNAL 'IBAN-ERROR "For IBAN ~S:~% Bad character '~C' in ~S, ~ should be alphanumeric." SELF CH STRING)) (AREF +ALPHABET-FROM+ (IF (< INDEX 36) INDEX (- INDEX 26))))) STRING)) (DEFPARAMETER +COUNTRY-CODES+ (MAPCAR (FUNCTION THIRD) (COM.INFORMATIMAGO.COMMON-LISP.ISO3166:GET-COUNTRIES :ONLY-EXISTING T)) "List of 2-letter country codes.") ;;+COUNTRY-CODES+ (DEFMETHOD CHECK-COUNTRY ((SELF IBAN)) " DO: Checks the country code in the basic-form, and raises an error if not valid. RAISE: IBAN-ERROR RETURN: SELF " (LET ((CC (SUBSEQ (BASIC-FORM SELF) 0 2))) (UNLESS (MEMBER CC +COUNTRY-CODES+ :TEST (FUNCTION STRING-EQUAL)) (SIGNAL 'IBAN-ERROR "For IBAN ~S:~% Bad country code: ~S" SELF CC))) SELF) ;;CHECK-COUNTRY (DEFUN CHECK-IBAN-KEY (IBAN) ;; IBAN must be in basic format, all non alphanumeric characters removed. ;; 0- move the first four characters of the IBAN to the end. ;; 1- convert the letters into numerics. ;; 2- apply MOD 97-10 (ISO 7064) : remainder of n by 97 must be 1 ;; 3- return T when the IBAN key checks. (= 1 (MOD (LOOP FOR CH ACROSS (CONCATENATE 'STRING (SUBSEQ IBAN 4) (SUBSEQ IBAN 0 4)) WITH N = 0 DO (SETF N (+ (* (IF (ALPHA-CHAR-P CH) 100 10) N) (PARSE-INTEGER (STRING CH) :RADIX 36 :JUNK-ALLOWED NIL))) FINALLY (RETURN N)) 97))) ;;CHECK-IBAN-KEY (DEFUN COMPUTE-IBAN-KEY (COUNTRY ACCOUNT) ;; ACCOUNT must be in basic format, all non alphanumeric characters removed. ;; 0- create artificial IBAN with 00 check sum. ;; 1- move the first four characters of the IBAN to the end. ;; 2- convert the letters into numerics. ;; 3- apply MOD 97-10 (ISO 7064): check sum is 98 - n mod 97. ;; 4- return the complete IBAN. (FORMAT NIL "~2A~2,'0D~A" COUNTRY (- 98 (MOD (LOOP FOR CH ACROSS (CONCATENATE 'STRING ACCOUNT COUNTRY "00") WITH N = 0 DO (SETF N (+ (* (IF (ALPHA-CHAR-P CH) 100 10) N) (PARSE-INTEGER (STRING CH) :RADIX 36 :JUNK-ALLOWED NIL))) FINALLY (RETURN N)) 97)) ACCOUNT)) ;;COMPUTE-IBAN-KEY (DEFMETHOD SET-IBAN ((SELF IBAN) (IBAN STRING) &KEY (WITH-KEY NIL)) " DO: Change the IBAN. If WITH-KEY is true then the IBAN key is checked and an error raised if it is not valid, else the IBAN key is computed and substituted. RETURN: SELF RAISE: An IBAN-ERROR when with-key and the key in the IBAN is incorrect. " (SETF IBAN (GET-AND-CHECK-ALPHANUM SELF (REMOVE-IF (COMPLEMENT (FUNCTION ALPHANUMERICP)) IBAN))) (SETF (SLOT-VALUE SELF 'BASIC-FORM) (IF WITH-KEY (IF (CHECK-IBAN-KEY IBAN) (SIGNAL 'IBAN-ERROR "For IBAN ~S~% Invalid key, given=~S, computed=~S." (SUBSEQ IBAN 2 4) (SUBSEQ (COMPUTE-IBAN-KEY (SUBSEQ IBAN 0 2) (SUBSEQ IBAN 4)) 2 4)) IBAN) (COMPUTE-IBAN-KEY (SUBSEQ IBAN 0 2) (SUBSEQ IBAN 4)))) (CHECK-COUNTRY SELF) SELF) ;;SET-IBAN ;;;; iban.lisp -- -- ;;;;