@@ -2,6 +2,7 @@
2{-# LANGUAGE DerivingStrategies #-}
2{-# LANGUAGE DerivingStrategies #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE StandaloneDeriving #-}
4{-# LANGUAGE StandaloneDeriving #-}
5+{-# LANGUAGE DerivingVia #-}
5{-# LANGUAGE TemplateHaskell #-}
6{-# LANGUAGE TemplateHaskell #-}
7module Data.CreditCard
8module Data.CreditCard
@@ -32,6 +33,7 @@ module Data.CreditCard
32 , _DiscoverCard
33 , _DiscoverCard
33 , _DinersClub
34 , _DinersClub
35 , CreditCardError(..)
37 , CreditCardError(..)
36 , _UnknownCardNumber
38 , _UnknownCardNumber
37 , _InvalidCardNumber
39 , _InvalidCardNumber
@@ -62,7 +64,6 @@ import Data.Ix
62import Data.List as L
64import Data.List as L
63import Data.List.NonEmpty (NonEmpty)
65import Data.List.NonEmpty (NonEmpty)
64import qualified Data.List.NonEmpty as NE
66import qualified Data.List.NonEmpty as NE
65−import Data.Maybe (fromMaybe)
66import Data.OpenApi as OpenApi
67import Data.OpenApi as OpenApi
67 ( ToSchema(..), SchemaOptions(..), defaultSchemaOptions
68 ( ToSchema(..), SchemaOptions(..), defaultSchemaOptions
68 , genericDeclareNamedSchema)
69 , genericDeclareNamedSchema)
@@ -228,12 +229,6 @@ panToBogusNumber (CreditCardPAN txt) = do
228 let middle = NE.fromList $ replicate (T.length txt - 8) D0
229 let middle = NE.fromList $ replicate (T.length txt - 8) D0
229 return $ CreditCardNumber $ sconcat $ NE.fromList [leading, middle, trailing]
230 return $ CreditCardNumber $ sconcat $ NE.fromList [leading, middle, trailing]
231−#ifndef ghcjs_HOST_OS
233−deriving instance FromField CreditCardPAN
234−deriving instance ToField CreditCardPAN
238mkCreditCardPan :: CreditCardNumber -> CreditCardPAN
233mkCreditCardPan :: CreditCardNumber -> CreditCardPAN
239mkCreditCardPan n = CreditCardPAN $ firstDigits <> stars <> lastDigits
234mkCreditCardPan n = CreditCardPAN $ firstDigits <> stars <> lastDigits
@@ -316,10 +311,25 @@ data CreditCardType
316 | DiscoverCard
311 | DiscoverCard
317 | DinersClub
312 | DinersClub
319− deriving (Eq, Show)
315+ deriving (Eq, Show, Generic, Enum, Bounded)
321makePrisms ''CreditCardType
317makePrisms ''CreditCardType
319+instance Arbitrary CreditCardType where
320+ arbitrary = elements [minBound..maxBound]
322+instance ToSchema CreditCardType where
323+ declareNamedSchema p = genericDeclareNamedSchema defaultSchemaOptions p
325+instance FromJSON CreditCardType where
327+ genericParseJSON defaultOptions
329+instance ToJSON CreditCardType where
331+ genericToJSON defaultOptions
323data CreditCardError
333data CreditCardError
324 = UnknownCardNumber
334 = UnknownCardNumber
325 | InvalidCardNumber
335 | InvalidCardNumber
@@ -353,11 +363,13 @@ guessCreditCardType n
353 | isDinersClub = Just DinersClub
363 | isDinersClub = Just DinersClub
354 | isJCB = Just JCB
364 | isJCB = Just JCB
355 | isVisa = Just Visa -- NOTE: starts with just 4? realy?
365 | isVisa = Just Visa -- NOTE: starts with just 4? realy?
366+ | isUnionPay = Just UnionPay
356 | otherwise = Nothing
367 | otherwise = Nothing
358 isVisa = (4 == firstDigits 1) && (digitsLength `elem` [13,16,19])
369 isVisa = (4 == firstDigits 1) && (digitsLength `elem` [13,16,19])
359 isMasterCard = (inRange (51, 55) (firstDigits 2)) && (digitsLength == 16)
370 isMasterCard = (inRange (51, 55) (firstDigits 2)) && (digitsLength == 16)
360 isAmEx = (firstDigits 2 `elem` [34, 37]) && (digitsLength == 15)
371 isAmEx = (firstDigits 2 `elem` [34, 37]) && (digitsLength == 15)
372+ isUnionPay = (firstDigits 2 `elem` [62]) && (digitsLength `elem` [16,17,18,19])
361 isDiscoverCard =
373 isDiscoverCard =
362 (firstDigits 4 == 6011 || inRange (644, 649) (firstDigits 3)
374 (firstDigits 4 == 6011 || inRange (644, 649) (firstDigits 3)
363 || inRange (622126, 622925) (firstDigits 6) || firstDigits 2 == 65)
375 || inRange (622126, 622925) (firstDigits 6) || firstDigits 2 == 65)
@@ -387,3 +399,32 @@ isValidCCNumber = isRight . creditCardType
388guessPANCardType :: CreditCardPAN -> Maybe CreditCardType
400guessPANCardType :: CreditCardPAN -> Maybe CreditCardType
389guessPANCardType = guessCreditCardType <=< panToBogusNumber
401guessPANCardType = guessCreditCardType <=< panToBogusNumber
403+#ifndef ghcjs_HOST_OS
405+deriving instance FromField CreditCardPAN
406+deriving instance ToField CreditCardPAN
407+instance ToField CreditCardType where
408+ toField cardType = toField $ case cardType of
410+ MasterCard -> "MasterCard"
411+ AmericanExpress-> "AmericanExpress"
412+ DiscoverCard -> "DiscoverCard"
413+ DinersClub -> "DinersClub"
415+ UnionPay -> "UnionPay" :: Text
417+instance FromField CreditCardType where
418+ fromField f mbs = do
419+ txt <- fromField f mbs
420+ case T.unpack txt of
421+ "Visa" -> return Visa
422+ "MasterCard" -> return MasterCard
423+ "AmericanExpress"-> return AmericanExpress
424+ "DiscoverCard" -> return DiscoverCard
425+ "DinersClub" -> return DinersClub
427+ "UnionPay" -> return UnionPay
428+ _ -> returnError ConversionFailed f ("Invalid credit card type: " ++ T.unpack txt)