packages feed

paypal-adaptive-hoops 0.11.0.2 → 0.13.1.0

raw patch · 28 files changed

+709/−767 lines, 28 filesdep +directorydep +filepathdep −unordered-containersdep ~HUnitdep ~test-frameworkdep ~test-framework-hunitPVP ok

version bump matches the API change (PVP)

Dependencies added: directory, filepath

Dependencies removed: unordered-containers

Dependency ranges changed: HUnit, test-framework, test-framework-hunit

API changes (from Hackage documentation)

- PayPal.Adaptive: AeConnectionErr :: 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: 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: Receiver :: Text -> Text -> Text -> Receiver
- 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: _AeConnectionErr :: Prism' AdaptiveErr 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: _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: clEnv :: Lens' Client Env
- PayPal.Adaptive: clSig :: 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 CompletePayResp
- PayPal.Adaptive: data Env
- PayPal.Adaptive: data PayExecStatus
- PayPal.Adaptive: data PayInfo
- PayPal.Adaptive: data PayResp
- PayPal.Adaptive: data Receiver
- PayPal.Adaptive: data TransactionStatus
- 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: prPayExecStatus :: Lens' PayResp PayExecStatus
- PayPal.Adaptive: prPayInfos :: Lens' PayResp [PayInfo]
- PayPal.Adaptive: reAccountId :: Lens' Receiver Text
- PayPal.Adaptive: reAmount :: Lens' Receiver Text
- PayPal.Adaptive: reEmail :: Lens' Receiver Text
- 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: 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.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: LookupTrID :: TransactionId -> LookupPayment TransactionId
- PayPal.Adaptive.Lookup: instance ToJSON (LookupPayment a)
- PayPal.Adaptive.Lookup: req :: Client -> LookupPayment a -> IO (Either AdaptiveErr PayResp)
- PayPal.Adaptive.Withdrawal: _amount :: Withdrawal -> Money
- PayPal.Adaptive.Withdrawal: _receiverEmail :: Withdrawal -> Text
- PayPal.Adaptive.Withdrawal: instance Eq Withdrawal
- PayPal.Adaptive.Withdrawal: instance Show Withdrawal
- PayPal.Adaptive.Withdrawal: instance ToJSON SerializeWithdrawal
- PayPal.Adaptive.Withdrawal: req :: Client -> Withdrawal -> IO (Either AdaptiveErr CompletePayResp)
+ PayPal.Adaptive: AeConnectionError :: SomeException -> AdaptiveError
+ PayPal.Adaptive: AeErrorResponse :: ByteString -> ErrorResponse -> AdaptiveError
+ PayPal.Adaptive: CompletePayResponse :: PayKey -> Receiver -> TransactionId -> TransactionId -> CompletePayResponse
+ PayPal.Adaptive: ErrorCode :: Int -> ErrorCode
+ PayPal.Adaptive: ErrorResponse :: [PayError] -> ErrorResponse
+ PayPal.Adaptive: NcFailed :: Text -> NotComplete
+ PayPal.Adaptive: NcIncomplete :: Text -> NotComplete
+ PayPal.Adaptive: NcShouldNotHappen :: Text -> NotComplete
+ PayPal.Adaptive: PayError :: ErrorCode -> Text -> PayError
+ PayPal.Adaptive: PayResponse :: Maybe Text -> PayStatus -> PayKey -> [PaymentInfo] -> PayResponse
+ PayPal.Adaptive: [_clAccountEmail] :: Client -> Text
+ PayPal.Adaptive: [_clAppId] :: Client -> Text
+ PayPal.Adaptive: [_clEnvironment] :: Client -> Environment
+ PayPal.Adaptive: [_clPassword] :: Client -> Text
+ PayPal.Adaptive: [_clSignature] :: Client -> Text
+ PayPal.Adaptive: [_clUserId] :: Client -> Text
+ PayPal.Adaptive: [_cpPayKey] :: CompletePayResponse -> PayKey
+ PayPal.Adaptive: [_cpReceiver] :: CompletePayResponse -> Receiver
+ PayPal.Adaptive: [_cpSenderTransactionId] :: CompletePayResponse -> TransactionId
+ PayPal.Adaptive: [_cpTransactionId] :: CompletePayResponse -> TransactionId
+ PayPal.Adaptive: [_errorCode] :: PayError -> ErrorCode
+ PayPal.Adaptive: [_errorMessage] :: PayError -> Text
+ PayPal.Adaptive: [_prPayError] :: PayResponse -> Maybe Text
+ PayPal.Adaptive: [_prPayKey] :: PayResponse -> PayKey
+ PayPal.Adaptive: [_prPayStatus] :: PayResponse -> PayStatus
+ PayPal.Adaptive: [_prPaymentInfos] :: PayResponse -> [PaymentInfo]
+ PayPal.Adaptive: [_unErrorCode] :: ErrorCode -> Int
+ PayPal.Adaptive: [_unErrorResponse] :: ErrorResponse -> [PayError]
+ PayPal.Adaptive: [_usdCents] :: Money -> Int
+ PayPal.Adaptive: _AeConnectionError :: Prism' AdaptiveError SomeException
+ PayPal.Adaptive: _AeErrorResponse :: Prism' AdaptiveError (ByteString, ErrorResponse)
+ PayPal.Adaptive: _NcFailed :: Prism' NotComplete Text
+ PayPal.Adaptive: _NcIncomplete :: Prism' NotComplete Text
+ PayPal.Adaptive: _NcShouldNotHappen :: Prism' NotComplete Text
+ PayPal.Adaptive: clEnvironment :: Lens' Client Environment
+ PayPal.Adaptive: clSignature :: Lens' Client Text
+ PayPal.Adaptive: cpPayKey :: Lens' CompletePayResponse PayKey
+ PayPal.Adaptive: cpReceiver :: Lens' CompletePayResponse Receiver
+ PayPal.Adaptive: cpSenderTransactionId :: Lens' CompletePayResponse TransactionId
+ PayPal.Adaptive: cpTransactionId :: Lens' CompletePayResponse TransactionId
+ PayPal.Adaptive: data AdaptiveError
+ PayPal.Adaptive: data CompletePayResponse
+ PayPal.Adaptive: data Environment
+ PayPal.Adaptive: data ErrorResponse
+ PayPal.Adaptive: data NotComplete
+ PayPal.Adaptive: data PayError
+ PayPal.Adaptive: data PayResponse
+ PayPal.Adaptive: errorCode :: Lens' PayError ErrorCode
+ PayPal.Adaptive: errorMessage :: Lens' PayError Text
+ PayPal.Adaptive: getPaymentInfo :: PayResponse -> Either NotComplete PaymentInfo
+ PayPal.Adaptive: invalidCredentials :: ErrorCode
+ PayPal.Adaptive: newtype ErrorCode
+ PayPal.Adaptive: noSuchEmail :: ErrorCode
+ PayPal.Adaptive: prPayStatus :: Lens' PayResponse PayStatus
+ PayPal.Adaptive: prPaymentInfos :: Lens' PayResponse [PaymentInfo]
+ PayPal.Adaptive: unErrorCode :: Iso' ErrorCode Int
+ PayPal.Adaptive: unErrorResponse :: Iso' ErrorResponse [PayError]
+ PayPal.Adaptive.Core.Internal: ppDecode :: ByteString -> Either AdaptiveError PayResponse
+ PayPal.Adaptive.Core.Internal: ppPost :: (ToJSON a) => Client -> Text -> a -> IO (Either AdaptiveError ByteString)
+ PayPal.Adaptive.Core.Internal: requestEnvelope :: Value
+ 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: instance Data.Aeson.Types.Class.ToJSON PayPal.Adaptive.Deposit.SerializeDeposit
+ PayPal.Adaptive.Deposit: instance GHC.Classes.Eq PayPal.Adaptive.Deposit.Deposit
+ PayPal.Adaptive.Deposit: instance GHC.Show.Show PayPal.Adaptive.Deposit.Deposit
+ PayPal.Adaptive.Deposit: request :: Client -> Deposit -> IO (Either AdaptiveError (ByteString, PayResponse))
+ PayPal.Adaptive.Lookup: LookupTransactionID :: TransactionId -> LookupPayment
+ PayPal.Adaptive.Lookup: instance Data.Aeson.Types.Class.ToJSON PayPal.Adaptive.Lookup.LookupPayment
+ PayPal.Adaptive.Lookup: instance GHC.Classes.Eq PayPal.Adaptive.Lookup.LookupPayment
+ PayPal.Adaptive.Lookup: instance GHC.Show.Show PayPal.Adaptive.Lookup.LookupPayment
+ PayPal.Adaptive.Lookup: request :: Client -> LookupPayment -> IO (Either AdaptiveError (ByteString, PayResponse))
+ PayPal.Adaptive.Withdrawal: [_amount] :: Withdrawal -> Money
+ PayPal.Adaptive.Withdrawal: [_receiverEmail] :: Withdrawal -> Text
+ PayPal.Adaptive.Withdrawal: instance Data.Aeson.Types.Class.ToJSON PayPal.Adaptive.Withdrawal.SerializeWithdrawal
+ PayPal.Adaptive.Withdrawal: instance GHC.Classes.Eq PayPal.Adaptive.Withdrawal.Withdrawal
+ PayPal.Adaptive.Withdrawal: instance GHC.Show.Show PayPal.Adaptive.Withdrawal.Withdrawal
+ PayPal.Adaptive.Withdrawal: request :: Client -> Withdrawal -> IO (Either AdaptiveError (ByteString, PayResponse))
- PayPal.Adaptive: AeDecodeFailed :: ByteString -> Text -> AdaptiveErr
+ PayPal.Adaptive: AeDecodeFailed :: ByteString -> Text -> AdaptiveError
- PayPal.Adaptive: Client :: Text -> Text -> Env -> Text -> Text -> Text -> Client
+ PayPal.Adaptive: Client :: Text -> Text -> Environment -> Text -> Text -> Text -> Client
- PayPal.Adaptive: Production :: Env
+ PayPal.Adaptive: Production :: Environment
- PayPal.Adaptive: Sandbox :: Env
+ PayPal.Adaptive: Sandbox :: Environment
- PayPal.Adaptive: _AeDecodeFailed :: Prism' AdaptiveErr (ByteString, Text)
+ PayPal.Adaptive: _AeDecodeFailed :: Prism' AdaptiveError (ByteString, Text)
- PayPal.Adaptive: _Production :: Prism' Env ()
+ PayPal.Adaptive: _Production :: Prism' Environment ()
- PayPal.Adaptive: _Sandbox :: Prism' Env ()
+ PayPal.Adaptive: _Sandbox :: Prism' Environment ()
- PayPal.Adaptive: checkComplete :: PayResp -> Either AdaptiveErr CompletePayResp
+ PayPal.Adaptive: checkComplete :: PayResponse -> Either NotComplete CompletePayResponse
- PayPal.Adaptive: prPayError :: Lens' PayResp (Maybe Text)
+ PayPal.Adaptive: prPayError :: Lens' PayResponse (Maybe Text)
- PayPal.Adaptive: prPayKey :: Lens' PayResp PayKey
+ PayPal.Adaptive: prPayKey :: Lens' PayResponse PayKey
- PayPal.Adaptive.Lookup: LookupPayKey :: PayKey -> LookupPayment PayKey
+ PayPal.Adaptive.Lookup: LookupPayKey :: PayKey -> LookupPayment
- PayPal.Adaptive.Lookup: data LookupPayment a
+ PayPal.Adaptive.Lookup: data LookupPayment

Files

Example.hs view
@@ -3,9 +3,9 @@ module Main where  import           Prelude                    hiding (getLine, lookup, putStr, putStrLn)-import           Control.Applicative import           Data.Text                  (Text) import           Data.Text.IO+ import qualified PayPal.Adaptive            as PP import qualified PayPal.Adaptive.Deposit    as DP import qualified PayPal.Adaptive.Lookup     as LP@@ -29,39 +29,57 @@  main :: IO () main = do++  exampleWithdrawal++  payKey <- startExampleDeposit++  putStrLn "-- Go here and use the other account's password to approve the payment:"+  putStrLn (DP.approvalUrl client payKey)+  putStrLn ""+  putStrLn "-- Once that's done press enter ..."+  _ <- getLine++  finishExampleDeposit payKey+++exampleWithdrawal :: IO ()+exampleWithdrawal = do   putStrLn "-- Sending money to another PayPal account."-  resp <- WD.req client withdrawal+  resp <- WD.request client withdrawal   case resp of-    Left e               -> print e+    Left e               -> error (show e)     Right withdrawalResp -> do       print withdrawalResp       putStrLn "" -      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 ""-          putStrLn "-- Once that's done press enter ..."-          _ <- getLine+startExampleDeposit :: IO PP.PayKey+startExampleDeposit = do+  putStrLn "-- Creating a payment from another account to us."+  resp <- DP.request client deposit+  putStrLn ""+  case resp of+    Left e            -> error (show e)+    Right (_,payResp) -> return (PP._prPayKey payResp) -          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+finishExampleDeposit :: PP.PayKey -> IO ()+finishExampleDeposit payKey = do+  putStrLn "-- Now looking up the payment on PayPal to make sure it succeeded."+  resp <- LP.request client (LP.LookupPayKey payKey)+  case resp of+    Left e            -> error (show e)+    Right (_,payResp) ->+      case PP.checkComplete payResp of+        Left e2               -> error (show e2)+        Right completePayment -> print completePayment  client :: PP.Client client = PP.Client   { PP._clAppId        = "APP-80W284485P519543T" -- Currently the ID for all sandbox apps.   , PP._clUserId       = userId-  , PP._clEnv          = PP.Sandbox+  , PP._clEnvironment  = PP.Sandbox   , PP._clPassword     = password-  , PP._clSig          = sig+  , PP._clSignature    = sig   , PP._clAccountEmail = accountEmail   } 
README.md view
@@ -1,6 +1,6 @@ # Intro -A high-level Haskell client for a limited part of PayPal's [Adaptive Payments API](https://developer.paypal.com/docs/classic/products/adaptive-payments/).+A Haskell client for a limited part of PayPal's [Adaptive Payments API](https://developer.paypal.com/docs/classic/products/adaptive-payments/).  Covers only sending, receiving, and looking up payments. Only supports handling a single payment per request, and doesn't cover sending payments that involve more than two parties. 
paypal-adaptive-hoops.cabal view
@@ -1,5 +1,5 @@ name:                 paypal-adaptive-hoops-version:              0.11.0.2+version:              0.13.1.0 author:               Ian Grant Jeffries maintainer:           ian@housejeffries.com category:             Web@@ -15,16 +15,26 @@ library   hs-source-dirs:     src   exposed-modules:    PayPal.Adaptive+                      PayPal.Adaptive.Core.Internal                       PayPal.Adaptive.Deposit                       PayPal.Adaptive.Lookup                       PayPal.Adaptive.Withdrawal-                      PayPal.Adaptive.Internal-  other-modules:      PayPal.Adaptive.Core+  other-modules:      Import+                      PayPal.Adaptive.Core.Client+                      PayPal.Adaptive.Core.Error+                      PayPal.Adaptive.Core.Money+                      PayPal.Adaptive.Core.PayResponse+                      PayPal.Adaptive.Core.PayResponse.PaymentInfo+                      PayPal.Adaptive.Core.PayResponse.PaymentInfo.Receiver+                      PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionId+                      PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionStatus+                      PayPal.Adaptive.Core.PayResponse.PayKey+                      PayPal.Adaptive.Core.PayResponse.PayStatus+                      PayPal.Adaptive.Core.Processing   default-language:   Haskell2010   ghc-options:        -Wall   default-extensions: OverloadedStrings-  other-extensions:   GADTs-                    , MultiWayIf+  other-extensions:   MultiWayIf                     , TemplateHaskell    build-depends:      aeson        >= 0.8   && < 0.10@@ -44,9 +54,7 @@   type:               exitcode-stdio-1.0   hs-source-dirs:     tests   main-is:            Local.hs-  other-modules:      Local.Money-                      Local.Parser-                      Utils+  other-modules:      Import   default-language:   Haskell2010   ghc-options:        -Wall   default-extensions: OverloadedStrings@@ -56,16 +64,17 @@                     , bytestring                     , paypal-adaptive-hoops                     , text+                    , directory            >= 1.2 && < 1.3+                    , filepath             >= 1.4 && < 1.5                     , HUnit                >= 1.2 && < 1.3                     , test-framework       >= 0.8 && < 0.9                     , test-framework-hunit >= 0.3 && < 0.4-                    , unordered-containers >= 0.2 && < 0.3  test-suite remote   type:               exitcode-stdio-1.0   hs-source-dirs:     tests   main-is:            Remote.hs-  other-modules:      Utils+  other-modules:      Import   default-language:   Haskell2010   ghc-options:        -Wall   default-extensions: OverloadedStrings@@ -75,10 +84,9 @@                     , bytestring                     , paypal-adaptive-hoops                     , text-                    , HUnit                >= 1.2 && < 1.3-                    , test-framework       >= 0.8 && < 0.9-                    , test-framework-hunit >= 0.3 && < 0.4-                    , unordered-containers >= 0.2 && < 0.3+                    , HUnit+                    , test-framework+                    , test-framework-hunit  executable example   main-is:            Example.hs
+ src/Import.hs view
@@ -0,0 +1,9 @@++module Import (module Import) where++import           Control.Lens     as Import hiding ((.=))+import           Data.Aeson       as Import+import           Data.Monoid      as Import+import           Data.Text        as Import (Text)+import           Data.Traversable as Import+import           Data.Vector      as Import (Vector)
src/PayPal/Adaptive.hs view
@@ -1,44 +1,23 @@-{-# LANGUAGE TemplateHaskell #-}  module PayPal.Adaptive (-   -- * Client-    Client           (..)-  , Env              (..)+    module PayPal.Adaptive.Core.Client -  -- * Errors-  , AdaptiveErr      (..)+  -- * Error+  , module PayPal.Adaptive.Core.Error -  -- * Payment Responses-  , PayResp          (..)-  , CompletePayResp  (..)-  , checkComplete-  , PayExecStatus    (..)-  , PayKey           (..)-  , PayInfo          (..)-  , Receiver         (..)-  , TransactionId    (..)-  , TransactionStatus(..)+  -- * PayResponse+  , module PayPal.Adaptive.Core.PayResponse -  -- * Money-  , Money            (..)-  , m2Currency-  , m2PayPal+  -- * Processing+  , module PayPal.Adaptive.Core.Processing -  , module PayPal.Adaptive+  -- * Money+  , module PayPal.Adaptive.Core.Money   ) 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)+import           PayPal.Adaptive.Core.Client+import           PayPal.Adaptive.Core.Error+import           PayPal.Adaptive.Core.Money+import           PayPal.Adaptive.Core.PayResponse+import           PayPal.Adaptive.Core.Processing
− src/PayPal/Adaptive/Core.hs
@@ -1,367 +0,0 @@-{-# 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/Core/Client.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.Client where++import           Data.Aeson.TH+import           Data.Aeson.Types (camelTo)++import           Import++data Client = Client+  { _clAppId        :: Text+  , _clUserId       :: Text+  , _clEnvironment  :: Environment+  , _clPassword     :: Text+  , _clSignature    :: Text+  , _clAccountEmail :: 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.+  } deriving (Eq, Show)++data Environment = Sandbox | Production deriving (Eq, Show, Read)++$(deriveJSON defaultOptions { fieldLabelModifier = camelTo '_' . drop 3 } ''Client)+$(deriveJSON defaultOptions { constructorTagModifier = id } ''Environment)+$(makeLenses ''Client)+$(makePrisms ''Environment)
+ src/PayPal/Adaptive/Core/Error.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.Error where++import           Control.Exception+import           Data.ByteString.Lazy (ByteString)+import           Data.Text.Read+import qualified Data.Vector          as V++import           Import++-- | 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 AdaptiveError+  = AeConnectionError SomeException+  | AeDecodeFailed    ByteString Text+  | AeErrorResponse   ByteString ErrorResponse+  deriving Show -- Eq can't be automatically derived because there's no Eq instance for SomeException.++data ErrorResponse = ErrorResponse { _unErrorResponse :: [PayError] } deriving (Eq, Show)++-- Error responses are what's returned if, e.g., your password is incorrect.+instance FromJSON ErrorResponse where+  parseJSON = withObject "ErrorResponse" $ \o ->+    ErrorResponse . V.toList <$> (traverse parseJSON =<< o .: "error")++data PayError = PayError { _errorCode    :: ErrorCode+                         , _errorMessage :: Text+                         } deriving (Eq, Show)++instance FromJSON PayError where+  parseJSON = withObject "PayError" $ \o -> PayError <$> o .: "errorId" <*> o .: "message"++newtype ErrorCode = ErrorCode { _unErrorCode :: Int } deriving Eq++instance Show ErrorCode where+  show n | n == invalidCredentials = "Error code " <> show (_unErrorCode n) <> " - invalid credentials"+         | n == noSuchEmail        = "Error code " <> show (_unErrorCode n) <> " - no such email"+         | otherwise               = "Error code " <> show (_unErrorCode n)++instance FromJSON ErrorCode where+  parseJSON = withText "ErrorCode" $ \t ->+    case decimal t of+      Left  _     -> fail "Could not parse error response code to Int"+      Right (b,_) -> return (ErrorCode b)++invalidCredentials :: ErrorCode+invalidCredentials = ErrorCode 520003++-- | Raised when the Deposit sender email or the Withdrawal account email doesn't exist.+noSuchEmail :: ErrorCode+noSuchEmail = ErrorCode 589039++$(makeLenses ''ErrorCode)+$(makeLenses ''ErrorResponse)+$(makeLenses ''PayError)+$(makePrisms ''AdaptiveError)
+ src/PayPal/Adaptive/Core/Internal.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE MultiWayIf #-}++module PayPal.Adaptive.Core.Internal where++import           Control.Exception+import           Data.ByteString.Lazy             (ByteString)+import qualified Data.Text                        as T+import           Data.Text.Encoding+import           Network.Wreq++import           Import+import           PayPal.Adaptive.Core.Client+import           PayPal.Adaptive.Core.Error+import           PayPal.Adaptive.Core.PayResponse++ppPost :: (ToJSON a) => Client -> Text -> a -> IO (Either AdaptiveError ByteString)+ppPost c endpoint p = catch (return . Right . (^.responseBody) =<< runPost) handler+  where+    runPost :: IO (Response ByteString)+    runPost = postWith opts (clBaseUrl <> T.unpack endpoint) (toJSON p)++    clBaseUrl :: String+    clBaseUrl =+      case _clEnvironment c of+        Sandbox    -> "https://svcs.sandbox.paypal.com/AdaptivePayments/"+        Production -> "https://svcs.paypal.com/AdaptivePayments/"++    handler :: SomeException -> IO (Either AdaptiveError ByteString)+    handler e = return . Left . AeConnectionError $ 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 (_clSignature c) ]+      & header "X-PAYPAL-APPLICATION-ID"       .~ [ encodeUtf8 (_clAppId     c) ]+      & header "X-PAYPAL-REQUEST-DATA-FORMAT"  .~ [ "JSON" ]+      & header "X-PAYPAL-RESPONSE-DATA-FORMAT" .~ [ "JSON" ]++ppDecode :: ByteString -> Either AdaptiveError PayResponse+ppDecode b =+  case eitherDecode b of+    Right d -> Right d+    Left  e ->+      case decode b of -- If the response isn't a PayResponse, it might be a PayPal error message.+        Just res@(ErrorResponse _) -> Left $ AeErrorResponse b res+        _                          -> Left $ AeDecodeFailed b (T.pack e)++-- | "en_US" is hardcoded because it's the only supported error language.+requestEnvelope :: Value+requestEnvelope = object ["errorLanguage" .= ("en_US" :: Text)]
+ src/PayPal/Adaptive/Core/Money.hs view
@@ -0,0 +1,31 @@++module PayPal.Adaptive.Core.Money where++import           Import++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)
+ src/PayPal/Adaptive/Core/PayResponse.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse+  ( module Export+  , module PayPal.Adaptive.Core.PayResponse+  ) where++import           Data.Maybe                                   (isJust)++import           Import+import           PayPal.Adaptive.Core.PayResponse.PayKey      as Export+import           PayPal.Adaptive.Core.PayResponse.PaymentInfo as Export+import           PayPal.Adaptive.Core.PayResponse.PayStatus   as Export++-- | Used for forgiving attempts to parse PayPal responses. For instance,+-- this library never expects more than one PaymentInfo. 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 'PaymentInfo'+-- is returned.+--+-- <https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/>+data PayResponse = PayResponse+  { _prPayError      :: Maybe Text+  -- ^ payErrorList (string) -- Information about why a payment failed.+  , _prPayStatus     :: PayStatus+  , _prPayKey        :: PayKey+  , _prPaymentInfos  :: [PaymentInfo]+  } deriving (Eq, Show)++instance FromJSON PayResponse 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 $ PayResponse e status k mempty+      Just v  -> do+        infos <- v .:? "paymentInfo" .!= mempty+        return $ PayResponse e status k infos++$(makeLenses ''PayResponse)
+ src/PayPal/Adaptive/Core/PayResponse/PayKey.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PayKey where++import           Import++-- | 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++$(makeLenses ''PayKey)
+ src/PayPal/Adaptive/Core/PayResponse/PayStatus.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PayStatus where++import           Data.Aeson.TH+import           Data.Char (toUpper)++import           Import++-- | This refers to the processing of this request. To make sure a payment+-- has actually gone through, check 'TransactionStatus' as well.+--+-- PayPal returns this using the JSON object key "paymentExecStatus"+-- in responses to Withdrawal and Deposit and "status" in responses to+-- LookupPayment.+--+-- https://developer.paypal.com/docs/classic/api/adaptive-payments/PaymentDetails_API_Operation/+data PayStatus+  = PeCreated+  -- ^ CREATED – The payment request was received; funds will be transferred once the payment is approved+  | PeCompleted+  -- ^ COMPLETED – The payment was successful+  | PePending+  -- ^ PENDING – The payment is awaiting processing+  | PeProcessing+  -- ^ PROCESSING – The payment is in progress+  | PeIncomplete+  -- ^ INCOMPLETE – Some transfers succeeded and some failed for a parallel payment or,+  -- for a delayed chained payment, secondary receivers have not been paid+  | PeExpired+  -- ^ EXPIRED+  --+  -- NOTE: This occurs in practice, but isn't in the documentation.+  | PeError+  -- ^ ERROR – The payment failed and all attempted transfers failed or all completed transfers+  -- were successfully reversed+  | PeReversalError+  -- ^ REVERSALERROR – One or more transfers failed when attempting to reverse a payment+  deriving (Eq, Show, Read)++$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . drop 2 } ''PayStatus)+$(makePrisms ''PayStatus)
+ src/PayPal/Adaptive/Core/PayResponse/PaymentInfo.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PaymentInfo+  ( module Export+  , module PayPal.Adaptive.Core.PayResponse.PaymentInfo+  ) where++import           Data.Aeson.TH+import           Data.Char                                                      (toLower)++import           Import+import           PayPal.Adaptive.Core.PayResponse.PaymentInfo.Receiver          as Export+import           PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionId     as Export+import           PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionStatus as Export++-- | 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.+--+--+-- https://developer.paypal.com/docs/classic/api/adaptive-payments/PaymentDetails_API_Operation/+--+-- Represents the payment attempt made to a receiver of a PayRequest. If the execution of the payment has not yet completed, there are no transaction details returned.+data PaymentInfo = PaymentInfo+  { _piReceiver                :: Receiver+  , _piTransactionId           :: Maybe TransactionId+  , _piTransactionStatus       :: Maybe TransactionStatus+  , _piSenderTransactionId     :: Maybe TransactionId+  , _piSenderTransactionStatus :: Maybe TransactionStatus+  } deriving (Eq, Show)++$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''PaymentInfo)+$(makeLenses ''PaymentInfo)
+ src/PayPal/Adaptive/Core/PayResponse/PaymentInfo/Receiver.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PaymentInfo.Receiver where++import           Data.Aeson.TH+import           Data.Char (toLower)++import           Import++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)++$(deriveFromJSON defaultOptions { fieldLabelModifier = (\(x:xs) -> toLower x:xs) . drop 3 } ''Receiver)+$(makeLenses ''Receiver)
+ src/PayPal/Adaptive/Core/PayResponse/PaymentInfo/TransactionId.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionId where++import           Import++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++$(makeLenses ''TransactionId)
+ src/PayPal/Adaptive/Core/PayResponse/PaymentInfo/TransactionStatus.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.PayResponse.PaymentInfo.TransactionStatus where++import           Data.Aeson.TH+import           Data.Aeson.Types (camelTo)+import           Data.Char (toUpper)++import           Import++-- | PaymentInfos have both a transactionStatus and senderTransactionStatus field.+-- As far as I can tell the same data type can represent both of them.+-- The comments for the data constructors come from senderTransactionStatus.+--+-- https://developer.paypal.com/docs/classic/api/adaptive-payments/Pay_API_Operation/+data TransactionStatus+  = TsCreated+  -- ^ CREATED – The payment request was received; funds will be transferred once approval is received+  | TsPending+  -- ^ PENDING – The transaction is awaiting further processing+  | TsProcessing+  -- ^ PROCESSING – The transaction is in progress+  | TsCompleted+  -- ^ COMPLETED – The sender's transaction has completed+  | TsDenied+  -- ^ DENIED – The transaction was rejected by the receiver+  | TsFailed+  -- ^ FAILED – The payment failed+  | TsRefunded+  -- ^ REFUNDED – The payment was refunded+  | TsPartiallyRefunded+  -- ^ PARTIALLY_REFUNDED – Transaction was partially refunded+  | TsReversed+  -- ^ REVERSED – The payment was returned to the sender+  deriving (Eq, Show, Read)++$(deriveFromJSON defaultOptions { constructorTagModifier = map toUpper . camelTo '_' . drop 2 } ''TransactionStatus)+$(makePrisms ''TransactionStatus)
+ src/PayPal/Adaptive/Core/Processing.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE TemplateHaskell #-}++module PayPal.Adaptive.Core.Processing where++import qualified Data.Text as T++import           Import+import           PayPal.Adaptive.Core.PayResponse++-- | A version of 'PayResponse' for completed transactions.+--+-- Status fields are omitted since they should be complete.+--+-- Has no separate field for a 'PaymentInfo' 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 CompletePayResponse = CompletePayResponse+  { _cpPayKey              :: PayKey+  , _cpReceiver            :: Receiver+  , _cpTransactionId       :: TransactionId+  , _cpSenderTransactionId :: TransactionId+  } deriving (Eq, Show)++-- | A high level data type that tries to summarize the status+-- of a payment in simple terms (meant to be used in an 'Either',+-- e.g. 'Either' 'NotComplete' 'CompletePayResponse').+data NotComplete+  = NcIncomplete      Text+  | NcFailed          Text+  | NcShouldNotHappen Text+  deriving (Eq, Show)++checkComplete :: PayResponse -> Either NotComplete CompletePayResponse+checkComplete pr = do+  payStatusCompleted pr+  info <- getPaymentInfo pr+  sid <- transactionIdJust (_piSenderTransactionId info)+  transactionStatusCompleted (_piTransactionStatus info)+  transactionStatusCompleted (_piSenderTransactionStatus info)+  tid <- transactionIdJust (_piTransactionId info)+  errorFieldNothing pr+  return $ CompletePayResponse (_prPayKey pr) (_piReceiver info) sid tid+  where+    transactionIdJust :: Maybe TransactionId -> Either NotComplete TransactionId+    transactionIdJust Nothing = Left $ NcShouldNotHappen "expected a Just for Maybe TransactionId"+    transactionIdJust (Just tid) = Right tid++    errorFieldNothing :: PayResponse -> Either NotComplete ()+    errorFieldNothing a+      | _prPayError a == Nothing = Right ()+      | otherwise                =+        Left $ NcShouldNotHappen "PayResponse error field is Just in otherwise correct payment"++    transactionStatusCompleted :: Maybe TransactionStatus -> Either NotComplete ()+    transactionStatusCompleted Nothing       = Left (NcShouldNotHappen "TransactionStatus is Nothing")+    transactionStatusCompleted (Just status) =+      let msg = "TransactionStatus " <> T.pack (show status)+      in case status of+        TsCreated           -> Left (NcIncomplete msg)+        TsPending           -> Left (NcIncomplete msg)+        TsProcessing        -> Left (NcIncomplete msg)+        TsCompleted         -> Right ()+        TsDenied            -> Left (NcFailed msg)+        TsFailed            -> Left (NcFailed msg)+        TsRefunded          -> Left (NcShouldNotHappen msg)+        TsPartiallyRefunded -> Left (NcShouldNotHappen msg)+        TsReversed          -> Left (NcShouldNotHappen msg)++    payStatusCompleted :: PayResponse -> Either NotComplete ()+    payStatusCompleted a =+      let status = _prPayStatus a+          msg = "_prPayStatus: " <> T.pack (show status)+      in case status of+        PeCreated       -> Left (NcIncomplete msg)+        PeCompleted     -> Right ()+        PePending       -> Left (NcIncomplete msg)+        PeProcessing    -> Left (NcIncomplete msg)+        PeIncomplete    -> Left (NcShouldNotHappen msg) -- PeIncomplete has to do with delayed or chained+                                                        -- payments, which we don't support.+        PeExpired       -> Left (NcFailed msg)+        PeError         -> Left (NcFailed msg)+        PeReversalError -> Left (NcShouldNotHappen msg)++getPaymentInfo :: PayResponse -> Either NotComplete PaymentInfo+getPaymentInfo a =+  case _prPaymentInfos a of+    []     -> Left $ NcIncomplete "no PaymentInfo"+    [info] -> Right info+    _      -> Left $ NcShouldNotHappen "more than one PaymentInfo"++$(makeLenses ''CompletePayResponse)+$(makePrisms ''NotComplete)
src/PayPal/Adaptive/Deposit.hs view
@@ -17,12 +17,15 @@  module PayPal.Adaptive.Deposit where -import Control.Lens         hiding ((.=))-import Data.Aeson-import Data.Monoid-import Data.Text            (Text)-import PayPal.Adaptive.Core+import           Data.ByteString.Lazy             (ByteString) +import           Import+import           PayPal.Adaptive.Core.Client+import           PayPal.Adaptive.Core.Error+import           PayPal.Adaptive.Core.Internal+import           PayPal.Adaptive.Core.Money+import           PayPal.Adaptive.Core.PayResponse+ data Deposit = Deposit   { _amount      :: Money   , _senderEmail :: Text@@ -36,40 +39,32 @@ 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-          ]-        ]]+    , "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+request :: Client -> Deposit -> IO (Either AdaptiveError (ByteString,PayResponse) )+request 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)+  case resp of+    Left e    -> return $ Left e+    Right bts -> return $ (,) bts <$> ppDecode bts  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+  case _clEnvironment 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
@@ -1,16 +0,0 @@-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
@@ -1,21 +1,21 @@-{-# 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+import           Data.ByteString.Lazy             (ByteString) -data LookupPayment a where-  LookupTrID   :: TransactionId -> LookupPayment TransactionId-  LookupPayKey :: PayKey        -> LookupPayment PayKey+import           Import+import           PayPal.Adaptive.Core.Client+import           PayPal.Adaptive.Core.Error+import           PayPal.Adaptive.Core.Internal+import           PayPal.Adaptive.Core.PayResponse -instance ToJSON (LookupPayment a) where-  toJSON (LookupTrID (TransactionId p)) =+data LookupPayment = LookupTransactionID TransactionId | LookupPayKey PayKey deriving (Eq, Show)++instance ToJSON LookupPayment where+  toJSON (LookupTransactionID (TransactionId p)) =     object [ "transactionId"   .= p            , "requestEnvelope" .= requestEnvelope            ]@@ -24,5 +24,9 @@            , "requestEnvelope" .= requestEnvelope            ] -req :: Client -> LookupPayment a -> IO (Either AdaptiveErr PayResp)-req c l = (>>= ppDecode) <$> ppPost c "PaymentDetails" l+request :: Client -> LookupPayment -> IO (Either AdaptiveError (ByteString,PayResponse) )+request c l = do+  resp <- ppPost c "PaymentDetails" l+  case resp of+    Left e    -> return $ Left e+    Right bts -> return $ (,) bts <$> ppDecode bts
src/PayPal/Adaptive/Withdrawal.hs view
@@ -13,11 +13,15 @@  module PayPal.Adaptive.Withdrawal where -import Control.Lens         hiding ((.=))-import Data.Aeson-import Data.Text            (Text)-import PayPal.Adaptive.Core+import           Data.ByteString.Lazy             (ByteString) +import           Import+import           PayPal.Adaptive.Core.Client+import           PayPal.Adaptive.Core.Error+import           PayPal.Adaptive.Core.Internal+import           PayPal.Adaptive.Core.Money+import           PayPal.Adaptive.Core.PayResponse+ data Withdrawal = Withdrawal   { _amount        :: Money   , _receiverEmail :: Text@@ -29,7 +33,7 @@ instance ToJSON SerializeWithdrawal where   toJSON (SerializeWithdrawal p accountEmail) = object     [ "actionType"      .= ("PAY" :: Text)-    , "currencyCode"    .= (m2Currency . _amount) p+    , "currencyCode"    .= m2Currency (_amount p)     , "receiverList"    .= object                              [ "receiver" .=                                [ object@@ -46,10 +50,11 @@  -- | 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+request :: Client -> Withdrawal -> IO (Either AdaptiveError (ByteString,PayResponse) )+request c w = do   resp <- ppPost c "Pay" $ SerializeWithdrawal w (_clAccountEmail c)-  return $ checkComplete =<< ppDecode =<< resp+  case resp of+    Left e    -> return $ Left e+    Right bts -> return $ (,) bts <$> ppDecode bts --- * Lenses $(makeLenses ''Withdrawal)
+ tests/Import.hs view
@@ -0,0 +1,29 @@++module Import (module Import) where++import           Control.Applicative as Import+import           Data.Aeson          as Import+import           Data.Foldable       as Import+import           Data.Monoid         as Import+import           Data.Text           as Import (Text)+import           Data.Traversable    as Import++import           Test.HUnit++assertJust :: String -> Maybe a -> IO a+assertJust s a =+  case a of+    Nothing -> assertFailure s >> fail "assertJust failed"+    Just b  -> return b++assertRight :: (Show a) => Either a b -> IO b+assertRight a =+  case a of+    Left e  -> assertFailure (show e) >> fail "assertRight failed"+    Right b -> return b++assertLeft :: (Show b) => Either a b -> IO a+assertLeft a =+  case a of+    Left e  -> return e+    Right b -> assertFailure (show b) >> fail "assertLeft failed"
tests/Local.hs view
@@ -1,11 +1,13 @@+ module Main where +import Local.BulkParser import Local.Money import Local.Parser import Test.Framework (Test, defaultMain)  tests :: [Test]-tests = [unitMoney, unitParser]+tests = [unitMoney, unitParser, bulkParser]  main :: IO () main = defaultMain tests
− tests/Local/Money.hs
@@ -1,46 +0,0 @@-module Local.Money (unitMoney) where--import PayPal.Adaptive-import Test.Framework-import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit                     (Assertion, (@=?))--unitMoney :: Test-unitMoney = testGroup "money"-  [ testCase "handle large negative amount correctly"        largeNegative-  , testCase "handle negative amount correctly"              negative-  , testCase "handle 0 correctly"                            zero-  , testCase "handle 1 correctly"                            oneDigit-  , testCase "handle 10 correctly"                           twoDigitsTrailingZero-  , testCase "show both decimals even if both are zero"      threeDigitsTrailingZeros-  , testCase "show both decimals even if the second is zero" threeDigitsTrailingZero-  , testCase "handles large amount correctly"                largeAmount-  ]--testM2P :: Int -> String -> Assertion-testM2P input correct =-  correct @=? m2PayPal USD { _usdCents = input }--largeNegative            :: Assertion-largeNegative            = testM2P (-98783) "-987.83"--negative                 :: Assertion-negative                 = testM2P (-1) "-0.01"--zero                     :: Assertion-zero                     = testM2P 0 "0.00"--oneDigit                 :: Assertion-oneDigit                 = testM2P 1 "0.01"--twoDigitsTrailingZero    :: Assertion-twoDigitsTrailingZero    = testM2P 10 "0.10"--threeDigitsTrailingZeros :: Assertion-threeDigitsTrailingZeros = testM2P 100 "1.00"--threeDigitsTrailingZero  :: Assertion-threeDigitsTrailingZero  = testM2P 110 "1.10"--largeAmount              :: Assertion-largeAmount              = testM2P 792874 "7928.74"
− tests/Local/Parser.hs
@@ -1,121 +0,0 @@-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           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"-  [ testCase "correctly encodes a Withdrawal"             serializeWithdrawal-  , testCase "correctly encodes a Deposit"                serializeDeposit-  , testCase "correctly decodes a Withdrawal response"    parseWithdrawalResp-  , testCase "correctly decodes a Deposit response"       parseDepositResp-  , testCase "correctly decodes a LookupPayment response" parseLookupResp-  , testCase "correctly decodes an error code response"   parseErrResp-  ]--eqSerialized :: (ToJSON a) => FilePath -> a -> Assertion-eqSerialized x y = do-  expected <- B.readFile x-  let actual = encode y-  filterBS expected @=? filterBS actual-  where-    filterBS :: BSC.ByteString -> BSC.ByteString-    filterBS = BSC.filter (not . isSpace)--eqParsed :: (Show a, Eq a, FromJSON a) => a -> FilePath -> Assertion-eqParsed expected x = do-  b <- B.readFile x-  actual <- assertRight (eitherDecode b)-  expected @=? actual--serializeWithdrawal :: Assertion-serializeWithdrawal = "tests/Local/json/serializeWithdrawal.json" `eqSerialized` a-  where-    a :: WD.SerializeWithdrawal-    a = WD.SerializeWithdrawal b "payments@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 :: DP.SerializeDeposit-    a = DP.SerializeDeposit b "payments@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-parseWithdrawalResp = expected `eqParsed` "tests/Local/json/parseWithdrawalResp.json"-  where-    expected = PayResp-      { _prPayError      = Nothing-      , _prPayExecStatus = PeCompleted-      , _prPayKey        = PayKey "AP-21C62741YR509274N"-      , _prPayInfos      = [p]-      }-    p = PayInfo-      { _piReceiver            = r-      , _piSenderTransactionId = Just (TransactionId "7TU31598YN877045D")-      , _piTransactionStatus   = Just TsCompleted-      , _piTransactionId       = Just (TransactionId "1DG71997KF688883E")-      }-    r = Receiver-      { _reAmount    = "1.00"-      , _reEmail     = "user@mail.com"-      , _reAccountId = "69D9D7KVA6WAN"-      }--parseDepositResp :: Assertion-parseDepositResp = expected `eqParsed` "tests/Local/json/parseDepositResp.json"-  where-    expected = PayResp-      { _prPayError      = Nothing-      , _prPayExecStatus = PeCreated-      , _prPayKey        = PayKey "AP-3NL080742J1818731"-      , _prPayInfos      = []-      }--parseLookupResp :: Assertion-parseLookupResp = expected `eqParsed` "tests/Local/json/parseLookupResp.json"-  where-    expected = PayResp-      { _prPayError      = Nothing-      , _prPayExecStatus = PeCompleted-      , _prPayKey        = PayKey "AP-5XY65045XE4097324"-      , _prPayInfos      = [p]-      }-    p = PayInfo-      { _piReceiver            = r-      , _piSenderTransactionId = Just (TransactionId "9U084775U2533540T")-      , _piTransactionStatus   = Just TsCompleted-      , _piTransactionId       = Just (TransactionId "0CL88103PF900230J")-      }-    r = Receiver-      { _reAmount    = "1.00"-      , _reEmail     = "user@mail.com"-      , _reAccountId = "69D9D7KVA6WAN"-      }--parseErrResp :: Assertion-parseErrResp = expected `eqParsed` "tests/Local/json/parseErrResp.json"-  where-    expected = AeErrCodes [560022]
tests/Remote.hs view
@@ -2,21 +2,17 @@  module Main where -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           Test.Framework                 (Test, defaultMain, testGroup)+import           Test.Framework.Providers.HUnit (testCase)+import           Test.HUnit                     hiding (Test)++import           Import 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]@@ -46,29 +42,17 @@       depositUserAddressDNE   ] --- | Currently the ID for all sandbox apps.-appId :: Text-appId = "APP-80W284485P519543T"+newtype TestSettings = TestSettings { _unTestSettings :: (Client, Text, Text) } -instance FromJSON Client where-  parseJSON = withObject "client credentials" $ \o ->-    Client appId                 <$>-    o .: "sandboxClientUid"      <*>-    pure Sandbox                 <*>-    o .: "sandboxClientPassword" <*>-    o .: "sandboxClientSig"      <*>-    o .: "sandboxAccountEmail"+instance FromJSON TestSettings where+  parseJSON = withObject "TestSettings" $ \o -> do+    client          <- o .: "client"+    userEmail       <- (.: "email_with_paypal_account") =<< o .: "other"+    nonSandboxEmail <- (.: "email_without_paypal_account") =<< o .: "other"+    return $ TestSettings (client,userEmail,nonSandboxEmail)  config :: IO (Client, Text, Text)-config = do-  b <- B.readFile "sandbox.json"-  client <- assertRight (eitherDecode b)-  Object o <- assertRight (eitherDecode b)-  String userEmail <- assertJust "sandboxUserEmail not found"-    $ H.lookup "sandboxUserEmail" o-  String notSandboxEmail <- assertJust "randomNonSandboxUserEmail not found"-    $ H.lookup "randomNonSandboxUserEmail" o-  return (client, userEmail, notSandboxEmail)+config = fmap _unTestSettings . assertRight . eitherDecode =<< B.readFile "sandbox.json"  incorrectEmailClient :: Text -> Client -> Client incorrectEmailClient badAccountEmail (Client a b c d e _) = Client a b c d e badAccountEmail@@ -91,47 +75,48 @@ correctWithdrawal :: Assertion correctWithdrawal = do   (client, userEmail, _) <- config-  wdResp <- assertRight =<< WD.req client (withdrawal userEmail)-  lpResp <- assertRight =<< LP.req client (LP.LookupPayKey $ _crPayKey wdResp)+  wdResp <- assertRight . checkComplete =<< fmap snd . assertRight =<< WD.request client (withdrawal userEmail)+  lpResp <- fmap snd . assertRight =<< LP.request client (LP.LookupPayKey $ _cpPayKey wdResp)   void $ assertRight (checkComplete lpResp)  withdrawalAccountAddressDNE :: Assertion withdrawalAccountAddressDNE = do   (client, userEmail, notSandboxEmail) <- config   let c = incorrectEmailClient notSandboxEmail client-  adaptiveErr <- assertLeft =<< WD.req c (withdrawal userEmail)-  assertEqual "send error due to a nonexistent account address" AeNoSuchEmail adaptiveErr+  adaptiveErr <- assertLeft =<< WD.request c (withdrawal userEmail)+  case adaptiveErr of+    AeErrorResponse _ (ErrorResponse [PayError code _]) ->+      assertEqual "send error due to a nonexistent account address" noSuchEmail code+    e -> assertFailure $ "withdrawalAccountAddressDNE got unexpected error " <> show e  withdrawalUserAddressDNE :: Assertion withdrawalUserAddressDNE = do   (client, _, notSandboxEmail) <- config-  payResp <- assertExpected =<< assertLeft =<< WD.req client (withdrawal notSandboxEmail)-  assertLookup =<< assertRight =<< LP.req client (LP.LookupPayKey $ _prPayKey payResp)+  payResp <- fmap snd . assertRight =<< WD.request client (withdrawal notSandboxEmail)+  assertExpected payResp+  assertLookup =<< fmap snd . assertRight =<< LP.request client (LP.LookupPayKey $ _prPayKey payResp)   where-    assertExpected :: AdaptiveErr -> IO PayResp-    assertExpected (AePending payResp) = do-      assertEqual-        "withdraw to nonexistent user addresses return no PayInfos"-        0 (length $ _prPayInfos payResp)-      return payResp-    assertExpected _ =-      assertFalse "send error due to a nonexistent user address didn't return AePending"-      >> fail "checkResp failed"+    assertExpected :: PayResponse -> IO ()+    assertExpected payResp = do+      void . assertLeft $ checkComplete payResp+      assertEqual "withdraw to nonexistent user addresses return no PayInfos"+                  0+                  (length $ _prPaymentInfos payResp) -    assertLookup :: PayResp -> Assertion+    assertLookup :: PayResponse -> Assertion     assertLookup a = do-      info <- assertRight $ do-        payExecStatusCompleted a-        errorFieldNothing a-        checkPayKey a-        getPayInfo a+      assertEqual "withdrawalAccountAddressDNE _prPayStatus" PeCompleted (_prPayStatus a)+      info <- assertRight (getPaymentInfo a)       assertEqual "pending withdrawal transaction status" Nothing (_piTransactionStatus info)  withdrawalNotEnoughFunds :: Assertion withdrawalNotEnoughFunds = do   (client, userEmail, _) <- config-  adaptiveErr <- assertLeft =<< WD.req client (largeWithdrawal userEmail)-  assertEqual "send error due to not enough funds" (AeErrCodes [520009]) adaptiveErr+  adaptiveErr <- assertLeft =<< WD.request client (largeWithdrawal userEmail)+  case adaptiveErr of+    AeErrorResponse _ (ErrorResponse [PayError code _]) ->+      assertEqual "send error due to not enough funds" (ErrorCode 520009) code+    e -> assertFailure $ "withdrawalAccountAddressDNE got unexpected error " <> show e   where     largeWithdrawal :: Text -> WD.Withdrawal     largeWithdrawal userAddr = WD.Withdrawal@@ -142,36 +127,33 @@ correctDepositBeginning :: Assertion correctDepositBeginning = do   (client, userEmail, _) <- config-  payKey <- assertRight =<< DP.req client (deposit userEmail)-  assertExpected =<< assertRight =<< LP.req client (LP.LookupPayKey payKey)+  payKey <- fmap (_prPayKey . snd) . assertRight =<< DP.request client (deposit userEmail)+  assertExpected =<< fmap snd . assertRight =<< LP.request client (LP.LookupPayKey payKey)   where-    assertExpected :: PayResp -> Assertion+    assertExpected :: PayResponse -> Assertion     assertExpected a = do-      info <- assertRight $ do-        payExecStatusCreated a-        errorFieldNothing a-        checkPayKey a-        getPayInfo a+      assertEqual "correctDepositBeginning _prPayStatus" PeCreated (_prPayStatus a)+      info <- assertRight (getPaymentInfo a)       assertEqual "pending deposit transaction status" Nothing (_piTransactionStatus info)  depositAccountAddressDNE :: Assertion depositAccountAddressDNE = do   (client, userEmail, notSandboxEmail) <- config   let c = incorrectEmailClient notSandboxEmail client-  payKey <- assertRight =<< DP.req c (deposit userEmail)-  assertExpected =<< assertRight =<< LP.req client (LP.LookupPayKey payKey)+  payKey <- fmap (_prPayKey . snd) . assertRight =<< DP.request c (deposit userEmail)+  assertExpected . snd =<< assertRight =<< LP.request client (LP.LookupPayKey payKey)   where-    assertExpected :: PayResp -> Assertion+    assertExpected :: PayResponse -> Assertion     assertExpected a = do-      info <- assertRight $ do-        payExecStatusCreated a-        errorFieldNothing a-        checkPayKey a-        getPayInfo a+      assertEqual "depositUserAddressDNE _prPayStatus" PeCreated (_prPayStatus a)+      info <- assertRight (getPaymentInfo a)       assertEqual "status for deposit where to address isn't an account" Nothing (_piTransactionStatus info)  depositUserAddressDNE :: Assertion depositUserAddressDNE = do   (client, _, notSandboxEmail) <- config-  adaptiveErr <- assertLeft =<< DP.req client (deposit notSandboxEmail)-  assertEqual "create error due to a nonexistent user address" AeNoSuchEmail adaptiveErr+  adaptiveErr <- assertLeft =<< DP.request client (deposit notSandboxEmail)+  case adaptiveErr of+    AeErrorResponse _ (ErrorResponse [PayError code _]) ->+      assertEqual "create error due to a nonexistent user address" noSuchEmail code+    e -> assertFailure $ "withdrawalAccountAddressDNE got unexpected error " <> show e
− tests/Utils.hs
@@ -1,24 +0,0 @@-module Utils where--import Test.HUnit--assertFalse :: String -> IO ()-assertFalse s = assertFailure s >> fail "assertFalse"--assertJust :: String -> Maybe a -> IO a-assertJust s a =-  case a of-    Nothing -> assertFailure s >> fail "assertJust failed"-    Just b  -> return b--assertRight :: (Show a) => Either a b -> IO b-assertRight a =-  case a of-    Left e  -> assertFailure (show e) >> fail "assertRight failed"-    Right b -> return b--assertLeft :: (Show b) => Either a b -> IO a-assertLeft a =-  case a of-    Left e  -> return e-    Right b -> assertFailure (show b) >> fail "assertLeft failed"