module Network.Payments.PayPal.Payments
( URL
, PaymentID
, Intent(..)
, RedirectUrls(..)
, ReturnLinkParams(..)
, CreateRequest(..)
, CreateResponse(..)
, ExecuteRequest(..)
, ExecuteResponse(..)
, FindResponse(..)
, ListResponse(..)
, createPayment
, approvalUrlFromCreate
, executePayment
, findPaymentById
, listPayments
, returnLinkParams
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Monad
import Data.Aeson
import Data.Aeson.Types
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.Map as M
import Data.Maybe
#if __GLASGOW_HASKELL__ < 710
import Data.Monoid
#endif
import Data.Time.Clock
import Data.Time.Format
import qualified Network.HTTP.Client as HTTP
import Network.Payments.PayPal
import Network.Payments.PayPal.Types.Hateoas
import Network.Payments.PayPal.Types.Paging
import Network.Payments.PayPal.Types.Payer
import Network.Payments.PayPal.Types.Transaction
import Network.Wreq
import qualified Network.Wreq.Types as WTypes
type URL = String
type PaymentID = String
data Intent = SaleIntent | AuthoriseIntent | OrderIntent
deriving (Eq, Read, Show)
instance ToJSON Intent where
toJSON SaleIntent = "sale"
toJSON AuthoriseIntent = "authorize"
toJSON OrderIntent = "order"
instance FromJSON Intent where
parseJSON (String "sale") = return SaleIntent
parseJSON (String "authorize") = return AuthoriseIntent
parseJSON (String "order") = return OrderIntent
parseJSON _ = mzero
data RedirectUrls = RedirectUrls
{ redirUrlReturn :: URL
, redirUrlCancel :: URL
} deriving (Eq, Show)
instance ToJSON RedirectUrls where
toJSON urls =
object ["return_url" .= redirUrlReturn urls,
"cancel_url" .= redirUrlCancel urls]
instance FromJSON RedirectUrls where
parseJSON (Object obj) =
RedirectUrls <$>
obj .: "return_url" <*>
obj .: "cancel_url"
parseJSON _ = mzero
data ReturnLinkParams = ReturnLinkParams
{ retLinkParamPayId :: PaymentID
, retLinkParamToken :: String
, retLinkParamPayerId :: String
} deriving (Eq, Show)
data PaymentState = PayStateCreated | PayStateApproved | PayStateFailed |
PayStateCancelled | PayStateExpired | PayStatePending
deriving (Eq, Read, Show)
instance FromJSON PaymentState where
parseJSON (String "created") = return PayStateCreated
parseJSON (String "approved") = return PayStateApproved
parseJSON (String "failed") = return PayStateFailed
parseJSON (String "canceled") = return PayStateCancelled
parseJSON (String "expired") = return PayStateExpired
parseJSON (String "pending") = return PayStatePending
parseJSON _ = mzero
data CreateRequest = CreateRequest
{ createReqIntent :: Intent
, createReqPayer :: Payer
, createReqTransactions :: [Transaction]
, createReqRedirectUrls :: Maybe RedirectUrls
} deriving (Eq, Show)
instance ToJSON CreateRequest where
toJSON req =
object (["intent" .= createReqIntent req,
"payer" .= createReqPayer req,
"transactions" .= createReqTransactions req] ++
maybeToList (("redirect_urls" .=) <$> createReqRedirectUrls req))
data CreateResponse = CreateResponse
{ createResIntent :: Intent
, createResPayer :: Payer
, createResTransactions :: [Transaction]
, createResRedirectUrls :: Maybe RedirectUrls
, createResPayId :: PaymentID
, createResCreateTime :: UTCTime
, createResPayState :: PaymentState
, createResUpdateTime :: Maybe UTCTime
, createResHateoasLinks :: [HateoasLink]
} deriving (Eq, Show)
instance FromJSON CreateResponse where
parseJSON (Object obj) =
CreateResponse <$>
obj .: "intent" <*>
obj .: "payer" <*>
obj .: "transactions" <*>
obj .:? "redirect_urls" <*>
obj .: "id" <*>
(obj .: "create_time" >>= parseTimeIso8106) <*>
obj .: "state" <*>
(obj .:? "update_time" >>=
maybe (return Nothing) (\str -> Just <$> parseTimeIso8106 str)) <*>
obj .: "links"
parseJSON _ = mzero
data ExecuteTransaction = ExecuteTransaction
{ executeTransactionAmount :: Amount
} deriving (Eq, Show)
instance ToJSON ExecuteTransaction where
toJSON trans = object ["amount" .= executeTransactionAmount trans]
data ExecuteRequest = ExecuteRequest
{ executeReqPayerId :: String
, executeReqTransactions :: [ExecuteTransaction]
} deriving (Eq, Show)
instance ToJSON ExecuteRequest where
toJSON req =
let transactions = if length (executeReqTransactions req) > 0
then ["transactions" .= executeReqTransactions req]
else []
in object (["payer_id" .= executeReqPayerId req] ++ transactions)
data ExecuteResponse = ExecuteResponse
{ executeResIntent :: Intent
, executeResPayer :: Payer
, executeResTransactions :: [Transaction]
, executeResHateoasLinks :: [HateoasLink]
} deriving (Eq, Show)
instance FromJSON ExecuteResponse where
parseJSON (Object obj) =
ExecuteResponse <$>
obj .: "intent" <*>
obj .: "payer" <*>
obj .: "transactions" <*>
obj .: "links"
parseJSON _ = mzero
data FindResponse = FindResponse
{ findResIntent :: Intent
, findResPayer :: Payer
, findResTransactions :: [Transaction]
, findResRedirectUrls :: Maybe RedirectUrls
, findResPayId :: PaymentID
, findResCreateTime :: UTCTime
, findResPayState :: PaymentState
, findResUpdateTime :: Maybe UTCTime
} deriving (Eq, Show)
instance FromJSON FindResponse where
parseJSON (Object obj) =
FindResponse <$>
obj .: "intent" <*>
obj .: "payer" <*>
obj .: "transactions" <*>
obj .:? "redirect_urls" <*>
obj .: "id" <*>
(obj .: "create_time" >>= parseTimeIso8106) <*>
obj .: "state" <*>
(obj .:? "update_time" >>= maybe (return Nothing)
(\str -> Just <$> parseTimeIso8106 str))
parseJSON _ = mzero
data ListResponse = ListResponse
{ listResPayments :: [CreateResponse]
, listResCount :: Integer
, listResNextId :: Maybe PaymentID
} deriving (Eq, Show)
instance FromJSON ListResponse where
parseJSON (Object obj) =
ListResponse <$>
(fromMaybe [] <$> obj .:? "payments") <*>
obj .: "count" <*>
obj .:? "next_id"
parseJSON _ = mzero
createPayment :: CreateRequest -> PayPalOperations CreateResponse
createPayment request =
let url = "/v1/payments/payment"
contentType = "application/json"
content = encode request
payload = WTypes.Raw contentType $ HTTP.RequestBodyLBS content
in PayPalOperation (UseHttpPost payload) url defaults
approvalUrlFromCreate :: CreateResponse -> Maybe URL
approvalUrlFromCreate response =
let criteriaFunction hateoas =
hateoasRel hateoas == "approval_url" &&
hateoasMethod hateoas == HateoasRedirect
maybeHateoas =
listToMaybe $ filter criteriaFunction $ createResHateoasLinks response
in hateoasHref <$> maybeHateoas
executePayment :: PaymentID -> ExecuteRequest ->
PayPalOperations ExecuteResponse
executePayment id' request =
let url = "/v1/payments/payment/" ++ id' ++ "/execute"
contentType = "application/json"
content = encode request
payload = WTypes.Raw contentType $ HTTP.RequestBodyLBS content
in PayPalOperation (UseHttpPost payload) url defaults
findPaymentById :: PaymentID -> PayPalOperations FindResponse
findPaymentById id' =
let url = "/v1/payments/payment/" ++ id'
in PayPalOperation UseHttpGet url defaults
listPayments :: Maybe PagingRequest -> PayPalOperations ListResponse
listPayments pagingRequest =
let url = "/v1/payments/payment/" ++
(maybe mempty (\req -> "?" ++ pagingReqToQuery req) pagingRequest)
in PayPalOperation UseHttpGet url defaults
returnLinkParams :: M.Map BS.ByteString BS.ByteString -> Maybe ReturnLinkParams
returnLinkParams query = do
paymentId <- BS8.unpack <$> M.lookup "paymentId" query
token <- BS8.unpack <$> M.lookup "token" query
payerId <- BS8.unpack <$> M.lookup "PayerID" query
return $ ReturnLinkParams paymentId token payerId
parseTimeIso8106 :: String -> Parser UTCTime
parseTimeIso8106 str =
parseTimeM True defaultTimeLocale (iso8601DateFormat $ Just "%H:%M:%SZ") str