module Network.Payments.PayPal
( UseHttpMethod(..)
, PayPalOperations(..)
, JSONText
, ErrorMessage
, PayPalError(..)
, execPayPal
, execPayPalOpers
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Exception
import Control.Lens
import Data.Aeson
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy as LBS
import Data.Time.Clock
import qualified Network.HTTP.Client as HTTPClient
import Network.Payments.PayPal.Auth
import Network.Payments.PayPal.Environment
import Network.Wreq
data UseHttpMethod = UseHttpGet | UseHttpPost Payload | UseHttpPatch Payload
instance Show UseHttpMethod where
show UseHttpGet = "HttpGet"
show (UseHttpPost _) = "HttpPost"
show (UseHttpPatch _) = "HttpPatch"
data PayPalOperations :: * -> * where
PPOPure :: a -> PayPalOperations a
PPOBind :: PayPalOperations a -> (a -> PayPalOperations b) ->
PayPalOperations b
PayPalOperation :: FromJSON a =>
{ ppoMethod :: UseHttpMethod
, ppoUrl :: String
, ppoOptions :: Options
} -> PayPalOperations a
instance Functor PayPalOperations where
fmap f m = PPOBind m (PPOPure . f)
instance Applicative PayPalOperations where
pure x = PPOPure x
mf <*> mx = PPOBind mf (\f -> PPOBind mx (\x -> PPOPure (f x)))
instance Monad PayPalOperations where
m >>= f = PPOBind m f
type JSONText = LBS.ByteString
type ErrorMessage = String
data PayPalError = HttpStatusError Int |
ResponseParseError ErrorMessage JSONText |
HttpError HTTPClient.HttpException | OtherError String
deriving (Show)
execPayPal :: FromJSON a => EnvironmentUrl -> ClientID -> Secret ->
PayPalOperations a -> IO (Either PayPalError a)
execPayPal envUrl username password operations = do
accessTokenOrErr <- fetchAccessTokenWithExpiration envUrl username password
case accessTokenOrErr of
Left (AccessTokenHttpError httpErr) -> return $ Left $ HttpError httpErr
Left (AccessTokenStatusError statusCode') ->
return $ Left $ HttpStatusError statusCode'
Left (AccessTokenParseError errMsg text) ->
return $ Left $ ResponseParseError errMsg text
Right accTokenWithEx -> do
result <- execPayPalOpers envUrl username password accTokenWithEx
operations
case result of
Left err -> return $ Left err
Right (result', _) -> return $ Right result'
execPayPalOpers :: EnvironmentUrl -> ClientID -> Secret ->
AccessTokenWithExpiration -> PayPalOperations a ->
IO (Either PayPalError (a, AccessTokenWithExpiration))
execPayPalOpers _ _ _ accTokenWithEx (PPOPure a) =
return $ Right (a, accTokenWithEx)
execPayPalOpers envUrl' username password accTokenWithEx (PPOBind m f) = do
treeLeftResult <- execPayPalOpers envUrl' username password accTokenWithEx m
either (return . Left)
(\(res, newAccTk) -> execPayPalOpers envUrl' username password
newAccTk $ f res)
treeLeftResult
execPayPalOpers env@(EnvironmentUrl baseUrl) username password
accTokenWithEx@(accessToken, expiration)
(PayPalOperation method url preOptions) = do
curTime <- getCurrentTime
latestAccTkOrErr <- if diffUTCTime expiration curTime <= 0
then fetchAccessTokenWithExpiration env username password
else return $ Right accTokenWithEx
case latestAccTkOrErr of
Left (AccessTokenHttpError httpErr) -> return $ Left $ HttpError httpErr
Left (AccessTokenStatusError statusCode') ->
return $ Left $ HttpStatusError statusCode'
Left (AccessTokenParseError errMsg text) ->
return $ Left $ ResponseParseError errMsg text
Right latestAccTk -> do
let accToken = aToken accessToken
opts = preOptions &
header "Authorization" .~ [BS8.pack ("Bearer " ++ accToken)]
responseOrErr <- try $ case method of
UseHttpGet -> getWith opts (baseUrl ++ url)
UseHttpPost payload -> postWith opts (baseUrl ++ url) payload
UseHttpPatch payload ->
customPayloadMethodWith "PATCH" opts (baseUrl ++ url) payload
case responseOrErr of
Left err -> return $ Left $ HttpError err
Right response -> do
let statusCode' = response ^. responseStatus . statusCode
if statusCode' == 200 then
let responseText = response ^. responseBody
in case eitherDecode responseText of
Left errMsg ->
return $ Left $ ResponseParseError errMsg responseText
Right result -> return $ Right (result, latestAccTk)
else
return $ Left $ HttpStatusError statusCode'