mirror of
https://github.com/facebook/duckling.git
synced 2024-12-01 08:19:36 +03:00
bf89e34365
Reviewed By: JoelMarcey Differential Revision: D15439223 fbshipit-source-id: c5af3cb06318748142fe503945b38beffadfc28a
124 lines
4.0 KiB
Haskell
124 lines
4.0 KiB
Haskell
-- Copyright (c) 2016-present, Facebook, Inc.
|
|
-- All rights reserved.
|
|
--
|
|
-- This source code is licensed under the BSD-style license found in the
|
|
-- LICENSE file in the root directory of this source tree.
|
|
|
|
|
|
module Duckling.CreditCardNumber.Helpers
|
|
( otherCreditCardNumberRegex
|
|
, visaCreditCardNumberRegex
|
|
, amexCreditCardNumberRegex
|
|
, discoverCreditCardNumberRegex
|
|
, mastercardCreditCardNumberRegex
|
|
, dinerClubCreditCardNumberRegex
|
|
, isValidCreditCardNumber
|
|
, minNumberDigits
|
|
, maxNumberDigits
|
|
, creditCard
|
|
) where
|
|
|
|
import Data.Text (Text)
|
|
import Prelude
|
|
import Data.String
|
|
|
|
import Duckling.CreditCardNumber.Types (CreditCardNumberData(..))
|
|
import qualified Duckling.CreditCardNumber.Types as TCreditCardNumber
|
|
import qualified Data.Text as T
|
|
import qualified Data.Char as C
|
|
import qualified Data.Bits as B
|
|
|
|
-- -----------------------------------------------------------------
|
|
-- Patterns
|
|
|
|
otherCreditCardNumberRegex :: String
|
|
otherCreditCardNumberRegex =
|
|
concat [ "("
|
|
, "(?!" , visaCreditCardNumberRegex, ")"
|
|
, "(?!" , amexCreditCardNumberRegex, ")"
|
|
, "(?!" , discoverCreditCardNumberRegex, ")"
|
|
, "(?!" , mastercardCreditCardNumberRegex, ")"
|
|
, "(?!" , dinerClubCreditCardNumberRegex, ")"
|
|
, "\\d{" , show minNumberDigits , "," , show maxNumberDigits , "}"
|
|
, ")"
|
|
]
|
|
|
|
-- | Visa credit card regex informed by latest BIN info
|
|
visaCreditCardNumberRegex :: String
|
|
visaCreditCardNumberRegex = "(" ++ withoutDashes ++ "|" ++ withDashes ++")"
|
|
where
|
|
withoutDashes = "4[0-9]{15}"
|
|
withDashes = "4[0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9]{4}"
|
|
|
|
-- | American Express credit card regex informed by latest BIN info
|
|
amexCreditCardNumberRegex :: String
|
|
amexCreditCardNumberRegex = "(" ++ withoutDashes ++ "|" ++ withDashes ++")"
|
|
where
|
|
withoutDashes = "3[47][0-9]{13}"
|
|
withDashes = "3[47][0-9]{2}-[0-9]{6}-[0-9]{5}"
|
|
|
|
-- | Discover credit card regex informed by latest BIN info
|
|
discoverCreditCardNumberRegex :: String
|
|
discoverCreditCardNumberRegex = "(" ++ withoutDashes ++ "|" ++ withDashes ++")"
|
|
where
|
|
withoutDashes = "6(?:011|[45][0-9]{2})[0-9]{12}"
|
|
withDashes = "6(?:011|[45][0-9]{2})-[0-9]{4}-[0-9]{4}-[0-9]{4}"
|
|
|
|
-- | Mastercard credit card regex informed by latest BIN info
|
|
mastercardCreditCardNumberRegex :: String
|
|
mastercardCreditCardNumberRegex =
|
|
"(" ++ withoutDashes ++ "|" ++ withDashes ++")"
|
|
where
|
|
withoutDashes = "5[1-5][0-9]{14}"
|
|
withDashes = "5[1-5][0-9]{2}-[0-9]{4}-[0-9]{4}-[0-9]{4}"
|
|
|
|
-- | Diner Club credit card regex informed by latest BIN info
|
|
dinerClubCreditCardNumberRegex :: String
|
|
dinerClubCreditCardNumberRegex = "(" ++ withoutDashes ++ "|" ++ withDashes ++")"
|
|
where
|
|
withoutDashes = "3(?:0[0-5]|[68][0-9])[0-9]{11}"
|
|
withDashes = "3(?:0[0-5]|[68][0-9])[0-9]-[0-9]{6}-[0-9]{4}"
|
|
|
|
-- -----------------------------------------------------------------
|
|
-- Validation
|
|
|
|
-- | An implementation of the Luhn algorithm (see
|
|
-- https://en.wikipedia.org/wiki/Luhn_algorithm) to check if a given credit card
|
|
-- number is valid
|
|
isValidCreditCardNumber :: Text -> Bool
|
|
isValidCreditCardNumber ccNum =
|
|
T.length ccNum >= minNumberDigits &&
|
|
T.length ccNum <= maxNumberDigits &&
|
|
validCheckSum
|
|
where
|
|
validCheckSum :: Bool
|
|
validCheckSum =
|
|
T.all C.isDigit ccNum &&
|
|
fst (T.foldr f (0, 0) ccNum) `rem` 10 == 0
|
|
where
|
|
f char (checksum, e) =
|
|
let
|
|
val = C.digitToInt char
|
|
-- every even digit should be doubled
|
|
d = sumDigits (B.shift val e)
|
|
in (checksum + d, 1 - e)
|
|
-- we only need sum of digits for numbers from 0 to 18
|
|
sumDigits a
|
|
| a > 9 = a - 9
|
|
| otherwise = a
|
|
|
|
minNumberDigits :: Int
|
|
minNumberDigits = 8
|
|
|
|
maxNumberDigits :: Int
|
|
maxNumberDigits = 19
|
|
|
|
-- -----------------------------------------------------------------
|
|
-- Production
|
|
|
|
creditCard :: Text -> TCreditCardNumber.Issuer -> CreditCardNumberData
|
|
creditCard ccNum i =
|
|
CreditCardNumberData { TCreditCardNumber.number = ccNum
|
|
, TCreditCardNumber.issuer = i
|
|
}
|