@@ -1,7 +1,11 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
34{-# LANGUAGE UndecidableInstances #-}
5+
46module Web.Connexpay.Payments ( CreditCard(..)
7+ , AuthRequest(..)
8+ , Customer(..)
59 , AuthResponse(..)
610 , authorisePayment
711 , VoidRequest(..)
@@ -18,6 +22,7 @@ import Control.Monad.Reader (asks)
1822import Control.Monad.Writer.Strict
1923import Data.Aeson
2024import Data.Aeson.KeyMap qualified as KeyMap
25+import Data.Aeson.TH
2126import Data.Aeson.Types (Pair, typeMismatch)
2227import Data.ByteString.Lazy qualified as ByteString
2328import Data.Int (Int32)
@@ -33,6 +38,17 @@ import Web.Connexpay.Data
3338import Web.Connexpay.Types
3439import Web.Connexpay.Utils
3540
41+
42+data Customer = Customer { address1 :: Text
43+ , address2 :: Maybe Text
44+ , zip :: Maybe Text
45+ }
46+
47+deriveToJSON defaultOptions
48+ { fieldLabelModifier = capitalize
49+ , omitNothingFields = True
50+ } ''Customer
51+
3652-- | Credit card info
3753-- No 'Show' instance should be made for this type
3854-- in order to avoid sensitive data leaks.
@@ -40,8 +56,8 @@ data CreditCard = CreditCard { number :: Text -- ^ Credit card numbe
4056 , cardholder :: Maybe Text -- ^ Cardholder name, optional.
4157 , expiration :: (Word, Word) -- ^ Expiration date (month,year).
4258 , cvv :: Maybe Text -- ^ CVC/CVV code.
59+ , customer :: Maybe Customer -- ^ Required to get AVS
4360 }
44−
4561type ShowError = TypeError.Text "CreditCard must not be shown in order to avoid leaking sensitive data"
4662
4763instance TypeError ShowError => Show CreditCard where
@@ -57,6 +73,8 @@ instance ToJSON CreditCard where
5773 tell ["Cvv2" .= cvv]
5874 let expDate = padDate (tshow (snd cc.expiration)) <> padDate (tshow (fst cc.expiration))
5975 tell ["ExpirationDate" .= expDate]
76+ whenJust cc.customer $ \customer ->
77+ tell [ "Customer" .= customer ]
6078
6179padDate :: Text -> Text
6280padDate t | Text.length t == 1 = "0" <> t
@@ -125,13 +143,18 @@ instance FromJSON AuthResponse where
125143 <*> o .:? "cvvVerificationCode"
126144 parseJSON v = typeMismatch "AuthReponse" v
127145
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.
152+ }
153+
128154-- | Authorise a credit card payment.
129−authorisePayment :: CreditCard -- ^ Credit card details (see 'CreditCard')
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.
155+authorisePayment :: AuthRequest
133156 -> ConnexpayM AuthResponse
134−authorisePayment cc amt invoice vendor =
157+authorisePayment request =
135158 do resp <- sendRequestJson "authonlys" body
136159 let rbody = responseBody resp
137160 -- Special case for Connexpay local transaction
@@ -144,11 +167,11 @@ authorisePayment cc amt invoice vendor =
144167 throwError (PaymentFailure LocalTransaction Nothing)
145168 pure rbody
146169 where body = execWriter $
147− do tell [ "Card" .= cc ]
148− tell [ "Amount" .= getAmount amt ]
149− whenJust invoice $ \i ->
170+ do tell [ "Card" .= request.creditCard ]
171+ tell [ "Amount" .= getAmount request.amount ]
172+ whenJust request.invoice $ \i ->
150173 tell [ "OrderNumber" .= i ]
151− whenJust vendor $ \v ->
174+ whenJust request.vendor $ \v ->
152175 tell [ "StatementDescription" .= v ]
153176 -- We are supposed to pass RiskData, but it still
154177 -- can be an empty object. Consider population this