blob: 45343ecc77add171d64b6ac0bb2d2769da582b42 (
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
|
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 = 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
| '0' <= d && d <= '9' -> acc * 10 + toInteger (digitToInt d)
| 'A' <= d && d <= 'Z'
|| 'a' <= d && d <= 'z' ->
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
|