{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Web.Stripe.Client.HttpStreams
( stripe
, stripeConn
, withConnection
, StripeRequest (..)
, StripeError (..)
, StripeConfig (..)
, callAPI
) where
import Control.Exception (SomeException, finally, try)
import Control.Monad (when)
import Data.Aeson (Result(..), FromJSON, Value, fromJSON, json')
import qualified Data.ByteString as S
import Data.Monoid (mempty, (<>))
import qualified Data.Text.Encoding as T
import Network.Http.Client (Connection,
baselineContextSSL, buildRequest,
closeConnection,
getStatusCode, http,
inputStreamBody, openConnectionSSL,
receiveResponse, sendRequest,
setAuthorizationBasic, encodedFormBody,
setContentType, setHeader,
setTransferEncoding)
import qualified Network.Http.Client as C
import OpenSSL (withOpenSSL)
import qualified System.IO.Streams as Streams
import qualified System.IO.Streams.Attoparsec as Streams
import System.IO.Streams.Attoparsec (ParseException(..))
import Web.Stripe.Client (APIVersion (..), Method(..), StripeConfig (..),
StripeError (..),
StripeErrorType (..), StripeRequest (..),
StripeReturn, getStripeKey,
toBytestring, toText,
paramsToByteString, attemptDecode, unknownCode,
handleStream
)
stripe
:: (FromJSON (StripeReturn a)) =>
StripeConfig
-> StripeRequest a
-> IO (Either StripeError (StripeReturn a))
stripe config request =
withConnection $ \conn -> do
stripeConn conn config request
stripeConn
:: (FromJSON (StripeReturn a)) =>
Connection
-> StripeConfig
-> StripeRequest a
-> IO (Either StripeError (StripeReturn a))
stripeConn conn config request =
callAPI conn fromJSON config request
withConnection :: (Connection -> IO (Either StripeError a))
-> IO (Either StripeError a)
withConnection f =
withOpenSSL $ do
ctx <- baselineContextSSL
result <- try (openConnectionSSL ctx "api.stripe.com" 443) :: IO (Either SomeException Connection)
case result of
Left msg -> return $ Left $ StripeError ConnectionFailure (toText msg) Nothing Nothing Nothing
Right conn -> (f conn) `finally` (closeConnection conn)
debug :: Bool
debug = False
m2m :: Method -> C.Method
m2m GET = C.GET
m2m POST = C.POST
m2m DELETE = C.DELETE
callAPI
:: Connection
-> (Value -> Result b)
-> StripeConfig
-> StripeRequest a
-> IO (Either StripeError b)
callAPI conn fromJSON' StripeConfig {..} StripeRequest{..} = do
let reqBody | method == GET = mempty
| otherwise = queryParams
reqURL | method == GET = S.concat [
T.encodeUtf8 endpoint
, "?"
, paramsToByteString queryParams
]
| otherwise = T.encodeUtf8 endpoint
req <- buildRequest $ do
http (m2m method) $ "/v1/" <> reqURL
setAuthorizationBasic (getStripeKey secretKey) mempty
setContentType "application/x-www-form-urlencoded"
setHeader "Stripe-Version" (toBytestring V20141007)
setHeader "Connection" "Keep-Alive"
setTransferEncoding
sendRequest conn req (encodedFormBody reqBody)
receiveResponse conn $ \response inputStream ->
do when debug $ print response
let statusCode = getStatusCode response
if not (attemptDecode statusCode)
then return unknownCode
else do
v <- try (Streams.parseFromStream json' inputStream)
let r =
case v of
(Left (ParseException msg)) -> Error msg
(Right a) -> Success a
return $ handleStream fromJSON' statusCode r