{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-| A demonstration of basic functionality provided by the DocuSign Client. -}
module Main where
import Control.Monad ( void )
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Data.Text ( Text )
import Data.UUID ( UUID )
import DocuSign.Client ( DocuSignClient (..)
, docuSignClient
, runClient )
import DocuSign.Client.Configuration ( Config (..)
, AccountConfig (..)
, ServerConfig (..) )
import DocuSign.Client.Types ( Document (..)
, DocumentId
, Envelope (..)
, Recipient (..)
, Uri )
import GHC.Generics ( Generic )
import Options.Generic ( ParseField
, ParseFields
, ParseRecord
, type (<?>) (..) )
import System.FilePath ( takeFileName )
import qualified Data.ByteString as B
import qualified Data.Text as T
import qualified DocuSign.Base.ContentTypes as DC
import qualified DocuSign.Client.Types as DT
import qualified Options.Generic as O
data CommandLineOptions = CommandLineOptions
{ host :: Text <?> "DocuSign server hostname"
, port :: Word <?> "DocuSign server port"
, account :: Word <?> "DocuSign account ID"
, key :: UUID <?> "DocuSign account key"
, username :: Text <?> "DocuSign account username (email address)"
, password :: Text <?> "DocuSign account password"
, anchor :: Text <?> "Anchor text to determine signature location"
, input :: FilePath <?> "Path to unsigned document (PDF)"
, output :: FilePath <?> "Path to write signed document (PDF)"
} deriving (Generic, Show)
main :: IO ()
main = runExample =<< O.getRecord "docusign-example"
runExample :: CommandLineOptions -> IO ()
runExample options =
runClient (makeDocuSignConfig options) (example docuSignClient options) >>=
either
(\e -> putStrLn "Error:" >> print e)
(const $ pure ())
-- | A simple example of embedded document signing.
--
-- See the following guide for further details:
--
-- https://developers.docusign.com/esign-rest-api/guides/embedded-signing
--
example :: MonadIO m => DocuSignClient m -> CommandLineOptions -> m ()
example DocuSignClient {..} options@CommandLineOptions {..} = do
liftIO $ putStrLn "Loading document ..."
doc <- liftIO $ readInputDocument options
liftIO $ putStrLn "Sending document to DocuSign ..."
let aid = DT.mkAccountId $ fromIntegral $ getOption account
(eid, url) <- postDocumentsForRedirectionBasedSigning
aid [doc] defaultEnvelope
(makeRecipient options) (const postSigningUri)
liftIO $ do
putStrLn "A new envelope was created with the following ID:"
print eid
putStrLn "Your document is now ready for signing at the link below:"
print url
putStrLn "After you've signed the document, press [Enter]."
void getLine
liftIO $ putStrLn "Fetching signed document ..."
signedDoc <- fetchDocument aid eid defaultDocumentId
liftIO $ do
putStrLn "Saving signed document ..."
B.writeFile (getOption output) (DC.toBytes signedDoc)
putStrLn "Finished!"
defaultDocumentId :: DocumentId
defaultDocumentId = DT.mkDocumentId 1
defaultEnvelope :: Envelope
defaultEnvelope = Envelope
{ envelopeSubject = "Please sign this document"
, envelopeMessage = "Please sign this document" }
makeDocuSignConfig :: CommandLineOptions -> Config
makeDocuSignConfig CommandLineOptions {..} =
Config AccountConfig {..} ServerConfig {..}
where
accountId = DT.mkAccountId $ fromIntegral $ getOption account
accountKey = getOption key
accountUsername = getOption username
accountPassword = getOption password
serverHost = getOption host
serverPort = fromIntegral $ getOption port
makeRecipient :: CommandLineOptions -> Recipient
makeRecipient CommandLineOptions {..} = Recipient
{ recipientClientUserId = DT.mkUserId "TestUserId"
, recipientEmailAddress = DT.mkEmailAddress "test@example.com"
, recipientName = "Test Recipient"
, recipientSignatureAnchorText = pure $ getOption anchor }
postSigningUri :: Uri
postSigningUri = DT.mkUri "http://httpbin.org/get"
readInputDocument :: CommandLineOptions -> IO Document
readInputDocument options = do
let inputPath = getOption $ input options
content <- B.readFile inputPath
pure Document
{ documentContent = DC.fromBytes content
, documentName = T.pack $ takeFileName inputPath
, documentId = defaultDocumentId }
-- Generic option parsing instances:
instance ParseRecord CommandLineOptions
instance ParseField Word
instance ParseFields Word
instance ParseRecord Word where parseRecord = fmap O.getOnly O.parseRecord
instance ParseField UUID
instance ParseFields UUID
instance ParseRecord UUID where parseRecord = fmap O.getOnly O.parseRecord
getOption :: (field <?> help) -> field
getOption = O.unHelpful