@@ -1,7 +1,11 @@
1{-# LANGUAGE DataKinds #-}
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
3{-# LANGUAGE UndecidableInstances #-}
4{-# LANGUAGE UndecidableInstances #-}
4module Web.Connexpay.Payments ( CreditCard(..)
6module Web.Connexpay.Payments ( CreditCard(..)
5 , AuthResponse(..)
9 , AuthResponse(..)
6 , authorisePayment
10 , authorisePayment
7 , VoidRequest(..)
11 , VoidRequest(..)
@@ -18,6 +22,7 @@ import Control.Monad.Reader (asks)
18import Control.Monad.Writer.Strict
22import Control.Monad.Writer.Strict
19import Data.Aeson
23import Data.Aeson
20import Data.Aeson.KeyMap qualified as KeyMap
24import Data.Aeson.KeyMap qualified as KeyMap
21import Data.Aeson.Types (Pair, typeMismatch)
26import Data.Aeson.Types (Pair, typeMismatch)
22import Data.ByteString.Lazy qualified as ByteString
27import Data.ByteString.Lazy qualified as ByteString
23import Data.Int (Int32)
28import Data.Int (Int32)
@@ -33,6 +38,17 @@ import Web.Connexpay.Data
33import Web.Connexpay.Types
38import Web.Connexpay.Types
34import Web.Connexpay.Utils
39import Web.Connexpay.Utils
42+data Customer = Customer { address1 :: Text
43+ , address2 :: Maybe Text
47+deriveToJSON defaultOptions
48+ { fieldLabelModifier = capitalize
49+ , omitNothingFields = True
36-- | Credit card info
52-- | Credit card info
37-- No 'Show' instance should be made for this type
53-- No 'Show' instance should be made for this type
38-- in order to avoid sensitive data leaks.
54-- in order to avoid sensitive data leaks.
@@ -40,8 +56,8 @@ data CreditCard = CreditCard { number :: Text -- ^ Credit card numbe
40 , cardholder :: Maybe Text -- ^ Cardholder name, optional.
56 , cardholder :: Maybe Text -- ^ Cardholder name, optional.
41 , expiration :: (Word, Word) -- ^ Expiration date (month,year).
57 , expiration :: (Word, Word) -- ^ Expiration date (month,year).
42 , cvv :: Maybe Text -- ^ CVC/CVV code.
58 , cvv :: Maybe Text -- ^ CVC/CVV code.
59+ , customer :: Maybe Customer -- ^ Required to get AVS
45type ShowError = TypeError.Text "CreditCard must not be shown in order to avoid leaking sensitive data"
61type ShowError = TypeError.Text "CreditCard must not be shown in order to avoid leaking sensitive data"
47instance TypeError ShowError => Show CreditCard where
63instance TypeError ShowError => Show CreditCard where
@@ -57,6 +73,8 @@ instance ToJSON CreditCard where
57 tell ["Cvv2" .= cvv]
73 tell ["Cvv2" .= cvv]
58 let expDate = padDate (tshow (snd cc.expiration)) <> padDate (tshow (fst cc.expiration))
74 let expDate = padDate (tshow (snd cc.expiration)) <> padDate (tshow (fst cc.expiration))
59 tell ["ExpirationDate" .= expDate]
75 tell ["ExpirationDate" .= expDate]
76+ whenJust cc.customer $ \customer ->
77+ tell [ "Customer" .= customer ]
61padDate :: Text -> Text
79padDate :: Text -> Text
62padDate t | Text.length t == 1 = "0" <> t
80padDate t | Text.length t == 1 = "0" <> t
@@ -125,13 +143,18 @@ instance FromJSON AuthResponse where
125 <*> o .:? "cvvVerificationCode"
143 <*> o .:? "cvvVerificationCode"
126 parseJSON v = typeMismatch "AuthReponse" v
144 parseJSON v = typeMismatch "AuthReponse" v
146+data AuthRequest = AuthRequest { creditCard :: CreditCard
147+ , amount :: Money USD
148+ , invoice :: Maybe Text
149+ , vendor :: Maybe Text
150+ -- ^ Merchant description that will appear in a
151+ -- customer's statement.
128-- | Authorise a credit card payment.
154-- | Authorise a credit card payment.
129−authorisePayment :: CreditCard -- ^ Credit card details (see 'CreditCard')
155+authorisePayment :: AuthRequest
130− -> Money USD -- ^ Amount to charge, USD
131− -> Maybe Text -- ^ Invoice description
132− -> Maybe Text -- ^ Merchant description that will appear in a customer's statement.
133 -> ConnexpayM AuthResponse
156 -> ConnexpayM AuthResponse
134−authorisePayment cc amt invoice vendor =
157+authorisePayment request =
135 do resp <- sendRequestJson "authonlys" body
158 do resp <- sendRequestJson "authonlys" body
136 let rbody = responseBody resp
159 let rbody = responseBody resp
137 -- Special case for Connexpay local transaction
160 -- Special case for Connexpay local transaction
@@ -144,11 +167,11 @@ authorisePayment cc amt invoice vendor =
144 throwError (PaymentFailure LocalTransaction Nothing)
167 throwError (PaymentFailure LocalTransaction Nothing)
145 pure rbody
168 pure rbody
146 where body = execWriter $
169 where body = execWriter $
147− do tell [ "Card" .= cc ]
170+ do tell [ "Card" .= request.creditCard ]
148− tell [ "Amount" .= getAmount amt ]
171+ tell [ "Amount" .= getAmount request.amount ]
149− whenJust invoice $ \i ->
172+ whenJust request.invoice $ \i ->
150 tell [ "OrderNumber" .= i ]
173 tell [ "OrderNumber" .= i ]
151− whenJust vendor $ \v ->
174+ whenJust request.vendor $ \v ->
152 tell [ "StatementDescription" .= v ]
175 tell [ "StatementDescription" .= v ]
153 -- We are supposed to pass RiskData, but it still
176 -- We are supposed to pass RiskData, but it still
154 -- can be an empty object. Consider population this
177 -- can be an empty object. Consider population this