blob: d9566b953273ce1e841096d725879f8da85d5797 (
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
41
42
43
44
45
46
47
48
|
module Data.Iban (Iban, mkIban, toText) where
import Control.Applicative ((<|>))
import Data.Attoparsec.Text as AP
import Data.Char
( digitToInt,
isAscii,
isDigit,
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 = AP.parseOnly $ do
countryCode <- AP.count 2 AP.letter
checkDigits <- AP.count 2 AP.digit
chars <- AP.many1 (AP.letter <|> AP.digit)
endOfInput
if length chars < 30
then
if valid countryCode checkDigits chars
then return ()
else fail $ "IBAN checksum does not match (" ++ countryCode ++ checkDigits ++ chars ++ ")"
else fail "IBAN has more than 34 characters"
where
letterToInt c = ord (toUpper c) - ord 'A' + 10
charsToInteger =
foldl'
( \acc -> \case
d
| isDigit d -> acc * 10 + toInteger (digitToInt d)
| isAscii d -> acc * 100 + toInteger (letterToInt d)
| otherwise -> error "unreachable"
)
0
ibanToInteger countryCode checkDigits chars =
charsToInteger chars * 1000000 + charsToInteger countryCode * 100 + charsToInteger checkDigits
valid countryCode checkDigits chars =
ibanToInteger countryCode checkDigits chars `mod` 97 == 1
toText :: Iban -> T.Text
toText (Iban t) = t
|