blob: a42e1924d9c220f2816fb0e88bc0bc9e2de90715 (
about) (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
module Data.Iban (Iban, mkIban) where
import Control.Applicative ((<|>))
import Data.Attoparsec.Text as AP
import Data.Char
( digitToInt,
ord,
toUpper,
)
import Data.Text qualified as T
newtype Iban = Iban T.Text deriving (Show, Eq)
mkIban :: T.Text -> Either String Iban
mkIban t = validateIban t >> return (Iban t)
validateIban :: T.Text -> Either String ()
validateIban t = AP.parseOnly ibanP t
where
ibanP = do
countryCode <- AP.count 2 ibanLetter
checkDigits <- AP.count 2 ibanDigit
chars <- AP.many1 ibanChar
endOfInput
if length chars < 30
then
if valid countryCode checkDigits chars
then return ()
else fail $ "IBAN checksum does not match (" ++ T.unpack t ++ ")"
else fail "IBAN has more than 34 characters"
where
ibanChar = ibanDigit <|> ibanLetter
ibanDigit = toInteger . digitToInt <$> AP.digit
ibanLetter = letterToInt <$> AP.letter
letterToInt c = toInteger (ord (toUpper c) - ord 'A' + 10)
charsToInteger = foldl' (\acc d -> if d >= 10 then acc * 100 + d else acc * 10 + d) 0
ibanToInteger countryCode checkDigits chars =
charsToInteger chars * 1000000 + charsToInteger countryCode * 100 + charsToInteger checkDigits
valid countryCode checkDigits chars =
ibanToInteger countryCode checkDigits chars `mod` 97 == 1
|