paypal-adaptive-hoops 0.10.0.2 → 0.11.0.0
raw patch · 15 files changed
+734/−745 lines, 15 filesdep −data-defaultPVP ok
version bump matches the API change (PVP)
Dependencies removed: data-default
API changes (from Hackage documentation)
- Web.PayPal.Adaptive: AeConnectionErr :: Text -> AdaptiveErr
- Web.PayPal.Adaptive: AeDecodeFailed :: ByteString -> Text -> AdaptiveErr
- Web.PayPal.Adaptive: AeErrCodes :: [Int] -> AdaptiveErr
- Web.PayPal.Adaptive: AeInvalidCredentials :: AdaptiveErr
- Web.PayPal.Adaptive: AeNoSuchEmail :: AdaptiveErr
- Web.PayPal.Adaptive: AePending :: PayResp -> AdaptiveErr
- Web.PayPal.Adaptive: AeRefused :: PayResp -> AdaptiveErr
- Web.PayPal.Adaptive: AeShouldNotHappen :: PayResp -> Text -> AdaptiveErr
- Web.PayPal.Adaptive: Client :: Text -> Text -> Env -> Text -> Text -> Text -> Client
- Web.PayPal.Adaptive: Deposit :: Money -> Text -> Text -> Text -> Deposit
- Web.PayPal.Adaptive: LookupPayKey :: PayKey -> LookupPayment PayKey
- Web.PayPal.Adaptive: LookupTrID :: TransactionId -> LookupPayment TransactionId
- Web.PayPal.Adaptive: PayInfo :: Receiver -> Maybe TransactionId -> Maybe TransactionStatus -> Maybe TransactionId -> PayInfo
- Web.PayPal.Adaptive: PayKey :: Text -> PayKey
- Web.PayPal.Adaptive: PayResp :: Maybe Text -> PayExecStatus -> PayKey -> [PayInfo] -> PayResp
- Web.PayPal.Adaptive: PeCompleted :: PayExecStatus
- Web.PayPal.Adaptive: PeCreated :: PayExecStatus
- Web.PayPal.Adaptive: PeError :: PayExecStatus
- Web.PayPal.Adaptive: PeIncomplete :: PayExecStatus
- Web.PayPal.Adaptive: PePending :: PayExecStatus
- Web.PayPal.Adaptive: PeProcessing :: PayExecStatus
- Web.PayPal.Adaptive: PeReversalError :: PayExecStatus
- Web.PayPal.Adaptive: Production :: Env
- Web.PayPal.Adaptive: Receiver :: Text -> Text -> Text -> Receiver
- Web.PayPal.Adaptive: Sandbox :: Env
- Web.PayPal.Adaptive: TransactionId :: Text -> TransactionId
- Web.PayPal.Adaptive: TsCompleted :: TransactionStatus
- Web.PayPal.Adaptive: TsCreated :: TransactionStatus
- Web.PayPal.Adaptive: TsDenied :: TransactionStatus
- Web.PayPal.Adaptive: TsFailed :: TransactionStatus
- Web.PayPal.Adaptive: TsPartiallyRefunded :: TransactionStatus
- Web.PayPal.Adaptive: TsPending :: TransactionStatus
- Web.PayPal.Adaptive: TsProcessing :: TransactionStatus
- Web.PayPal.Adaptive: TsRefunded :: TransactionStatus
- Web.PayPal.Adaptive: TsReversed :: TransactionStatus
- Web.PayPal.Adaptive: USD :: Int -> Money
- Web.PayPal.Adaptive: Withdrawal :: Money -> Text -> Text -> Text -> Withdrawal
- Web.PayPal.Adaptive: _clAccountEmail :: Client -> Text
- Web.PayPal.Adaptive: _clAppId :: Client -> Text
- Web.PayPal.Adaptive: _clEnv :: Client -> Env
- Web.PayPal.Adaptive: _clPassword :: Client -> Text
- Web.PayPal.Adaptive: _clSig :: Client -> Text
- Web.PayPal.Adaptive: _clUserId :: Client -> Text
- Web.PayPal.Adaptive: _dpAmount :: Deposit -> Money
- Web.PayPal.Adaptive: _dpCancelUrl :: Deposit -> Text
- Web.PayPal.Adaptive: _dpReturnUrl :: Deposit -> Text
- Web.PayPal.Adaptive: _dpSenderEmail :: Deposit -> Text
- Web.PayPal.Adaptive: _piReceiver :: PayInfo -> Receiver
- Web.PayPal.Adaptive: _piSenderTransactionId :: PayInfo -> Maybe TransactionId
- Web.PayPal.Adaptive: _piTransactionId :: PayInfo -> Maybe TransactionId
- Web.PayPal.Adaptive: _piTransactionStatus :: PayInfo -> Maybe TransactionStatus
- Web.PayPal.Adaptive: _prPayError :: PayResp -> Maybe Text
- Web.PayPal.Adaptive: _prPayExecStatus :: PayResp -> PayExecStatus
- Web.PayPal.Adaptive: _prPayInfo :: PayResp -> [PayInfo]
- Web.PayPal.Adaptive: _prPayKey :: PayResp -> PayKey
- Web.PayPal.Adaptive: _reAccountId :: Receiver -> Text
- Web.PayPal.Adaptive: _reAmount :: Receiver -> Text
- Web.PayPal.Adaptive: _reEmail :: Receiver -> Text
- Web.PayPal.Adaptive: _unPayKey :: PayKey -> Text
- Web.PayPal.Adaptive: _unTransactionId :: TransactionId -> Text
- Web.PayPal.Adaptive: _usdCents :: Money -> Int
- Web.PayPal.Adaptive: _wdAmount :: Withdrawal -> Money
- Web.PayPal.Adaptive: _wdCancelUrl :: Withdrawal -> Text
- Web.PayPal.Adaptive: _wdReceiverEmail :: Withdrawal -> Text
- Web.PayPal.Adaptive: _wdReturnUrl :: Withdrawal -> Text
- Web.PayPal.Adaptive: approvalUrl :: Client -> PayKey -> Text
- Web.PayPal.Adaptive: class AdaptiveAPI a
- Web.PayPal.Adaptive: data AdaptiveErr
- Web.PayPal.Adaptive: data Client
- Web.PayPal.Adaptive: data Deposit
- Web.PayPal.Adaptive: data Env
- Web.PayPal.Adaptive: data LookupPayment a
- Web.PayPal.Adaptive: data Money
- Web.PayPal.Adaptive: data PayExecStatus
- Web.PayPal.Adaptive: data PayInfo
- Web.PayPal.Adaptive: data PayResp
- Web.PayPal.Adaptive: data Receiver
- Web.PayPal.Adaptive: data TransactionStatus
- Web.PayPal.Adaptive: data Withdrawal
- Web.PayPal.Adaptive: m2Currency :: Money -> Text
- Web.PayPal.Adaptive: m2PayPal :: Money -> String
- Web.PayPal.Adaptive: newtype PayKey
- Web.PayPal.Adaptive: newtype TransactionId
- Web.PayPal.Adaptive: toPayPal :: AdaptiveAPI a => Client -> a -> IO (Either AdaptiveErr PayResp)
- Web.PayPal.Adaptive.Internal: SerializeDeposit :: Deposit -> Text -> SerializeDeposit
- Web.PayPal.Adaptive.Internal: SerializeWithdrawal :: Withdrawal -> Text -> SerializeWithdrawal
- Web.PayPal.Adaptive.Internal: checkPayKey :: PayResp -> Either AdaptiveErr ()
- Web.PayPal.Adaptive.Internal: data SerializeDeposit
- Web.PayPal.Adaptive.Internal: data SerializeWithdrawal
- Web.PayPal.Adaptive.Internal: errorFieldNothing :: PayResp -> Either AdaptiveErr ()
- Web.PayPal.Adaptive.Internal: getPayInfo :: PayResp -> Either AdaptiveErr PayInfo
- Web.PayPal.Adaptive.Internal: noPayInfo :: PayResp -> Either AdaptiveErr ()
- Web.PayPal.Adaptive.Internal: parseErrId :: Value -> Parser Int
- Web.PayPal.Adaptive.Internal: payExecStatusCompleted :: PayResp -> Either AdaptiveErr ()
- Web.PayPal.Adaptive.Internal: payExecStatusCreated :: PayResp -> Either AdaptiveErr ()
- Web.PayPal.Adaptive.Internal: ppDecode :: FromJSON a => Response ByteString -> Either AdaptiveErr a
- Web.PayPal.Adaptive.Internal: ppPost :: ToJSON a => Client -> Text -> a -> IO (Either AdaptiveErr (Response ByteString))
- Web.PayPal.Adaptive.Internal: requestEnvelope :: Value
- Web.PayPal.Adaptive.Internal: transactionStatusCompleted :: PayResp -> PayInfo -> Either AdaptiveErr ()
+ PayPal.Adaptive: AeConnectionErr :: Text -> AdaptiveErr
+ PayPal.Adaptive: AeDecodeFailed :: ByteString -> Text -> AdaptiveErr
+ PayPal.Adaptive: AeErrCodes :: [Int] -> AdaptiveErr
+ PayPal.Adaptive: AeInvalidCredentials :: AdaptiveErr
+ PayPal.Adaptive: AeNoSuchEmail :: AdaptiveErr
+ PayPal.Adaptive: AePending :: PayResp -> AdaptiveErr
+ PayPal.Adaptive: AeRefused :: PayResp -> AdaptiveErr
+ PayPal.Adaptive: AeShouldNotHappen :: PayResp -> Text -> AdaptiveErr
+ PayPal.Adaptive: Client :: Text -> Text -> Env -> Text -> Text -> Text -> Client
+ PayPal.Adaptive: CompletePayResp :: PayKey -> Receiver -> TransactionId -> TransactionId -> CompletePayResp
+ PayPal.Adaptive: PayInfo :: Receiver -> Maybe TransactionId -> Maybe TransactionStatus -> Maybe TransactionId -> PayInfo
+ PayPal.Adaptive: PayKey :: Text -> PayKey
+ PayPal.Adaptive: PayResp :: Maybe Text -> PayExecStatus -> PayKey -> [PayInfo] -> PayResp
+ PayPal.Adaptive: PeCompleted :: PayExecStatus
+ PayPal.Adaptive: PeCreated :: PayExecStatus
+ PayPal.Adaptive: PeError :: PayExecStatus
+ PayPal.Adaptive: PeIncomplete :: PayExecStatus
+ PayPal.Adaptive: PePending :: PayExecStatus
+ PayPal.Adaptive: PeProcessing :: PayExecStatus
+ PayPal.Adaptive: PeReversalError :: PayExecStatus
+ PayPal.Adaptive: Production :: Env
+ PayPal.Adaptive: Receiver :: Text -> Text -> Text -> Receiver
+ PayPal.Adaptive: Sandbox :: Env
+ PayPal.Adaptive: TransactionId :: Text -> TransactionId
+ PayPal.Adaptive: TsCompleted :: TransactionStatus
+ PayPal.Adaptive: TsCreated :: TransactionStatus
+ PayPal.Adaptive: TsDenied :: TransactionStatus
+ PayPal.Adaptive: TsFailed :: TransactionStatus
+ PayPal.Adaptive: TsPartiallyRefunded :: TransactionStatus
+ PayPal.Adaptive: TsPending :: TransactionStatus
+ PayPal.Adaptive: TsProcessing :: TransactionStatus
+ PayPal.Adaptive: TsRefunded :: TransactionStatus
+ PayPal.Adaptive: TsReversed :: TransactionStatus
+ PayPal.Adaptive: USD :: Int -> Money
+ PayPal.Adaptive: _AeConnectionErr :: Prism' AdaptiveErr Text
+ PayPal.Adaptive: _AeDecodeFailed :: Prism' AdaptiveErr (ByteString, Text)
+ PayPal.Adaptive: _AeErrCodes :: Prism' AdaptiveErr [Int]
+ PayPal.Adaptive: _AeInvalidCredentials :: Prism' AdaptiveErr ()
+ PayPal.Adaptive: _AeNoSuchEmail :: Prism' AdaptiveErr ()
+ PayPal.Adaptive: _AePending :: Prism' AdaptiveErr PayResp
+ PayPal.Adaptive: _AeRefused :: Prism' AdaptiveErr PayResp
+ PayPal.Adaptive: _AeShouldNotHappen :: Prism' AdaptiveErr (PayResp, Text)
+ PayPal.Adaptive: _PeCompleted :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PeCreated :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PeError :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PeIncomplete :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PePending :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PeProcessing :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _PeReversalError :: Prism' PayExecStatus ()
+ PayPal.Adaptive: _Production :: Prism' Env ()
+ PayPal.Adaptive: _Sandbox :: Prism' Env ()
+ PayPal.Adaptive: _TransactionId :: Iso' TransactionId Text
+ PayPal.Adaptive: _TsCompleted :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsCreated :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsDenied :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsFailed :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsPartiallyRefunded :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsPending :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsProcessing :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsRefunded :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _TsReversed :: Prism' TransactionStatus ()
+ PayPal.Adaptive: _clAccountEmail :: Client -> Text
+ PayPal.Adaptive: _clAppId :: Client -> Text
+ PayPal.Adaptive: _clEnv :: Client -> Env
+ PayPal.Adaptive: _clPassword :: Client -> Text
+ PayPal.Adaptive: _clSig :: Client -> Text
+ PayPal.Adaptive: _clUserId :: Client -> Text
+ PayPal.Adaptive: _crPayKey :: CompletePayResp -> PayKey
+ PayPal.Adaptive: _crReceiver :: CompletePayResp -> Receiver
+ PayPal.Adaptive: _crSenderTransactionId :: CompletePayResp -> TransactionId
+ PayPal.Adaptive: _crTransactionId :: CompletePayResp -> TransactionId
+ PayPal.Adaptive: _piReceiver :: PayInfo -> Receiver
+ PayPal.Adaptive: _piSenderTransactionId :: PayInfo -> Maybe TransactionId
+ PayPal.Adaptive: _piTransactionId :: PayInfo -> Maybe TransactionId
+ PayPal.Adaptive: _piTransactionStatus :: PayInfo -> Maybe TransactionStatus
+ PayPal.Adaptive: _prPayError :: PayResp -> Maybe Text
+ PayPal.Adaptive: _prPayExecStatus :: PayResp -> PayExecStatus
+ PayPal.Adaptive: _prPayInfos :: PayResp -> [PayInfo]
+ PayPal.Adaptive: _prPayKey :: PayResp -> PayKey
+ PayPal.Adaptive: _reAccountId :: Receiver -> Text
+ PayPal.Adaptive: _reAmount :: Receiver -> Text
+ PayPal.Adaptive: _reEmail :: Receiver -> Text
+ PayPal.Adaptive: _unPayKey :: PayKey -> Text
+ PayPal.Adaptive: _unTransactionId :: TransactionId -> Text
+ PayPal.Adaptive: _usdCents :: Money -> Int
+ PayPal.Adaptive: checkComplete :: PayResp -> Either AdaptiveErr CompletePayResp
+ PayPal.Adaptive: clAccountEmail :: Lens' Client Text
+ PayPal.Adaptive: clAppId :: Lens' Client Text
+ PayPal.Adaptive: clEnv :: Lens' Client Env
+ PayPal.Adaptive: clPassword :: Lens' Client Text
+ PayPal.Adaptive: clSig :: Lens' Client Text
+ PayPal.Adaptive: clUserId :: Lens' Client Text
+ PayPal.Adaptive: crPayKey :: Lens' CompletePayResp PayKey
+ PayPal.Adaptive: crReceiver :: Lens' CompletePayResp Receiver
+ PayPal.Adaptive: crSenderTransactionId :: Lens' CompletePayResp TransactionId
+ PayPal.Adaptive: crTransactionId :: Lens' CompletePayResp TransactionId
+ PayPal.Adaptive: data AdaptiveErr
+ PayPal.Adaptive: data Client
+ PayPal.Adaptive: data CompletePayResp
+ PayPal.Adaptive: data Env
+ PayPal.Adaptive: data Money
+ PayPal.Adaptive: data PayExecStatus
+ PayPal.Adaptive: data PayInfo
+ PayPal.Adaptive: data PayResp
+ PayPal.Adaptive: data Receiver
+ PayPal.Adaptive: data TransactionStatus
+ PayPal.Adaptive: m2Currency :: Money -> Text
+ PayPal.Adaptive: m2PayPal :: Money -> String
+ PayPal.Adaptive: newtype PayKey
+ PayPal.Adaptive: newtype TransactionId
+ PayPal.Adaptive: piReceiver :: Lens' PayInfo Receiver
+ PayPal.Adaptive: piSenderTransactionId :: Lens' PayInfo (Maybe TransactionId)
+ PayPal.Adaptive: piTransactionId :: Lens' PayInfo (Maybe TransactionId)
+ PayPal.Adaptive: piTransactionStatus :: Lens' PayInfo (Maybe TransactionStatus)
+ PayPal.Adaptive: prPayError :: Lens' PayResp (Maybe Text)
+ PayPal.Adaptive: prPayExecStatus :: Lens' PayResp PayExecStatus
+ PayPal.Adaptive: prPayInfos :: Lens' PayResp [PayInfo]
+ PayPal.Adaptive: prPayKey :: Lens' PayResp PayKey
+ PayPal.Adaptive: reAccountId :: Lens' Receiver Text
+ PayPal.Adaptive: reAmount :: Lens' Receiver Text
+ PayPal.Adaptive: reEmail :: Lens' Receiver Text
+ PayPal.Adaptive.Deposit: Deposit :: Money -> Text -> Text -> Text -> Deposit
+ PayPal.Adaptive.Deposit: SerializeDeposit :: Deposit -> Text -> SerializeDeposit
+ PayPal.Adaptive.Deposit: _amount :: Deposit -> Money
+ PayPal.Adaptive.Deposit: _cancelUrl :: Deposit -> Text
+ PayPal.Adaptive.Deposit: _returnUrl :: Deposit -> Text
+ PayPal.Adaptive.Deposit: _senderEmail :: Deposit -> Text
+ PayPal.Adaptive.Deposit: amount :: Lens' Deposit Money
+ PayPal.Adaptive.Deposit: approvalUrl :: Client -> PayKey -> Text
+ PayPal.Adaptive.Deposit: cancelUrl :: Lens' Deposit Text
+ PayPal.Adaptive.Deposit: data Deposit
+ PayPal.Adaptive.Deposit: data SerializeDeposit
+ PayPal.Adaptive.Deposit: instance Eq Deposit
+ PayPal.Adaptive.Deposit: instance Show Deposit
+ PayPal.Adaptive.Deposit: instance ToJSON SerializeDeposit
+ PayPal.Adaptive.Deposit: req :: Client -> Deposit -> IO (Either AdaptiveErr PayKey)
+ PayPal.Adaptive.Deposit: returnUrl :: Lens' Deposit Text
+ PayPal.Adaptive.Deposit: senderEmail :: Lens' Deposit Text
+ PayPal.Adaptive.Internal: checkPayKey :: PayResp -> Either AdaptiveErr ()
+ PayPal.Adaptive.Internal: errorFieldNothing :: PayResp -> Either AdaptiveErr ()
+ PayPal.Adaptive.Internal: getPayInfo :: PayResp -> Either AdaptiveErr PayInfo
+ PayPal.Adaptive.Internal: noPayInfo :: PayResp -> Either AdaptiveErr ()
+ PayPal.Adaptive.Internal: parseErrId :: Value -> Parser Int
+ PayPal.Adaptive.Internal: payExecStatusCompleted :: PayResp -> Either AdaptiveErr ()
+ PayPal.Adaptive.Internal: payExecStatusCreated :: PayResp -> Either AdaptiveErr ()
+ PayPal.Adaptive.Internal: ppDecode :: Response ByteString -> Either AdaptiveErr PayResp
+ PayPal.Adaptive.Internal: ppPost :: ToJSON a => Client -> Text -> a -> IO (Either AdaptiveErr (Response ByteString))
+ PayPal.Adaptive.Internal: requestEnvelope :: Value
+ PayPal.Adaptive.Internal: transactionIdJust :: PayResp -> Maybe TransactionId -> Either AdaptiveErr TransactionId
+ PayPal.Adaptive.Internal: transactionStatusCompleted :: PayResp -> PayInfo -> Either AdaptiveErr ()
+ PayPal.Adaptive.Lookup: LookupPayKey :: PayKey -> LookupPayment PayKey
+ PayPal.Adaptive.Lookup: LookupTrID :: TransactionId -> LookupPayment TransactionId
+ PayPal.Adaptive.Lookup: data LookupPayment a
+ PayPal.Adaptive.Lookup: instance ToJSON (LookupPayment a)
+ PayPal.Adaptive.Lookup: req :: Client -> LookupPayment a -> IO (Either AdaptiveErr PayResp)
+ PayPal.Adaptive.Withdrawal: SerializeWithdrawal :: Withdrawal -> Text -> SerializeWithdrawal
+ PayPal.Adaptive.Withdrawal: Withdrawal :: Money -> Text -> Withdrawal
+ PayPal.Adaptive.Withdrawal: _amount :: Withdrawal -> Money
+ PayPal.Adaptive.Withdrawal: _receiverEmail :: Withdrawal -> Text
+ PayPal.Adaptive.Withdrawal: amount :: Lens' Withdrawal Money
+ PayPal.Adaptive.Withdrawal: data SerializeWithdrawal
+ PayPal.Adaptive.Withdrawal: data Withdrawal
+ PayPal.Adaptive.Withdrawal: instance Eq Withdrawal
+ PayPal.Adaptive.Withdrawal: instance Show Withdrawal
+ PayPal.Adaptive.Withdrawal: instance ToJSON SerializeWithdrawal
+ PayPal.Adaptive.Withdrawal: receiverEmail :: Lens' Withdrawal Text
+ PayPal.Adaptive.Withdrawal: req :: Client -> Withdrawal -> IO (Either AdaptiveErr CompletePayResp)
Files
- Example.hs +38/−48
- paypal-adaptive-hoops.cabal +7/−8
- src/PayPal/Adaptive.hs +44/−0
- src/PayPal/Adaptive/Core.hs +367/−0
- src/PayPal/Adaptive/Deposit.hs +75/−0
- src/PayPal/Adaptive/Internal.hs +16/−0
- src/PayPal/Adaptive/Lookup.hs +28/−0
- src/PayPal/Adaptive/Withdrawal.hs +55/−0
- src/Web/PayPal/Adaptive.hs +0/−22
- src/Web/PayPal/Adaptive/Core.hs +0/−537
- src/Web/PayPal/Adaptive/Internal.hs +0/−17
- tests/Local/Money.hs +2/−2
- tests/Local/Parser.hs +28/−26
- tests/Local/json/serializeWithdrawal.json +1/−1
- tests/Remote.hs +73/−84
Example.hs view
@@ -2,11 +2,14 @@ module Main where -import Prelude hiding (getLine, lookup, putStr, putStrLn)-import Data.Default-import Data.Text (Text)+import Prelude hiding (getLine, lookup, putStr, putStrLn)+import Control.Applicative+import Data.Text (Text) import Data.Text.IO-import qualified Web.PayPal.Adaptive as PP+import qualified PayPal.Adaptive as PP+import qualified PayPal.Adaptive.Deposit as DP+import qualified PayPal.Adaptive.Lookup as LP+import qualified PayPal.Adaptive.Withdrawal as WD -- You need to create a sandbox app on PayPal and enter its credentials -- below for this app to run.@@ -27,46 +30,31 @@ main :: IO () main = do putStrLn "-- Sending money to another PayPal account."- PP.toPayPal client withdrawal >>= print- putStrLn ""-- putStrLn "-- Creating a payment from another account to us."- r <- PP.toPayPal client deposit- print r- putStrLn ""- case r of- Left _ -> putStrLn "Stopping on payment creation failure."- Right payResp -> do- let payKey = PP._prPayKey payResp- putStrLn "-- Go here and use the other account's password to approve the payment:"- putStrLn $ PP.approvalUrl client payKey+ resp <- WD.req client withdrawal+ case resp of+ Left e -> print e+ Right withdrawalResp -> do+ print withdrawalResp putStrLn ""- putStrLn "-- Once that's done press enter ..."- _ <- getLine - putStrLn "-- Now looking up the payment on PayPal to make sure it succeeded."- lookupAttempt <- PP.toPayPal client (PP.LookupPayKey payKey)- case lookupAttempt of- Right lookupResp ->- case PP._prPayInfo lookupResp of- [info] -> do- case PP._piTransactionStatus info of- Just PP.TsCompleted -> putStrLn "Withdrawal complete."- _ -> do- putStrLn "The payment has not gone through yet. Expected the PayInfo in"- putStrLn "the response to have a _piTransactionStatus of Just TsCompleted:"- putStrLn ""- print lookupResp- _ -> do- putStrLn "The payment has not gone through yet."- putStrLn "Expected exactly one PayInfo in the response:"- putStrLn ""- print lookupResp- Left lookupError -> do- putStrLn "Error getting information about the payment:"+ putStrLn "-- Creating a payment from another account to us."+ resp2 <- DP.req client deposit+ putStrLn ""+ case resp2 of+ Left e2 -> print e2+ Right payKey -> do+ putStrLn "-- Go here and use the other account's password to approve the payment:"+ putStrLn (DP.approvalUrl client payKey) putStrLn ""- print lookupError+ putStrLn "-- Once that's done press enter ..."+ _ <- getLine + putStrLn "-- Now looking up the payment on PayPal to make sure it succeeded."+ resp3 <- (PP.checkComplete =<<) <$> LP.req client (LP.LookupPayKey payKey)+ case resp3 of+ Left e3 -> print e3+ Right completePayment -> print completePayment+ client :: PP.Client client = PP.Client { PP._clAppId = "APP-80W284485P519543T" -- Currently the ID for all sandbox apps.@@ -77,14 +65,16 @@ , PP._clAccountEmail = accountEmail } -withdrawal :: PP.Withdrawal-withdrawal = def- { PP._wdAmount = PP.USD 100- , PP._wdReceiverEmail = sandboxTestUser+withdrawal :: WD.Withdrawal+withdrawal = WD.Withdrawal+ { WD._amount = PP.USD 100+ , WD._receiverEmail = sandboxTestUser } -deposit :: PP.Deposit-deposit = def- { PP._dpAmount = PP.USD 100- , PP._dpSenderEmail = sandboxTestUser+deposit :: DP.Deposit+deposit = DP.Deposit+ { DP._amount = PP.USD 100+ , DP._senderEmail = sandboxTestUser+ , DP._returnUrl = "https://example.com/"+ , DP._cancelUrl = "https://example.com/" }
paypal-adaptive-hoops.cabal view
@@ -1,5 +1,5 @@ name: paypal-adaptive-hoops-version: 0.10.0.2+version: 0.11.0.0 author: Ian Grant Jeffries maintainer: ian@housejeffries.com category: Web@@ -14,9 +14,12 @@ library hs-source-dirs: src- exposed-modules: Web.PayPal.Adaptive- Web.PayPal.Adaptive.Internal- other-modules: Web.PayPal.Adaptive.Core+ exposed-modules: PayPal.Adaptive+ PayPal.Adaptive.Deposit+ PayPal.Adaptive.Lookup+ PayPal.Adaptive.Withdrawal+ PayPal.Adaptive.Internal+ other-modules: PayPal.Adaptive.Core default-language: Haskell2010 ghc-options: -Wall default-extensions: OverloadedStrings@@ -27,7 +30,6 @@ build-depends: aeson >= 0.8 && < 0.9 , base >= 4.6 && < 4.9 , bytestring >= 0.10 && < 0.11- , data-default >= 0.5 && < 0.6 , errors >= 1.4 && < 1.5 , http-client >= 0.4.2 && < 0.5 , lens >= 4.6 && < 5.0@@ -52,7 +54,6 @@ build-depends: aeson , base , bytestring- , data-default , paypal-adaptive-hoops , text , HUnit >= 1.2 && < 1.3@@ -72,7 +73,6 @@ build-depends: aeson , base , bytestring- , data-default , paypal-adaptive-hoops , text , HUnit >= 1.2 && < 1.3@@ -87,7 +87,6 @@ other-extensions: OverloadedStrings build-depends: base- , data-default , paypal-adaptive-hoops , text
+ src/PayPal/Adaptive.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive (++ -- * Client+ Client (..)+ , Env (..)++ -- * Errors+ , AdaptiveErr (..)++ -- * Payment Responses+ , PayResp (..)+ , CompletePayResp (..)+ , checkComplete+ , PayExecStatus (..)+ , PayKey (..)+ , PayInfo (..)+ , Receiver (..)+ , TransactionId (..)+ , TransactionStatus(..)++ -- * Money+ , Money (..)+ , m2Currency+ , m2PayPal++ , module PayPal.Adaptive+ ) where++import Control.Lens+import PayPal.Adaptive.Core++-- * Lenses+$(makeLenses ''Client)+$(makeLenses ''CompletePayResp)+$(makePrisms ''AdaptiveErr)+$(makePrisms ''Env)+$(makePrisms ''PayExecStatus)+$(makeLenses ''PayInfo)+$(makeLenses ''PayResp)+$(makeLenses ''Receiver)+$(makePrisms ''TransactionId)+$(makePrisms ''TransactionStatus)
+ src/PayPal/Adaptive/Core.hs view
@@ -0,0 +1,367 @@+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core where++import Prelude hiding (mapM)+import Control.Applicative+import Control.Exception+import Control.Lens hiding ((.=))+import Data.Aeson+import Data.Aeson.TH hiding (Options)+import Data.Aeson.Types hiding (Options)+import Data.ByteString.Lazy (ByteString)+import Data.Char+import Data.Maybe+import Data.Monoid+import Data.Text (Text)+import qualified Data.Text as T+import Data.Text.Encoding+import Data.Text.Read+import Data.Traversable+import qualified Data.Vector as V+import Network.Wreq++--------------------------------------------------+-- Client+--------------------------------------------------++data Client = Client+ { _clAppId :: Text+ , _clUserId :: Text+ , _clEnv :: Env+ , _clPassword :: Text+ , _clSig :: Text+ -- | Not needed for authentication, but deposits are sent+ -- to here and withdrawals are sent from here. Must be the+ -- email associated with your PayPal app.+ , _clAccountEmail :: Text+ } deriving (Eq, Show)++data Env = Sandbox | Production deriving (Eq, Show, Read)++--------------------------------------------------+-- Errors+--------------------------------------------------++-- | Text is an error message in all the below types.+--+-- AeShouldNotHappen is meant to cover PayPal responses to API requests+-- that this library doesn't support sending in the first place.+-- TODO: is this actually how we're using it?+data AdaptiveErr+ = AeConnectionErr Text+ | AeDecodeFailed ByteString Text+ | AeInvalidCredentials+ -- ^ Just a wrapper around an AeErrCodes Int.+ | AeNoSuchEmail+ -- ^ Just a wrapper around an AeErrCodes Int. Raised when the Deposit+ -- sender email or the Withdrawal account email doesn't exist.+ | AeErrCodes [Int]+ | AePending PayResp+ | AeRefused PayResp+ | AeShouldNotHappen PayResp Text+ deriving (Eq, Show)++-- Error responses are what's returned if, e.g., your password is incorrect.+--+-- Here's an example error response. Only the parts we parse are included.+--+-- {+-- "error": [+-- {+-- "errorId": "111111",+-- }+-- ]+-- }+instance FromJSON AdaptiveErr where+ parseJSON = withObject "error response" $ \o ->+ AeErrCodes . V.toList <$> (mapM parseErrId =<< o .: "error")++-- | Exported by PayPal.Adaptive.Internal+parseErrId :: Value -> Parser Int+parseErrId = withObject "error array item" $ \o -> do+ a <- o .: "errorId"+ case decimal a of+ Left _ -> fail "Could not parse error response code to Int"+ Right (b,_) -> return b++--------------------------------------------------+-- * Payment Responses+--------------------------------------------------++-- | Used for forgiving attempts to parse PayPal responses. For instance,+-- this library never expects more than one PayInfo. We still try to+-- parse them as a list here so we can still sucessfully parse a PayResp+-- to use in an 'AdaptiveError' if for some reason more than one 'PayInfo'+-- is returned +data PayResp = PayResp+ { _prPayError :: Maybe Text+ , _prPayExecStatus :: PayExecStatus+ , _prPayKey :: PayKey+ , _prPayInfos :: [PayInfo]+ } deriving (Eq, Show)++-- | A version of 'PayResp' for completed transactions.+--+-- Status fields are omitted since they should be complete.+--+-- Has no separate field for a 'PayInfo' list since successful transactions+-- will always have exactly one. Instead the pay info fields have been+-- flattened into the top level of this structure.+data CompletePayResp = CompletePayResp+ { _crPayKey :: PayKey+ , _crReceiver :: Receiver+ , _crSenderTransactionId :: TransactionId+ , _crTransactionId :: TransactionId+ } deriving (Eq, Show)++checkComplete :: PayResp -> Either AdaptiveErr CompletePayResp+checkComplete pr = do+ errorFieldNothing pr+ payExecStatusCompleted pr+ checkPayKey pr+ info <- getPayInfo pr+ sid <- transactionIdJust pr (_piSenderTransactionId info)+ transactionStatusCompleted pr info+ tid <- transactionIdJust pr (_piTransactionId info)+ return $ CompletePayResp (_prPayKey pr) (_piReceiver info) sid tid++instance FromJSON PayResp where+ parseJSON = withObject "PayResp" $ \o -> do+ e <- o .:? "payErrorList"+ k <- o .: "payKey"++ s <- o .:? "paymentExecStatus"+ s' <- o .:? "status"+ status <- maybe (fail "no paymentExecStatus or status") return (if isJust s then s else s')++ a <- o .:? "paymentInfoList"+ case a of+ Nothing -> return $ PayResp e status k mempty+ Just v -> do+ infos <- v .:? "paymentInfo" .!= mempty+ return $ PayResp e status k infos++-- | This refers to the processing of this request. To make sure a payment+-- has actually gone through, check 'TransactionStatus' instead.+--+-- PayPal returns this using the JSON object key "paymentExecStatus"+-- in responses to Withdrawal and Deposit and "status" in responses to+-- LookupPayment.+data PayExecStatus+ = PeCreated+ | PeCompleted+ | PeIncomplete+ | PeError+ | PeReversalError+ | PeProcessing+ | PePending+ deriving (Eq, Show, Read)++-- | Expires after three hours.+--+-- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>+newtype PayKey = PayKey { _unPayKey :: Text } deriving (Eq, Show)++instance FromJSON PayKey where+ parseJSON = withText "PayKey" $ return . PayKey++instance ToJSON PayKey where+ toJSON (PayKey a) = toJSON a++-- | Right toPayPal responses return a PayInfo along with a+-- _piTransactionStatus Just, with some exceptions.+--+-- 1. Withdrawals to nonexistent accounts return no PayInfos.+--+-- 2. Lookups on such transactions return one PayInfo with a+-- _piTransactionStatus of Nothing.+--+-- 3. Deposits don't return PayInfos.+--+-- 4. Lookups on such transactions return one PayInfo with a+-- _piTransactionStatus of Nothing.+--+-- NOTE: We use the same newtype for both _piSenderTransactionId and+-- _piTransactionStatus. They will have different values, but either+-- can be used to look up a payment.+data PayInfo = PayInfo+ { _piReceiver :: Receiver+ , _piSenderTransactionId :: Maybe TransactionId+ , _piTransactionStatus :: Maybe TransactionStatus+ , _piTransactionId :: Maybe TransactionId+ } deriving (Eq, Show)++data Receiver = Receiver+ { _reAmount :: Text+ -- ^ Haven't converted this to Money yet because I'm not sure how+ -- to determine its currency.+ , _reEmail :: Text+ , _reAccountId :: Text+ } deriving (Eq, Show)++newtype TransactionId = TransactionId { _unTransactionId :: Text } deriving (Eq, Show)++instance FromJSON TransactionId where+ parseJSON = withText "TransactionId" $ return . TransactionId++instance ToJSON TransactionId where+ toJSON (TransactionId a) = toJSON a++data TransactionStatus+ = TsCompleted+ | TsPending+ | TsCreated+ | TsPartiallyRefunded+ | TsDenied+ | TsProcessing+ | TsReversed+ | TsRefunded+ | TsFailed+ deriving (Eq, Show, Read)++--------------------------------------------------+-- Money+--------------------------------------------------++data Money = USD { _usdCents :: Int } deriving (Eq, Show)++-- NOTE: Once we add more currencies this instance will violate the+-- Monoid laws.+instance Monoid Money where+ mempty = USD 0+ mappend (USD c1) (USD c2) = USD $ c1 + c2++m2Currency :: Money -> Text+m2Currency (USD _) = "USD"++m2PayPal :: Money -> String+m2PayPal (USD c) =+ let s = show (abs c)+ a = case length s of+ 0 -> "0.00"+ 1 -> "0.0" <> s+ 2 -> "0." <> s+ _ -> tailInsert 2 '.' s+ in if c < 0 then '-':a else a++ where+ tailInsert :: Int -> a -> [a] -> [a]+ tailInsert i x xs =+ let (ys, zs) = splitAt i (reverse xs)+ in reverse (ys <> pure x <> zs)++--------------------------------------------------+-- Internal Helpers+--------------------------------------------------++-- | "en_US" is hardcoded because it's the only supported error language.+requestEnvelope :: Value+requestEnvelope = object ["errorLanguage" .= ("en_US" :: Text)]++ppPost :: (ToJSON a) => Client -> Text -> a -> IO (Either AdaptiveErr (Response ByteString))+ppPost c endpoint p = catch (return . Right =<< runPost) handler+ where+ runPost :: IO (Response ByteString)+ runPost = postWith opts (clBaseUrl <> T.unpack endpoint) (toJSON p)++ clBaseUrl :: String+ clBaseUrl =+ case _clEnv c of+ Sandbox -> "https://svcs.sandbox.paypal.com/AdaptivePayments/"+ Production -> "https://svcs.paypal.com/AdaptivePayments/"++ handler :: SomeException -> IO (Either AdaptiveErr (Response ByteString))+ handler e = return . Left . AeConnectionErr . T.pack . show $ e++ opts :: Options+ opts = defaults+ & header "X-PAYPAL-SECURITY-USERID" .~ [ encodeUtf8 (_clUserId c) ]+ & header "X-PAYPAL-SECURITY-PASSWORD" .~ [ encodeUtf8 (_clPassword c) ]+ & header "X-PAYPAL-SECURITY-SIGNATURE" .~ [ encodeUtf8 (_clSig c) ]+ & header "X-PAYPAL-APPLICATION-ID" .~ [ encodeUtf8 (_clAppId c) ]+ & header "X-PAYPAL-REQUEST-DATA-FORMAT" .~ [ "JSON" ]+ & header "X-PAYPAL-RESPONSE-DATA-FORMAT" .~ [ "JSON" ]++ppDecode :: Response ByteString -> Either AdaptiveErr PayResp+ppDecode r =+ let b = r ^. responseBody+ in case eitherDecode b of+ Right d -> Right d+ Left e -> -- If the response isn't a PayResp, it might be a PayPal error message.+ case decode b of+ Just (AeErrCodes codes) -> Left $ codeErr codes+ _ -> Left $ AeDecodeFailed b (T.pack e)+ where+ codeErr :: [Int] -> AdaptiveErr+ codeErr cs =+ if | elem 520003 cs -> AeInvalidCredentials+ | elem 589039 cs -> AeNoSuchEmail+ | otherwise -> AeErrCodes cs++payExecStatusCompleted :: PayResp -> Either AdaptiveErr ()+payExecStatusCompleted a =+ case _prPayExecStatus a of+ PeCompleted -> Right ()+ PeProcessing -> Left $ AePending a+ PePending -> Left $ AePending a+ PeError -> Left $ AeRefused a+ _ -> Left $ AeShouldNotHappen a "unsupported PayExecStatus"++-- | TODO: this makes the assumption that an empty PayInfo list means+-- the transaction is pending. Is this always right?+getPayInfo :: PayResp -> Either AdaptiveErr PayInfo+getPayInfo a =+ case _prPayInfos a of+ [] -> Left $ AePending a+ [info] -> Right info+ _ -> Left $ AeShouldNotHappen a "more than one PayInfo in reponse"++transactionStatusCompleted :: PayResp -> PayInfo -> Either AdaptiveErr ()+transactionStatusCompleted a info =+ case _piTransactionStatus info of+ Just TsCompleted -> Right ()+ Just TsPending -> Left $ AePending a+ Just TsProcessing -> Left $ AePending a+ Just TsDenied -> Left $ AeRefused a+ Just TsFailed -> Left $ AeRefused a+ _ -> Left $ AeShouldNotHappen a "unsupported TransactionStatus"++errorFieldNothing :: PayResp -> Either AdaptiveErr ()+errorFieldNothing a+ | _prPayError a == Nothing = Right ()+ | otherwise = Left $ AeShouldNotHappen a+ "PayResp error field is Just in otherwise correct payment"++checkPayKey :: PayResp -> Either AdaptiveErr ()+checkPayKey a+ | T.null . _unPayKey . _prPayKey $ a =+ Left $ AeShouldNotHappen a "got a PayKey with length zero"+ | otherwise = Right ()++payExecStatusCreated :: PayResp -> Either AdaptiveErr ()+payExecStatusCreated a =+ case _prPayExecStatus a of+ PeCreated -> Right ()+ PeProcessing -> Left $ AePending a+ PePending -> Left $ AePending a+ PeError -> Left $ AeRefused a+ _ -> Left $ AeShouldNotHappen a "unsupported PayExecStatus"++noPayInfo :: PayResp -> Either AdaptiveErr ()+noPayInfo a =+ case _prPayInfos a of+ [] -> Right ()+ _ -> Left $ AeShouldNotHappen a+ "At least one PayInfo present in otherwise successful deposit resp"++transactionIdJust :: PayResp -> Maybe TransactionId -> Either AdaptiveErr TransactionId+transactionIdJust a Nothing = Left $ AeShouldNotHappen a "expected a Just for Maybe TransactionId"+transactionIdJust _ (Just tid) = Right tid+++$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . drop 2 } ''PayExecStatus)+$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''PayInfo)+$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''Receiver)+$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . camelTo '_' . drop 2 } ''TransactionStatus)
+ src/PayPal/Adaptive/Deposit.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE TemplateHaskell #-}++-- | Start the process of someone paying you. You have to redirect the payer's+-- browser to PayPal to finish it.+--+-- "Pay" operation docs:+-- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>+--+-- This is a "Simple Payment" which means there's a single sender+-- and receiver (same as Withdrawal).+--+-- It uses the "Explicit" payment approval type, meaning the payer must go to+-- PayPal in their browser to approve the payment. Here's an example payment+-- approval URL with a PayKey of "foo":+--+-- https://www.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=foo++module PayPal.Adaptive.Deposit where++import Control.Lens hiding ((.=))+import Data.Aeson+import Data.Monoid+import Data.Text (Text)+import PayPal.Adaptive.Core++data Deposit = Deposit+ { _amount :: Money+ , _senderEmail :: Text+ , _returnUrl :: Text+ , _cancelUrl :: Text+ } deriving (Eq, Show)++-- | For internal use.+data SerializeDeposit = SerializeDeposit Deposit Text++instance ToJSON SerializeDeposit where+ toJSON (SerializeDeposit p accountEmail) = object+ [ "actionType" .= ("PAY" :: Text)+ , "currencyCode" .= (m2Currency . _amount) p+ , "receiverList" .=+ object+ [ "receiver" .= [ object+ ["amount" .= m2PayPal (_amount p)+ , "email" .= accountEmail+ ]+ ]]+ , "returnUrl" .= _returnUrl p+ , "cancelUrl" .= _cancelUrl p+ , "requestEnvelope" .= requestEnvelope+ , "senderEmail" .= _senderEmail p+ ]++req :: Client -> Deposit -> IO (Either AdaptiveErr PayKey)+req c d = do+ resp <- ppPost c "Pay" $ SerializeDeposit d (_clAccountEmail c)+ return $ do+ pr <- ppDecode =<< resp+ payExecStatusCreated pr+ noPayInfo pr+ errorFieldNothing pr+ checkPayKey pr+ Right (_prPayKey pr)++approvalUrl :: Client -> PayKey -> Text+approvalUrl c k =+ case _clEnv c of+ Sandbox ->+ "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="+ <> _unPayKey k+ Production ->+ "https://www.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="+ <> _unPayKey k++-- * Lenses+$(makeLenses ''Deposit)
+ src/PayPal/Adaptive/Internal.hs view
@@ -0,0 +1,16 @@+module PayPal.Adaptive.Internal+ ( ppPost+ , ppDecode+ , parseErrId+ , requestEnvelope+ , payExecStatusCompleted+ , getPayInfo+ , transactionStatusCompleted+ , errorFieldNothing+ , checkPayKey+ , payExecStatusCreated+ , noPayInfo+ , transactionIdJust+ ) where++import PayPal.Adaptive.Core
+ src/PayPal/Adaptive/Lookup.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE GADTs #-}++-- | Look up information about a payment.+--+-- <https://developer.paypal.com/docs/classic/api/adaptive-payments/PaymentDetails_API_Operation/>++module PayPal.Adaptive.Lookup where++import Control.Applicative+import Data.Aeson+import PayPal.Adaptive.Core++data LookupPayment a where+ LookupTrID :: TransactionId -> LookupPayment TransactionId+ LookupPayKey :: PayKey -> LookupPayment PayKey++instance ToJSON (LookupPayment a) where+ toJSON (LookupTrID (TransactionId p)) =+ object [ "transactionId" .= p+ , "requestEnvelope" .= requestEnvelope+ ]+ toJSON (LookupPayKey (PayKey p)) =+ object [ "payKey" .= p+ , "requestEnvelope" .= requestEnvelope+ ]++req :: Client -> LookupPayment a -> IO (Either AdaptiveErr PayResp)+req c l = (>>= ppDecode) <$> ppPost c "PaymentDetails" l
+ src/PayPal/Adaptive/Withdrawal.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE TemplateHaskell #-}++-- | Send a single payment from your account.+--+-- "Pay" operation docs:+-- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>+--+-- This is a "Simple Payment" which means there's a single sender+-- and receiver (same as Deposit).+--+-- It uses the "Implicit" payment approval type, meaning the application+-- making the API call must also be the sender of the payment.++module PayPal.Adaptive.Withdrawal where++import Control.Lens hiding ((.=))+import Data.Aeson+import Data.Text (Text)+import PayPal.Adaptive.Core++data Withdrawal = Withdrawal+ { _amount :: Money+ , _receiverEmail :: Text+ } deriving (Eq, Show)++-- | For internal use.+data SerializeWithdrawal = SerializeWithdrawal Withdrawal Text++instance ToJSON SerializeWithdrawal where+ toJSON (SerializeWithdrawal p accountEmail) = object+ [ "actionType" .= ("PAY" :: Text)+ , "currencyCode" .= (m2Currency . _amount) p+ , "receiverList" .= object+ [ "receiver" .=+ [ object+ [ "amount" .= m2PayPal (_amount p)+ , "email" .= _receiverEmail p+ ]+ ]+ ]+ , "returnUrl" .= ("https://example.com/" :: Text)+ , "cancelUrl" .= ("https://example.com/" :: Text)+ , "requestEnvelope" .= requestEnvelope+ , "senderEmail" .= accountEmail+ ]++-- | If a PayPal account doesn't exist yet PayPal responds with an empty+-- list of PayInfos. We return this as Left 'AePending'.+req :: Client -> Withdrawal -> IO (Either AdaptiveErr CompletePayResp)+req c w = do+ resp <- ppPost c "Pay" $ SerializeWithdrawal w (_clAccountEmail c)+ return $ checkComplete =<< ppDecode =<< resp++-- * Lenses+$(makeLenses ''Withdrawal)
− src/Web/PayPal/Adaptive.hs
@@ -1,22 +0,0 @@-module Web.PayPal.Adaptive- ( Client (..)- , Env (..)- , AdaptiveAPI (..)- , AdaptiveErr (..)- , Withdrawal (..)- , Deposit (..)- , approvalUrl- , LookupPayment (..)- , PayResp (..)- , PayExecStatus (..)- , PayKey (..)- , PayInfo (..)- , Receiver (..)- , TransactionId (..)- , TransactionStatus(..)- , Money (..)- , m2Currency- , m2PayPal- ) where--import Web.PayPal.Adaptive.Core
− src/Web/PayPal/Adaptive/Core.hs
@@ -1,537 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE TemplateHaskell #-}--module Web.PayPal.Adaptive.Core where--import Prelude hiding (mapM)-import Control.Applicative-import Control.Exception-import Control.Lens hiding ((.=))-import Control.Monad ((>=>))-import Data.Aeson-import Data.Aeson.TH hiding (Options)-import Data.Aeson.Types hiding (Options)-import Data.ByteString.Lazy (ByteString)-import Data.Char-import Data.Default-import Data.Maybe-import Data.Monoid-import Data.Text (Text)-import qualified Data.Text as T-import Data.Text.Encoding-import Data.Text.Read-import Data.Traversable-import qualified Data.Vector as V-import Network.Wreq------------------------------------------------------- Client-----------------------------------------------------data Client = Client- { _clAppId :: Text- , _clUserId :: Text- , _clEnv :: Env- , _clPassword :: Text- , _clSig :: Text- -- | Not needed for authentication, but deposits are sent- -- to here and withdrawals are sent from here. Must be the- -- email associated with your PayPal app.- , _clAccountEmail :: Text- } deriving (Eq, Show)--data Env = Sandbox | Production deriving (Eq, Show, Read)------------------------------------------------------- API class-----------------------------------------------------class AdaptiveAPI a where- toPayPal :: Client -> a -> IO (Either AdaptiveErr PayResp)---- | Exported by Web.PayPal.Adaptive.Internal-ppPost :: (ToJSON a) => Client -> Text -> a -> IO (Either AdaptiveErr (Response ByteString))-ppPost c endpoint p = catch (return . Right =<< runPost) handler- where- runPost :: IO (Response ByteString)- runPost = postWith opts (clBaseUrl <> T.unpack endpoint) (toJSON p)-- clBaseUrl :: String- clBaseUrl =- case _clEnv c of- Sandbox -> "https://svcs.sandbox.paypal.com/AdaptivePayments/"- Production -> "https://svcs.paypal.com/AdaptivePayments/"-- handler :: SomeException -> IO (Either AdaptiveErr (Response ByteString))- handler e = return . Left . AeConnectionErr . T.pack . show $ e-- opts :: Options- opts = defaults- & header "X-PAYPAL-SECURITY-USERID" .~ [ encodeUtf8 (_clUserId c) ]- & header "X-PAYPAL-SECURITY-PASSWORD" .~ [ encodeUtf8 (_clPassword c) ]- & header "X-PAYPAL-SECURITY-SIGNATURE" .~ [ encodeUtf8 (_clSig c) ]- & header "X-PAYPAL-APPLICATION-ID" .~ [ encodeUtf8 (_clAppId c) ]- & header "X-PAYPAL-REQUEST-DATA-FORMAT" .~ [ "JSON" ]- & header "X-PAYPAL-RESPONSE-DATA-FORMAT" .~ [ "JSON" ]---- | Exported by Web.PayPal.Adaptive.Internal-ppDecode :: (FromJSON a) => Response ByteString -> Either AdaptiveErr a-ppDecode r =- let b = r ^. responseBody- in case eitherDecode b of- Right d -> Right d- Left e -> -- If the response isn't a PayResp, it might be a PayPal error message.- case decode b of- Just (AeErrCodes codes) -> Left $ codeErr codes- _ -> Left $ AeDecodeFailed b (T.pack e)-- where- codeErr :: [Int] -> AdaptiveErr- codeErr cs =- if | elem 520003 cs -> AeInvalidCredentials- | elem 589039 cs -> AeNoSuchEmail- | otherwise -> AeErrCodes cs------------------------------------------------------- Errors------------------------------------------------------- | Text is an error message in all the below types.------ AeShouldNotHappen is meant to cover PayPal responses to API requests--- that this library doesn't support sending in the first place.--- TODO: is this actually how we're using it?-data AdaptiveErr- = AeConnectionErr Text- | AeDecodeFailed ByteString Text- -- | AeInvalidCredentials just a wrapper around an AeErrCodes Int.- | AeInvalidCredentials- -- | AeNoSuchEmail is just a wrapper around an AeErrCodes Int.- -- It's raised when the Deposit sender email or the Withdrawal- -- account email doesn't exist.- | AeNoSuchEmail- | AeErrCodes [Int]- | AePending PayResp- | AeRefused PayResp- | AeShouldNotHappen PayResp Text- deriving (Eq, Show)---- Error responses are what's returned if, e.g., your password is incorrect.------ Here's an example error response. Only the parts we parse are included.------ {--- "error": [--- {--- "errorId": "111111",--- }--- ]--- }-instance FromJSON AdaptiveErr where- parseJSON = withObject "error response" $ \o ->- AeErrCodes . V.toList <$> (mapM parseErrId =<< o .: "error")---- | Exported by Web.PayPal.Adaptive.Internal-parseErrId :: Value -> Parser Int-parseErrId = withObject "error array item" $ \o -> do- a <- o .: "errorId"- case decimal a of- Left _ -> fail "Could not parse error response code to Int"- Right (b,_) -> return b------------------------------------------------------- Payment Utils------------------------------------------------------- | Exported by Web.PayPal.Adaptive.Internal------ "en_US" is hardcoded because it's the only supported error language.-requestEnvelope :: Value-requestEnvelope = object ["errorLanguage" .= ("en_US" :: Text)]---- | Exported by Web.PayPal.Adaptive.Internal-payExecStatusCompleted :: PayResp -> Either AdaptiveErr ()-payExecStatusCompleted a =- case _prPayExecStatus a of- PeCompleted -> Right ()- PeProcessing -> Left $ AePending a- PePending -> Left $ AePending a- PeError -> Left $ AeRefused a- _ -> Left $ AeShouldNotHappen a "unsupported PayExecStatus"---- | Exported by Web.PayPal.Adaptive.Internal------ TODO: this makes the assumption that an empty PayInfo list means--- the transaction is pending. Is this always right?-getPayInfo :: PayResp -> Either AdaptiveErr PayInfo-getPayInfo a =- case _prPayInfo a of- [] -> Left $ AePending a- [info] -> Right info- _ -> Left $ AeShouldNotHappen a "more than one PayInfo in reponse"---- | Exported by Web.PayPal.Adaptive.Internal-transactionStatusCompleted :: PayResp -> PayInfo -> Either AdaptiveErr ()-transactionStatusCompleted a info =- case _piTransactionStatus info of- Just TsCompleted -> Right ()- Just TsPending -> Left $ AePending a- Just TsProcessing -> Left $ AePending a- Just TsDenied -> Left $ AeRefused a- Just TsFailed -> Left $ AeRefused a- _ -> Left $ AeShouldNotHappen a "unsupported TransactionStatus"---- | Exported by Web.PayPal.Adaptive.Internal-errorFieldNothing :: PayResp -> Either AdaptiveErr ()-errorFieldNothing a- | _prPayError a == Nothing = Right ()- | otherwise = Left $ AeShouldNotHappen a- "PayResp error field is Just in otherwise correct payment"---- | Exported by Web.PayPal.Adaptive.Internal-checkPayKey :: PayResp -> Either AdaptiveErr ()-checkPayKey a- | T.null . _unPayKey . _prPayKey $ a =- Left $ AeShouldNotHappen a "Length zero PayKey in otherwise correct deposit resp"- | otherwise = Right ()---- | Exported by Web.PayPal.Adaptive.Internal-payExecStatusCreated :: PayResp -> Either AdaptiveErr ()-payExecStatusCreated a =- case _prPayExecStatus a of- PeCreated -> Right ()- PeProcessing -> Left $ AePending a- PePending -> Left $ AePending a- PeError -> Left $ AeRefused a- _ -> Left $ AeShouldNotHappen a "unsupported PayExecStatus"---- | Exported by Web.PayPal.Adaptive.Internal-noPayInfo :: PayResp -> Either AdaptiveErr ()-noPayInfo a =- case _prPayInfo a of- [] -> Right ()- _ -> Left $ AeShouldNotHappen a- "At least one PayInfo present in otherwise successful deposit resp"------------------------------------------------------- Withdrawals------------------------------------------------------- | Send a single payment from your account.------ "Pay" operation docs:--- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>------ This is a "Simple Payment" which means there's a single sender--- and receiver (same as Deposit).------ It uses the "Implicit" payment approval type, meaning the application--- making the API call must also be the sender of the payment.-data Withdrawal = Withdrawal- { _wdAmount :: Money- , _wdReceiverEmail :: Text- , _wdReturnUrl :: Text- , _wdCancelUrl :: Text- } deriving (Eq, Show)--instance Default Withdrawal where- def = Withdrawal- { _wdAmount = mempty- , _wdReceiverEmail = mempty- , _wdReturnUrl = "https://example.com/"- , _wdCancelUrl = "https://example.com/cancel"- }---- | Exported by Web.PayPal.Adaptive.Internal-data SerializeWithdrawal = SerializeWithdrawal Withdrawal Text--instance ToJSON SerializeWithdrawal where- toJSON (SerializeWithdrawal p accountEmail) = object- [ "actionType" .= ("PAY" :: Text)- , "currencyCode" .= (m2Currency . _wdAmount) p- , "receiverList" .=- object- [ "receiver" .= [ object- ["amount" .= m2PayPal (_wdAmount p)- , "email" .= _wdReceiverEmail p- ]- ]]- , "returnUrl" .= _wdReturnUrl p- , "cancelUrl" .= _wdCancelUrl p- , "requestEnvelope" .= requestEnvelope- , "senderEmail" .= accountEmail- ]---- | Sending a payment to a PayPal account that doesn't exist yet--- returns a PayResp with an empty list of PayInfos. We return this--- as Left 'AePending'.-instance AdaptiveAPI Withdrawal where- toPayPal c p = (>>= ppDecode >=> ensureSucceeded) <$> ppPost c "Pay" serializeWithdrawal- where- serializeWithdrawal :: SerializeWithdrawal- serializeWithdrawal = SerializeWithdrawal p (_clAccountEmail c)-- ensureSucceeded :: PayResp -> Either AdaptiveErr PayResp- ensureSucceeded a = do- payExecStatusCompleted a- info <- getPayInfo a- transactionStatusCompleted a info- errorFieldNothing a- checkPayKey a- Right a------------------------------------------------------- Deposits------------------------------------------------------- | Start the process of someone paying you. You have to redirect the payer's--- browser to PayPal to finish it.------ "Pay" operation docs:--- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>------ This is a "Simple Payment" which means there's a single sender--- and receiver (same as Withdrawal).------ It uses the "Explicit" payment approval type, meaning the payer must go to--- PayPal in their browser to approve the payment. Here's an example payment--- approval URL with a PayKey of "foo":------ https://www.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=foo-data Deposit = Deposit- { _dpAmount :: Money- , _dpSenderEmail :: Text- , _dpReturnUrl :: Text- , _dpCancelUrl :: Text- } deriving (Eq, Show)--instance Default Deposit where- def = Deposit- { _dpAmount = mempty- , _dpSenderEmail = mempty- , _dpReturnUrl = "https://example.com/"- , _dpCancelUrl = "https://example.com/cancel"- }---- | Exported by Web.PayPal.Adaptive.Internal-data SerializeDeposit = SerializeDeposit Deposit Text--instance ToJSON SerializeDeposit where- toJSON (SerializeDeposit p accountEmail) = object- [ "actionType" .= ("PAY" :: Text)- , "currencyCode" .= (m2Currency . _dpAmount) p- , "receiverList" .=- object- [ "receiver" .= [ object- ["amount" .= m2PayPal (_dpAmount p)- , "email" .= accountEmail- ]- ]]- , "returnUrl" .= _dpReturnUrl p- , "cancelUrl" .= _dpCancelUrl p- , "requestEnvelope" .= requestEnvelope- , "senderEmail" .= _dpSenderEmail p- ]--instance AdaptiveAPI Deposit where- toPayPal c p = (>>= ppDecode >=> ensureSucceeded) <$> ppPost c "Pay" serializeDeposit- where- serializeDeposit :: SerializeDeposit- serializeDeposit = SerializeDeposit p (_clAccountEmail c)-- ensureSucceeded :: PayResp -> Either AdaptiveErr PayResp- ensureSucceeded a = do- payExecStatusCreated a- noPayInfo a- errorFieldNothing a- checkPayKey a- Right a--approvalUrl :: Client -> PayKey -> Text-approvalUrl c k =- case _clEnv c of- Sandbox ->- "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="- <> _unPayKey k- Production ->- "https://www.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="- <> _unPayKey k------------------------------------------------------- Lookup Payment------------------------------------------------------- | Look up information about a payment.------ <https://developer.paypal.com/docs/classic/api/adaptive-payments/PaymentDetails_API_Operation/>-data LookupPayment a where- LookupTrID :: TransactionId -> LookupPayment TransactionId- LookupPayKey :: PayKey -> LookupPayment PayKey--instance ToJSON (LookupPayment a) where- toJSON (LookupTrID (TransactionId p)) =- object [ "transactionId" .= p- , "requestEnvelope" .= requestEnvelope- ]- toJSON (LookupPayKey (PayKey p)) =- object [ "payKey" .= p- , "requestEnvelope" .= requestEnvelope- ]--instance AdaptiveAPI (LookupPayment a) where- toPayPal c p = (>>= ppDecode) <$> ppPost c "PaymentDetails" p------------------------------------------------------- Payment Responses-----------------------------------------------------data PayResp = PayResp- { _prPayError :: Maybe Text- , _prPayExecStatus :: PayExecStatus- , _prPayKey :: PayKey- , _prPayInfo :: [PayInfo]- } deriving (Eq, Show)--instance FromJSON PayResp where- parseJSON = withObject "PayResp" $ \o -> do- e <- o .:? "payErrorList"- k <- o .: "payKey"-- s <- o .:? "paymentExecStatus"- s' <- o .:? "status"- status <- maybe (fail "no paymentExecStatus or status") return (if isJust s then s else s')-- a <- o .:? "paymentInfoList"- case a of- Nothing -> return $ PayResp e status k mempty- Just v -> do- infos <- v .:? "paymentInfo" .!= mempty- return $ PayResp e status k infos---- This refers to the processing of this request. To make sure a payment--- has actually gone through, check 'TransactionStatus' instead.------ PayPal returns this using the JSON object key "paymentExecStatus"--- in responses to Withdrawal and Deposit and "status" in responses to--- LookupPayment.-data PayExecStatus- = PeCreated- | PeCompleted- | PeIncomplete- | PeError- | PeReversalError- | PeProcessing- | PePending- deriving (Eq, Show, Read)---- | Expires after three hours.------ <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>-newtype PayKey = PayKey { _unPayKey :: Text } deriving (Eq, Show)--instance FromJSON PayKey where- parseJSON = withText "PayKey" $ return . PayKey--instance ToJSON PayKey where- toJSON (PayKey a) = toJSON a---- | Right toPayPal responses return a PayInfo along with a--- _piTransactionStatus Just, with some exceptions.------ 1. Withdrawals to nonexistent accounts return no PayInfos.------ 2. Lookups on such transactions return one PayInfo with a--- _piTransactionStatus of Nothing.------ 3. Deposits don't return PayInfos.------ 4. Lookups on such transactions return one PayInfo with a--- _piTransactionStatus of Nothing.------ NOTE: We use the same newtype for both _piSenderTransactionId and--- _piTransactionStatus. They will have different values, but either--- can be used to look up a payment.-data PayInfo = PayInfo- { _piReceiver :: Receiver- , _piSenderTransactionId :: Maybe TransactionId- , _piTransactionStatus :: Maybe TransactionStatus- , _piTransactionId :: Maybe TransactionId- } deriving (Eq, Show)--data Receiver = Receiver- -- Haven't converted this to Money yet because I'm not sure how- -- to determine its currency.- { _reAmount :: Text- , _reEmail :: Text- , _reAccountId :: Text- } deriving (Eq, Show)--newtype TransactionId = TransactionId { _unTransactionId :: Text } deriving (Eq, Show)--instance FromJSON TransactionId where- parseJSON = withText "TransactionId" $ return . TransactionId--instance ToJSON TransactionId where- toJSON (TransactionId a) = toJSON a--data TransactionStatus- = TsCompleted- | TsPending- | TsCreated- | TsPartiallyRefunded- | TsDenied- | TsProcessing- | TsReversed- | TsRefunded- | TsFailed- deriving (Eq, Show, Read)------------------------------------------------------- Money-----------------------------------------------------data Money = USD { _usdCents :: Int } deriving (Eq, Show)---- NOTE: Once we add more currencies this instance will violate the--- Monoid laws.-instance Monoid Money where- mempty = USD 0- mappend (USD c1) (USD c2) = USD $ c1 + c2--m2Currency :: Money -> Text-m2Currency (USD _) = "USD"--m2PayPal :: Money -> String-m2PayPal (USD c) =- let s = show (abs c)- a = case length s of- 0 -> "0.00"- 1 -> "0.0" <> s- 2 -> "0." <> s- _ -> tailInsert 2 '.' s- in if c < 0 then '-':a else a-- where- tailInsert :: Int -> a -> [a] -> [a]- tailInsert i x xs =- let (ys, zs) = splitAt i (reverse xs)- in reverse (ys <> pure x <> zs)---$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''PayInfo)-$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . drop 2 } ''PayExecStatus)-$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''Receiver)-$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . camelTo '_' . drop 2 } ''TransactionStatus)--$(makePrisms ''AdaptiveErr)-$(makeLenses ''Client)-$(makeLenses ''Deposit)-$(makePrisms ''Env)-$(makePrisms ''PayExecStatus)-$(makeLenses ''PayInfo)-$(makeLenses ''PayResp)-$(makeLenses ''Receiver)-$(makeLenses ''Withdrawal)-$(makePrisms ''TransactionId)-$(makePrisms ''TransactionStatus)
− src/Web/PayPal/Adaptive/Internal.hs
@@ -1,17 +0,0 @@-module Web.PayPal.Adaptive.Internal- ( ppPost- , ppDecode- , parseErrId- , requestEnvelope- , payExecStatusCompleted- , getPayInfo- , transactionStatusCompleted- , errorFieldNothing- , checkPayKey- , payExecStatusCreated- , noPayInfo- , SerializeDeposit (..)- , SerializeWithdrawal(..)- ) where--import Web.PayPal.Adaptive.Core
tests/Local/Money.hs view
@@ -1,9 +1,9 @@ module Local.Money (unitMoney) where +import PayPal.Adaptive import Test.Framework import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit ((@=?), Assertion)-import Web.PayPal.Adaptive+import Test.HUnit (Assertion, (@=?)) unitMoney :: Test unitMoney = testGroup "money"
tests/Local/Parser.hs view
@@ -1,16 +1,16 @@ module Local.Parser (unitParser) where -import Data.Aeson-import qualified Data.ByteString.Lazy as B-import qualified Data.ByteString.Lazy.Char8 as BSC-import Data.Char (isSpace)-import Data.Default-import Test.Framework (Test, testGroup)-import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit (Assertion, (@=?))-import Utils-import Web.PayPal.Adaptive-import Web.PayPal.Adaptive.Internal+import Data.Aeson+import qualified Data.ByteString.Lazy as B+import qualified Data.ByteString.Lazy.Char8 as BSC+import Data.Char (isSpace)+import PayPal.Adaptive+import qualified PayPal.Adaptive.Deposit as DP+import qualified PayPal.Adaptive.Withdrawal as WD+import Test.Framework (Test, testGroup)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion, (@=?))+import Utils unitParser :: Test unitParser = testGroup "parsers"@@ -40,25 +40,27 @@ serializeWithdrawal :: Assertion serializeWithdrawal = "tests/Local/json/serializeWithdrawal.json" `eqSerialized` a where- a :: SerializeWithdrawal- a = SerializeWithdrawal b "payments@mail.com"+ a :: WD.SerializeWithdrawal+ a = WD.SerializeWithdrawal b "payments@mail.com" - b :: Withdrawal- b = def- { _wdAmount = USD 100- , _wdReceiverEmail = "user@mail.com"+ b :: WD.Withdrawal+ b = WD.Withdrawal+ { WD._amount = USD 100+ , WD._receiverEmail = "user@mail.com" } serializeDeposit :: Assertion serializeDeposit = "tests/Local/json/serializeDeposit.json" `eqSerialized` a where- a :: SerializeDeposit- a = SerializeDeposit b "payments@mail.com"+ a :: DP.SerializeDeposit+ a = DP.SerializeDeposit b "payments@mail.com" - b :: Deposit- b = def- { _dpAmount = USD 100- , _dpSenderEmail = "user@mail.com"+ b :: DP.Deposit+ b = DP.Deposit+ { DP._amount = USD 100+ , DP._senderEmail = "user@mail.com"+ , DP._returnUrl = "https://example.com/"+ , DP._cancelUrl = "https://example.com/cancel" } parseWithdrawalResp :: Assertion@@ -68,7 +70,7 @@ { _prPayError = Nothing , _prPayExecStatus = PeCompleted , _prPayKey = PayKey "AP-21C62741YR509274N"- , _prPayInfo = [p]+ , _prPayInfos = [p] } p = PayInfo { _piReceiver = r@@ -89,7 +91,7 @@ { _prPayError = Nothing , _prPayExecStatus = PeCreated , _prPayKey = PayKey "AP-3NL080742J1818731"- , _prPayInfo = []+ , _prPayInfos = [] } parseLookupResp :: Assertion@@ -99,7 +101,7 @@ { _prPayError = Nothing , _prPayExecStatus = PeCompleted , _prPayKey = PayKey "AP-5XY65045XE4097324"- , _prPayInfo = [p]+ , _prPayInfos = [p] } p = PayInfo { _piReceiver = r
tests/Local/json/serializeWithdrawal.json view
@@ -1,6 +1,6 @@ { "currencyCode": "USD",- "cancelUrl": "https://example.com/cancel",+ "cancelUrl": "https://example.com/", "receiverList": { "receiver": [ {
tests/Remote.hs view
@@ -2,19 +2,21 @@ module Main where -import Control.Applicative-import Control.Monad-import Data.Aeson-import qualified Data.ByteString.Lazy as B-import Data.Default-import Data.HashMap.Strict as H-import Data.Text (Text)-import Test.Framework (Test, defaultMain, testGroup)-import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit hiding (Test)-import Utils-import Web.PayPal.Adaptive-import Web.PayPal.Adaptive.Internal+import Control.Applicative+import Control.Monad+import Data.Aeson+import qualified Data.ByteString.Lazy as B+import Data.HashMap.Strict as H+import Data.Text (Text)+import PayPal.Adaptive+import qualified PayPal.Adaptive.Deposit as DP+import PayPal.Adaptive.Internal+import qualified PayPal.Adaptive.Lookup as LP+import qualified PayPal.Adaptive.Withdrawal as WD+import Test.Framework (Test, defaultMain, testGroup)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit hiding (Test)+import Utils main :: IO () main = defaultMain [integrationRemote]@@ -35,7 +37,7 @@ withdrawalNotEnoughFunds , testCase "deposit succeeds with good arguments"- correctDeposit+ correctDepositBeginning , testCase "deposit with a bad account address does NOT produce an error" depositAccountAddressDNE@@ -71,118 +73,105 @@ incorrectEmailClient :: Text -> Client -> Client incorrectEmailClient badAccountEmail (Client a b c d e _) = Client a b c d e badAccountEmail -withdrawal :: Text -> Withdrawal-withdrawal userAddr = def- { _wdAmount = USD 1- , _wdReceiverEmail = userAddr+withdrawal :: Text -> WD.Withdrawal+withdrawal userAddr = WD.Withdrawal+ { WD._amount = USD 1+ , WD._receiverEmail = userAddr } -deposit :: Text -> Deposit-deposit userAddr = def- { _dpAmount = USD 1- , _dpSenderEmail = userAddr+deposit :: Text -> DP.Deposit+deposit userAddr = DP.Deposit+ { DP._amount = USD 1+ , DP._senderEmail = userAddr+ , DP._returnUrl = "https://example.com/"+ , DP._cancelUrl = "https://example.com/cancel" } -assrt :: Either AdaptiveErr () -> IO ()-assrt (Left e) = assertFailure (show e) >> fail "check failed"-assrt (Right _) = return () - correctWithdrawal :: Assertion correctWithdrawal = do (client, userEmail, _) <- config- payResp <- assertRight =<< toPayPal client (withdrawal userEmail)- (assrt . checkLookup) =<< assertRight =<< toPayPal client (LookupPayKey . _prPayKey $ payResp)- where- checkLookup :: PayResp -> Either AdaptiveErr ()- checkLookup a = do- payExecStatusCompleted a- info <- getPayInfo a- transactionStatusCompleted a info- errorFieldNothing a- checkPayKey a- Right ()+ wdResp <- assertRight =<< WD.req client (withdrawal userEmail)+ lpResp <- assertRight =<< LP.req client (LP.LookupPayKey $ _crPayKey wdResp)+ void $ assertRight (checkComplete lpResp) withdrawalAccountAddressDNE :: Assertion withdrawalAccountAddressDNE = do (client, userEmail, notSandboxEmail) <- config let c = incorrectEmailClient notSandboxEmail client- adaptiveErr <- assertLeft =<< toPayPal c (withdrawal userEmail)+ adaptiveErr <- assertLeft =<< WD.req c (withdrawal userEmail) assertEqual "send error due to a nonexistent account address" AeNoSuchEmail adaptiveErr withdrawalUserAddressDNE :: Assertion withdrawalUserAddressDNE = do (client, _, notSandboxEmail) <- config- payResp <- checkResp =<< assertLeft =<< toPayPal client (withdrawal notSandboxEmail)- (assrt . checkLookup) =<< assertRight =<< toPayPal client (LookupPayKey . _prPayKey $ payResp)+ payResp <- assertExpected =<< assertLeft =<< WD.req client (withdrawal notSandboxEmail)+ assertLookup =<< assertRight =<< LP.req client (LP.LookupPayKey $ _prPayKey payResp) where- checkResp :: AdaptiveErr -> IO PayResp- checkResp (AePending payResp) = do+ assertExpected :: AdaptiveErr -> IO PayResp+ assertExpected (AePending payResp) = do assertEqual- "withdraw to nonexistent user addresses return no PayResps"- 0 (length $ _prPayInfo payResp)+ "withdraw to nonexistent user addresses return no PayInfos"+ 0 (length $ _prPayInfos payResp) return payResp- checkResp _ =+ assertExpected _ = assertFalse "send error due to a nonexistent user address didn't return AePending" >> fail "checkResp failed" - checkLookup :: PayResp -> Either AdaptiveErr ()- checkLookup a = do- payExecStatusCompleted a- info <- getPayInfo a- unless (_piTransactionStatus info == Nothing) $ Left (AeShouldNotHappen a- "a pending withdrawal should not have transaction status in this situation")- errorFieldNothing a- checkPayKey a- Right ()+ assertLookup :: PayResp -> Assertion+ assertLookup a = do+ info <- assertRight $ do+ payExecStatusCompleted a+ errorFieldNothing a+ checkPayKey a+ getPayInfo a+ assertEqual "pending withdrawal transaction status" Nothing (_piTransactionStatus info) withdrawalNotEnoughFunds :: Assertion withdrawalNotEnoughFunds = do (client, userEmail, _) <- config- adaptiveErr <- assertLeft =<< toPayPal client (largeWithdrawal userEmail)+ adaptiveErr <- assertLeft =<< WD.req client (largeWithdrawal userEmail) assertEqual "send error due to not enough funds" (AeErrCodes [520009]) adaptiveErr where- largeWithdrawal :: Text -> Withdrawal- largeWithdrawal userAddr = def- { _wdAmount = USD 1000000- , _wdReceiverEmail = userAddr+ largeWithdrawal :: Text -> WD.Withdrawal+ largeWithdrawal userAddr = WD.Withdrawal+ { WD._amount = USD 100000000+ , WD._receiverEmail = userAddr } -correctDeposit :: Assertion-correctDeposit = do+correctDepositBeginning :: Assertion+correctDepositBeginning = do (client, userEmail, _) <- config- payResp <- assertRight =<< toPayPal client (deposit userEmail)- (assrt . checkLookup) =<< assertRight =<< toPayPal client (LookupPayKey . _prPayKey $ payResp)+ payKey <- assertRight =<< DP.req client (deposit userEmail)+ assertExpected =<< assertRight =<< LP.req client (LP.LookupPayKey payKey) where- checkLookup :: PayResp -> Either AdaptiveErr ()- checkLookup a = do- payExecStatusCreated a- info <- getPayInfo a- unless (_piTransactionStatus info == Nothing) $ Left (AeShouldNotHappen a- "an uncompleted deposit should not have transaction status in this situation")- errorFieldNothing a- checkPayKey a- Right ()+ assertExpected :: PayResp -> Assertion+ assertExpected a = do+ info <- assertRight $ do+ payExecStatusCreated a+ errorFieldNothing a+ checkPayKey a+ getPayInfo a+ assertEqual "pending deposit transaction status" Nothing (_piTransactionStatus info) depositAccountAddressDNE :: Assertion depositAccountAddressDNE = do (client, userEmail, notSandboxEmail) <- config let c = incorrectEmailClient notSandboxEmail client- payResp <- assertRight =<< toPayPal c (deposit userEmail)- (assrt . checkLookup) =<< assertRight =<< toPayPal client (LookupPayKey . _prPayKey $ payResp)+ payKey <- assertRight =<< DP.req c (deposit userEmail)+ assertExpected =<< assertRight =<< LP.req client (LP.LookupPayKey payKey) where- checkLookup :: PayResp -> Either AdaptiveErr ()- checkLookup a = do- payExecStatusCreated a- info <- getPayInfo a- unless (_piTransactionStatus info == Nothing) $ Left (AeShouldNotHappen a- "a deposit should not have transaction status in this situation")- errorFieldNothing a- checkPayKey a- Right ()+ assertExpected :: PayResp -> Assertion+ assertExpected a = do+ info <- assertRight $ do+ payExecStatusCreated a+ errorFieldNothing a+ checkPayKey a+ getPayInfo a+ assertEqual "status for deposit where to address isn't an account" Nothing (_piTransactionStatus info) depositUserAddressDNE :: Assertion depositUserAddressDNE = do (client, _, notSandboxEmail) <- config- adaptiveErr <- assertLeft =<< toPayPal client (deposit notSandboxEmail)+ adaptiveErr <- assertLeft =<< DP.req client (deposit notSandboxEmail) assertEqual "create error due to a nonexistent user address" AeNoSuchEmail adaptiveErr