packages feed

mangopay-1.0: src/Web/MangoPay/Cards.hs

{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, OverloadedStrings, FlexibleContexts, FlexibleInstances, PatternGuards #-}
-- | handle cards
module Web.MangoPay.Cards where

import Web.MangoPay.Documents
import Web.MangoPay.Monad
import Web.MangoPay.Types
import Web.MangoPay.Users

import Data.Conduit
import Data.Text
import Data.Typeable (Typeable)
import Data.Aeson
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
import Control.Applicative
import qualified Network.HTTP.Types as HT

import qualified Network.HTTP.Conduit as H
import Control.Monad.IO.Class (liftIO)
import qualified Data.Conduit.List as EL (consume)
import qualified Data.Text.Encoding as TE
import qualified Data.ByteString as BS

import qualified Data.HashMap.Lazy as HM
import Control.Exception.Base (throw)

-- | card registration ID
type CardRegistrationID=Text

-- | perform the full registration of a card
fullRegistration :: (MonadBaseControl IO m, MonadResource m) => AnyUserID -> Currency -> CardInfo -> AccessToken -> MangoPayT m CardRegistration
fullRegistration uid currency cardInfo at=do
  -- create registration
  let cr1=mkCardRegistration uid currency
  cr2<-storeCardRegistration cr1 at
  -- register it
  cr3<-registerCard cardInfo cr2
  -- save registered version
  storeCardRegistration cr3 at
  

-- | create or edit a card registration
storeCardRegistration ::  (MonadBaseControl IO m, MonadResource m) => CardRegistration -> AccessToken -> MangoPayT m CardRegistration
storeCardRegistration cr at= 
        case crId cr of
                Nothing-> do
                        url<-getClientURL "/cardregistrations"
                        postExchange url (Just at) cr
                Just i-> do
                        url<-getClientURLMultiple ["/cardregistrations/",i]
                        let Object m=toJSON cr
                        putExchange url (Just at) $ Object $ HM.filterWithKey (\k _->k=="RegistrationData") m

-- | credit card information
data CardInfo = CardInfo {
  ciNumber :: Text
  ,ciExpire :: Text
  ,ciCSC :: Text
  } deriving (Show,Read,Eq,Ord,Typeable)

-- | register a card with the registration URL
registerCard :: (MonadBaseControl IO m, MonadResource m) => CardInfo -> CardRegistration -> MangoPayT m CardRegistration
registerCard ci cr |
  Just url <- crCardRegistrationURL cr,
  Just pre <- crPreregistrationData cr,
  Just ak <- crAccessKey cr=do
    req <-liftIO $ H.parseUrl $ unpack url  
    mgr<-getManager
    let b=HT.renderQuery False $ HT.toQuery [
            "accessKeyRef" ?+ ak
            ,"data" ?+ pre
            ,"cardNumber" ?+ ciNumber ci
            ,"cardExpirationDate" ?+ ciExpire ci
            ,"cardCvx" ?+ ciCSC ci]
    let req'=req {H.method=HT.methodPost
         , H.requestHeaders=[("content-type","application/x-www-form-urlencoded")]
         , H.requestBody=H.RequestBodyBS b}             
    res<- H.http req' mgr
    reg <- H.responseBody res $$+- EL.consume
    let t=TE.decodeUtf8 $ BS.concat reg
    if "data=" `isPrefixOf` t 
      then return cr{crRegistrationData=Just t}
      else do
        pt<-liftIO getPOSIXTime
        throw $ MpAppException $ MpError "" "RegistrationError" t $ Just pt            
registerCard _ _=do
  pt<-liftIO getPOSIXTime
  throw $ MpAppException $ MpError "" "IllegalState" "CardRegistration not ready" $ Just pt            
                
-- | helper function to create a new card registration
mkCardRegistration :: AnyUserID -> Currency -> CardRegistration
mkCardRegistration uid currency=CardRegistration Nothing Nothing Nothing uid currency Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing

-- | a card registration
data CardRegistration = CardRegistration {
  crId :: Maybe CardRegistrationID -- ^ The Id of the object
  ,crCreationDate  :: Maybe POSIXTime -- ^ The creation date of the object
  ,crTag :: Maybe Text -- ^  Custom data
  ,crUserId  :: AnyUserID -- ^  The ID of the author
  ,crCurrency  :: Currency -- ^ The currency of the card registrated
  ,crAccessKey :: Maybe Text -- ^ This key has to be sent with the card details and the PreregistrationData
  ,crPreregistrationData  :: Maybe Text -- ^  This passphrase has to be sent with the card details and the AccessKey
  ,crCardRegistrationURL  :: Maybe Text -- ^  The URL where to POST the card details, the AccessKey and PreregistrationData
  ,crRegistrationData   :: Maybe Text -- ^  You get the CardRegistrationData once you posted the card details, the AccessKey and PreregistrationData
  ,crCardType   :: Maybe Text -- ^  « CB_VISA_MASTERCARD » is the only value available yet
  ,crCardId   :: Maybe CardID -- ^  You get the CardId (to process payments) once you edited the CardRegistration Object with the RegistrationData
  ,crResultCode   :: Maybe Text -- ^  The result code of the object
  ,crResultMessage  :: Maybe Text -- ^  The message explaining the result code
  ,crStatus  :: Maybe DocumentStatus -- ^ The status of the object.
} deriving (Show,Eq,Ord,Typeable)


-- | to json as per MangoPay format        
instance ToJSON CardRegistration where
        toJSON cr=object ["Tag" .= crTag cr,"UserId" .= crUserId cr
          ,"Currency" .= crCurrency cr,"RegistrationData" .= crRegistrationData cr
          ,"CardRegistrationURL" .= crCardRegistrationURL cr]

-- | from json as per MangoPay format 
instance FromJSON CardRegistration where
        parseJSON (Object v) =CardRegistration <$>
                         v .: "Id" <*>
                         v .: "CreationDate" <*>
                         v .:? "Tag" <*>
                         v .: "UserId" <*>
                         v .: "Currency" <*>
                         v .:? "AccessKey"  <*>
                         v .:? "PreregistrationData"  <*>
                         v .:? "CardRegistrationURL"  <*>
                         v .:? "RegistrationData"  <*>
                         v .:? "CardType"  <*>
                         v .:? "CardId"  <*>
                         v .:? "ResultCode"  <*>
                         v .:? "ResultMessage"  <*>
                         v .:? "Status"  
        parseJSON _=fail "CardRegistration"  

-- | fetch a card from its ID
fetchCard :: (MonadBaseControl IO m, MonadResource m) => CardID -> AccessToken -> MangoPayT m Card
fetchCard cid at=do
        url<-getClientURLMultiple ["/cards/",cid]
        req<-getGetRequest url (Just at) ([]::HT.Query)
        getJSONResponse req 

-- | list all cards for a given user   
listCards :: (MonadBaseControl IO m, MonadResource m) => AnyUserID -> Maybe Pagination -> AccessToken -> MangoPayT m (PagedList Card)
listCards uid mp at=do
        url<-getClientURLMultiple ["/users/",uid,"/cards"]
        req<-getGetRequest url (Just at) (paginationAttributes mp)
        getJSONList req 

-- | validity of a card
data CardValidity=UNKNOWN | VALID | INVALID
  deriving (Show,Read,Eq,Ord,Bounded,Enum,Typeable)


-- | to json as per MangoPay format
instance ToJSON CardValidity where
        toJSON =toJSON . show

-- | from json as per MangoPay format
instance FromJSON CardValidity where
        parseJSON (String s)=pure $ read $ unpack s
        parseJSON _ =fail "CardValidity"


-- | a registered card
data Card=Card {
  cId :: CardID
  ,cCreationDate :: POSIXTime 
  ,cTag :: Maybe Text  
  ,cExpirationDate   :: Text -- ^  MMYY
  ,cAlias :: Text -- ^ Example: 497010XXXXXX4414
  ,cCardProvider  :: Text -- ^ The card provider, it could be « CB », « VISA », « MASTERCARD », etc.
  ,cCardType :: Text -- ^ « CB_VISA_MASTERCARD » is the only value available yet
  ,cProduct :: Maybe Text  
  ,cBankCode  :: Maybe Text  
  ,cActive :: Bool
  ,cCurrency :: Currency
  ,cValidity :: CardValidity -- ^ Once we proceed (or attempted to process) a payment with the card we are able to indicate if it is « valid » or « invalid ». If we didn’t process a payment yet the « Validity » stay at « unknown ».
  ,cCountry :: Text
  ,cUserId :: AnyUserID
  } deriving (Show,Eq,Ord,Typeable)
  
-- | from json as per MangoPay format 
instance FromJSON Card where
        parseJSON (Object v) =Card <$>
                         v .: "Id" <*>
                         v .: "CreationDate" <*>
                         v .:? "Tag" <*>
                         v .: "ExpirationDate" <*>
                         v .: "Alias" <*>
                         v .: "CardProvider"  <*>
                         v .: "CardType"  <*>
                         v .:? "Product"  <*>
                         v .:? "BankCode"  <*>
                         v .: "Active"  <*>
                         v .: "Currency"  <*>
                         v .: "Validity" <*>
                         v .: "Country" <*>
                         v .: "UserId" 
        parseJSON _=fail "Card"