{-# LANGUAGE ImplicitParams #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} ------------------------------------------------------------------------------ -- | -- Module: QuickBooks -- -- For more information, see: -- -- * QuickBooks API Reference: -- . -- ------------------------------------------------------------------------------ module QuickBooks ( -- * Authentication and authorization getAccessTokens , getAccessTokens' , getTempTokens , getTempTokens' , authorizationURLForToken , cancelOAuthAuthorization , cancelOAuthAuthorization' -- * Transaction entities -- ** Invoices -- *** Types , module QuickBooks.Types -- *** CRUD an invoice , createInvoice , createInvoice' , readInvoice , readInvoice' , updateInvoice , updateInvoice' , deleteInvoice , deleteInvoice' -- *** Send an invoice via email , EmailAddress , emailAddress , sendInvoice , sendInvoice' -- *** Read in configuration files , readAPIConfigFromFile , readAppConfigFromFile -- * Name list entities -- ** Customer , queryCustomer , queryCustomer' -- ** Line , queryItem , queryItem' ) where import QuickBooks.Authentication import QuickBooks.Types hiding (EmailAddress,emailAddress) import Control.Applicative ((<$>),(<*>), (<|>)) import Control.Arrow (second) import Data.ByteString.Char8 (pack) import Data.Maybe (fromJust) import Data.Text (Text) import Network.HTTP.Client.TLS (tlsManagerSettings) import Network.HTTP.Client (newManager) import System.Environment (getEnvironment) import Text.Email.Validate (EmailAddress, emailAddress) import QuickBooks.Customer import QuickBooks.Invoice ( createInvoiceRequest , deleteInvoiceRequest , readInvoiceRequest , updateInvoiceRequest , sendInvoiceRequest ) import QuickBooks.Item import QuickBooks.Logging (apiLogger, getLogger) import Data.Yaml (ParseException, decodeFileEither) -- $setup -- -- >>> import Data -- -- >>> :set -XOverloadedStrings -- >>> maybeTestOAuthToken <- lookupTestOAuthTokenFromEnv -- >>> let oAuthToken = maybe (error "") id maybeTestOAuthToken -- | -- -- >>> :{ -- do eitherQueryCustomer <- -- queryCustomer oAuthToken "Rondonuwu Fruit and Vegi" -- case eitherQueryCustomer of -- Right (QuickBooksCustomerResponse (customer:_)) -> -- print (customerId customer) -- _ -> -- putStrLn "Nothing" -- :} -- Just "21" queryCustomer :: OAuthToken -> Text -> IO (Either String (QuickBooksResponse [Customer])) queryCustomer tok = queryQuickBooks tok . QueryCustomer queryCustomer' :: APIConfig -> AppConfig -> OAuthToken -> Text -> IO (Either String (QuickBooksResponse [Customer])) queryCustomer' apiConfig appConfig tok = queryQuickBooks' apiConfig appConfig tok . QueryCustomer -- | -- -- >>> :{ -- do eitherQueryItem <- queryItem oAuthToken "Hours" -- case eitherQueryItem of -- Right (QuickBooksItemResponse (item:_)) -> -- print (itemId item) -- _ -> -- putStrLn "Nothing" -- :} -- Just "2" queryItem :: OAuthToken -> Text -> IO (Either String (QuickBooksResponse [Item])) queryItem tok = queryQuickBooks tok . QueryItem queryItem' :: APIConfig -> AppConfig -> OAuthToken -> Text -> IO (Either String (QuickBooksResponse [Item])) queryItem' apiConfig appConfig tok = queryQuickBooks' apiConfig appConfig tok . QueryItem -- | Create an invoice. -- -- Example: -- -- >>> import Data.Maybe (fromJust) -- >>> :{ -- do resp <- createInvoice oAuthToken testInvoice -- case resp of -- Left err -> putStrLn $ "My custom error message: " ++ err -- Right (QuickBooksInvoiceResponse invoice) -> do -- deleteInvoice oAuthToken (fromJust (invoiceId invoice)) (fromJust (invoiceSyncToken invoice)) -- putStrLn "I created an invoice!" -- :} -- I created an invoice! -- -- Note that we deleted the item we created using 'deleteInvoice'. createInvoice :: OAuthToken -> Invoice -> IO (Either String (QuickBooksResponse Invoice)) createInvoice tok = queryQuickBooks tok . CreateInvoice -- | Like createInvoice but accepts an APIConfig rather than reading it from the environment createInvoice' :: APIConfig -> AppConfig -> OAuthToken -> Invoice -> IO (Either String (QuickBooksResponse Invoice)) createInvoice' apiConfig appConfig tok = queryQuickBooks' apiConfig appConfig tok . CreateInvoice -- | Retrieve the details of an invoice that has been previously created. -- -- Example: -- -- First, we create an invoice (see 'createInvoice'): -- -- >>> import Data.Maybe (fromJust) -- >>> Right (QuickBooksInvoiceResponse cInvoice) <- createInvoice oAuthToken testInvoice -- -- Then, we read the invoice and test that it is the same invoice we created: -- -- >>> let cInvoiceId = fromJust (invoiceId cInvoice) -- >>> :{ -- do eitherReadInvoice <- readInvoice oAuthToken cInvoiceId -- case eitherReadInvoice of -- Left _ -> return False -- Right (QuickBooksInvoiceResponse rInvoice) -> return (cInvoice == rInvoice) -- :} -- True -- -- Finally, we delete the invoice we created: -- -- >>> deleteInvoice oAuthToken cInvoiceId (fromJust (invoiceSyncToken cInvoice)) readInvoice :: OAuthToken -> InvoiceId -> IO (Either String (QuickBooksResponse Invoice)) readInvoice tok = queryQuickBooks tok . ReadInvoice -- | Like readInvoice but accepts an APIConfig rather than reading it from the environment readInvoice' :: APIConfig -> AppConfig -> OAuthToken -> InvoiceId -> IO (Either String (QuickBooksResponse Invoice)) readInvoice' apiConfig appConfig tok = queryQuickBooks' apiConfig appConfig tok . ReadInvoice -- | Update an invoice. -- -- Example: -- -- First, we create an invoice (see 'createInvoice'): -- -- >>> import Data.Maybe (fromJust) -- >>> Right (QuickBooksInvoiceResponse cInvoice) <- createInvoice oAuthToken testInvoice -- -- Then, we update the customer reference of the invoice: -- -- >>> let nInvoice = cInvoice { invoiceCustomerRef = Reference Nothing Nothing "1" } -- >>> :{ -- do eitherUpdateInvoice <- updateInvoice oAuthToken nInvoice -- case eitherUpdateInvoice of -- Left _ -> return False -- Right (QuickBooksInvoiceResponse uInvoice) -> -- return (invoiceCustomerRef cInvoice == invoiceCustomerRef uInvoice) -- :} -- False -- -- Finally, we delete the invoice we created: -- -- >>> deleteInvoice oAuthToken (fromJust (invoiceId cInvoice)) (fromJust (invoiceSyncToken cInvoice)) updateInvoice :: OAuthToken -> Invoice -> IO (Either String (QuickBooksResponse Invoice)) updateInvoice tok = queryQuickBooks tok . UpdateInvoice -- | Like updateInvoice but accepts an APIConfig rather than reading it from the environment updateInvoice' :: APIConfig -> AppConfig -> OAuthToken -> Invoice -> IO (Either String (QuickBooksResponse Invoice)) updateInvoice' apiConfig appConfig tok = queryQuickBooks' apiConfig appConfig tok . UpdateInvoice -- | Delete an invoice. -- -- Example: -- -- First, we create an invoice (see 'createInvoice'): -- -- >>> import Data.Maybe (fromJust) -- >>> Right (QuickBooksInvoiceResponse cInvoice) <- createInvoice oAuthToken testInvoice -- -- Then, we delete it: -- -- >>> let cInvoiceId = fromJust (invoiceId cInvoice) -- >>> let cInvoiceSyncToken = fromJust (invoiceSyncToken cInvoice) -- >>> :{ -- do eitherDeleteInvoice <- deleteInvoice oAuthToken cInvoiceId cInvoiceSyncToken -- case eitherDeleteInvoice of -- Left e -> putStrLn e -- Right _ -> putStrLn "I deleted an invoice!" -- :} -- I deleted an invoice! deleteInvoice :: OAuthToken -> InvoiceId -> SyncToken -> IO (Either String (QuickBooksResponse DeletedInvoice)) deleteInvoice tok iId = queryQuickBooks tok . DeleteInvoice iId -- | Like deleteInvoice but accepts an APIConfig rather than reading it from the environment deleteInvoice' :: APIConfig -> AppConfig -> OAuthToken -> InvoiceId -> SyncToken -> IO (Either String (QuickBooksResponse DeletedInvoice)) deleteInvoice' apiConfig appConfig tok iId = queryQuickBooks' apiConfig appConfig tok . DeleteInvoice iId -- | Send an invoice via email. -- -- Example: -- -- First, we create an invoice (see 'createInvoice'): -- -- >>> import Data.Maybe (fromJust) -- >>> Right (QuickBooksInvoiceResponse cInvoice) <- createInvoice oAuthToken testInvoice -- -- Then, we send the invoice via email: -- -- >>> let cInvoiceId = fromJust (invoiceId cInvoice) -- >>> let testEmail = fromJust (emailAddress "test@test.com") -- >>> :{ -- do eitherSendInvoice <- sendInvoice oAuthToken cInvoiceId testEmail -- case eitherSendInvoice of -- Left e -> putStrLn e -- Right _ -> putStrLn "I sent an invoice!" -- :} -- I sent an invoice! -- -- Finally, we delete the invoice we created: -- -- >>> deleteInvoice oAuthToken cInvoiceId (fromJust (invoiceSyncToken cInvoice)) sendInvoice :: OAuthToken -> InvoiceId -> EmailAddress -> IO (Either String (QuickBooksResponse Invoice)) sendInvoice tok invId = queryQuickBooks tok . SendInvoice invId sendInvoice' :: APIConfig -> AppConfig -> OAuthToken -> InvoiceId -> EmailAddress -> IO (Either String (QuickBooksResponse Invoice)) sendInvoice' apiConfig appConfig tok invId = queryQuickBooks' apiConfig appConfig tok . SendInvoice invId -- | Get temporary tokens to request permission. -- -- Example: -- -- >>> :{ -- do eitherTempTokens <- getTempTokens "localhost" -- case eitherTempTokens of -- Left e -> putStrLn e -- Right _ -> putStrLn "I got my request tokens!" -- :} -- ... -- I got my request tokens! getTempTokens :: CallbackURL -> IO (Either String (QuickBooksResponse OAuthToken)) getTempTokens = queryQuickBooksOAuth Nothing . GetTempOAuthCredentials getTempTokens' :: AppConfig -> CallbackURL -> IO (Either String (QuickBooksResponse OAuthToken)) getTempTokens' appConfig = queryQuickBooksOAuth' appConfig Nothing . GetTempOAuthCredentials -- | Exchange oauth_verifier for access tokens getAccessTokens :: OAuthToken -> OAuthVerifier -> IO (Either String (QuickBooksResponse OAuthToken)) getAccessTokens tempToken = queryQuickBooksOAuth (Just tempToken) . GetAccessTokens getAccessTokens' :: AppConfig -- Your application's consumer key and consumer secret -> OAuthToken -- The temporary OAuth tokens obtained from getTempTokens -> OAuthVerifier -- The OAuthVerifier returned by QuickBooks when it calls your callback -> IO (Either String (QuickBooksResponse OAuthToken)) getAccessTokens' appConfig tempToken = do queryQuickBooksOAuth' appConfig (Just tempToken) . GetAccessTokens -- | Invalidate an OAuth access token and disconnect from QuickBooks. cancelOAuthAuthorization :: OAuthToken -> IO (Either String (QuickBooksResponse ())) cancelOAuthAuthorization tok = queryQuickBooksOAuth (Just tok) DisconnectQuickBooks cancelOAuthAuthorization' :: AppConfig -> OAuthToken -> IO (Either String (QuickBooksResponse ())) cancelOAuthAuthorization' appConfig tok = queryQuickBooksOAuth' appConfig (Just tok) DisconnectQuickBooks queryQuickBooks :: OAuthToken -> QuickBooksQuery a -> IO (Either String (QuickBooksResponse a)) queryQuickBooks tok query = do apiConfig <- readAPIConfig appConfig <- readAppConfig queryQuickBooks' apiConfig appConfig tok query queryQuickBooks' :: APIConfig -> AppConfig -> OAuthToken -> QuickBooksQuery a -> IO (Either String (QuickBooksResponse a)) queryQuickBooks' apiConfig appConfig tok query = do manager <- newManager tlsManagerSettings logger <- getLogger apiLogger let ?appConfig = appConfig let ?apiConfig = apiConfig let ?manager = manager let ?logger = logger case query of CreateInvoice invoice -> createInvoiceRequest tok invoice ReadInvoice _invoiceId -> readInvoiceRequest tok _invoiceId UpdateInvoice invoice -> updateInvoiceRequest tok invoice DeleteInvoice _invoiceId syncToken -> deleteInvoiceRequest tok _invoiceId syncToken SendInvoice _invoiceId emailAddr -> sendInvoiceRequest tok _invoiceId emailAddr QueryCustomer queryCustomerName -> queryCustomerRequest tok queryCustomerName QueryItem queryItemName -> queryItemRequest tok queryItemName queryQuickBooksOAuth :: Maybe OAuthToken -> QuickBooksOAuthQuery a -> IO (Either String (QuickBooksResponse a)) queryQuickBooksOAuth maybeOAuthToken query = do appConfig <- readAppConfig queryQuickBooksOAuth' appConfig maybeOAuthToken query queryQuickBooksOAuth' :: AppConfig -> Maybe OAuthToken -> QuickBooksOAuthQuery a -> IO (Either String (QuickBooksResponse a)) queryQuickBooksOAuth' appConfig maybeOauthToken query = do manager <- newManager tlsManagerSettings logger <- getLogger apiLogger let ?appConfig = appConfig let ?manager = manager let ?logger = logger case query of (GetTempOAuthCredentials callbackURL) -> getTempOAuthCredentialsRequest callbackURL (GetAccessTokens oauthVerifier) -> getAccessTokensRequest (fromJust maybeOauthToken) oauthVerifier DisconnectQuickBooks -> disconnectRequest (fromJust maybeOauthToken) readAPIConfig :: IO APIConfig readAPIConfig = do env <- getEnvironment case lookupAPIConfig env of Just config -> return config Nothing -> fail "The environment variables INTUIT_COMPANY_ID,INTUIT_TOKEN,INTUIT_SECRET, and INTUIT_HOSTNAME must be set" readAppConfig :: IO AppConfig readAppConfig = do env <- getEnvironment case lookupAppConfig env of Just config -> return config Nothing -> fail "The evironment variables INTUIT_CONSUMER_KEY and INTUIT_CONSUMER_SECRET must be set" lookupAPIConfig :: [(String, String)] -> Maybe APIConfig lookupAPIConfig environment = APIConfig <$> lookup "INTUIT_COMPANY_ID" env <*> lookup "INTUIT_TOKEN" env <*> lookup "INTUIT_SECRET" env <*> lookup "INTUIT_HOSTNAME" env <*> (lookup "INTUIT_API_LOGGING_ENABLED" env <|> Just "true") where env = map (second pack) environment readAPIConfigFromFile :: FilePath -> IO (Either ParseException APIConfig) readAPIConfigFromFile = decodeFileEither readAppConfigFromFile :: FilePath -> IO (Either ParseException AppConfig) readAppConfigFromFile = decodeFileEither lookupAppConfig :: [(String, String)] -> Maybe AppConfig lookupAppConfig environment = AppConfig <$> lookup "INTUIT_CONSUMER_KEY" env <*> lookup "INTUIT_CONSUMER_SECRET" env where env = map (second pack) environment