lib/eversign.ex

defmodule Eversign do
  @moduledoc """
  Documentation for `Eversign` API Module.

  This API only contains everything required to generate Documents (for now).
  This module is specifically just the HTTP REST Layer.
  Specific functionality can be found in the individual documentation for each module.

  https://eversign.com/api/documentation
  """
  @eversign_api "https://api.eversign.com/api"
  @eversign_js "https://static.eversign.com/js/embedded-signing.js"

  import Eversign.RequestBuilder
  alias Phoenix.HTML.Tag

  @doc """
  Provides a Phoenix compatible script-tag embedding the eversign JavaScript API.

  ## Returns

  - `...`

  ## Examples

      iex> Eversign.javascript_tag
      %Phoenix.HTML.Tag{..}

  """
  def javascript_tag,
    do: Tag.tag(:script, type: "text/javascript", src: @eversign_js)

  @doc """
  Gets the current configuration from Config prepared for query arguments.

  ## Returns

  - `[access_key: "", business_id: 0, language: "en", sandbox: 1]`

  ## Examples

      iex> Eversign.config()
      [
        access_key: "",
        business_id: 0,
        language: "en",
        sandbox: 1
      ]

  """
  def config, do: Application.get_env(:eversign, :config)

  defp client do
    [
      {Tesla.Middleware.BaseUrl, @eversign_api},
      {Tesla.Middleware.Timeout, timeout: 30_000},
      {Tesla.Middleware.JSON, engine: Poison, engine_opts: [keys: :atoms]},
      {Tesla.Middleware.Headers,
       [
         {"User-Agent", "Elixir"},
         {"Content-Type", "application/json"}
       ]}
    ]
    |> Tesla.client()
  end

  defp add_credentials(request) do
    config = config()

    request
    |> add_param(:query, :access_key, Keyword.get(config, :access_key))
    |> add_param(:query, :business_id, Keyword.get(config, :business_id))
    |> add_param(:query, :language, Keyword.get(config, :language, "en"))
  end

  @doc """
  List all Documents.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - type (String): Eversign document type/stage, defaults to :all

  ## Returns

  - `{:ok, [%Eversign.Document{}]}`
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  ## Examples

      iex> Eversign.list_documents(:all)
      {:ok, [%{}]}

  """
  @spec list_documents(Tesla.Client.t() | nil, nil | atom()) :: {:error, Tesla.Client.t()} | {:ok, list(map())}
  def list_documents(client \\ client(), type \\ :all) do
    %{}
    |> method(:get)
    |> url("/document")
    |> add_credentials()
    |> add_param(:query, :type, type)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
    |> response_body()
  end

  def test do
    b64 = "test/example.pdf" |> File.read!() |> Base.encode64()

    Eversign.create_document(%{
      use_hidden_tags: 1,
      files: [
        %{name: "xyz.pdf", file_base64: b64}
      ],
      meta: %{
        customer_id: 123,
        offer_id: 123
      },
      signers: [
        %{id: 1, name: "Zack McCracken", email: "franz@bett.ag", order: 1}
      ]
    })
  end

  @doc """
  Create a Document.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - params (Map): Eversign parameters required for document creation

  ## Returns

  - `{:ok, %Eversign.Document{}}`
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  ## Examples

      iex> Eversign.create_document(%{
      ...>   use_hidden_tags: 1,
      ...>   files: [
      ...>     %{name: "xyz.pdf", file_base64: "base64"},
      ...>   ],
      ...>   meta: %{
      ...>     customer_id: 123,
      ...>     offer_id: 123,
      ...>   },
      ...>   signers: [
      ...>     %{id: 1, name: "Zack McCracken", email: "zack@example.int", order: 1},
      ...>     %{id: 2, name: "Fred McCracken", email: "fred@example.int", order: 2},
      ...>   ]
      ...> })
      {:ok, %{}}

  """
  @spec create_document(Tesla.Client.t() | nil, map()) :: {:error, Tesla.Client.t()} | {:ok, map()}
  def create_document(client \\ client(), %{} = params) do
    _optional_params = %{
      :is_draft => :body,
      :title => :body,
      :message => :body,
      :use_signer_order => :body,
      :reminders => :body,
      :require_all_signers => :body,
      :custom_requester_name => :body,
      :custom_requester_email => :body,
      :redirect => :body,
      :redirect_decline => :body,
      :client => :body,
      :expires => :body,
      :embedded_signing_enabled => :body,
      :flexible_signing => :body,
      :use_hidden_tags => :body,
      :signers => :body,
      :files => :body,
      :recipients => :body,
      :meta => :body,
      :fields => :body
    }

    %{}
    |> method(:post)
    |> url("/document")
    |> add_param(:body, :sandbox, Keyword.get(config(), :sandbox, 1))
    |> add_credentials()
    |> Map.put(:body, params)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
  end

  @doc """
  Get Embedded Signing URLs.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - document_hash (String): Eversign Document Hash

  ## Returns

  - `{:ok, %Eversign.SignerListResponse{}}` on success
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  ## Examples

      iex> Eversign.embedded_signing_url("ABCDEFGH1234")
      {:ok, %{signers: []}}
  """
  @spec get_embedded_signing_url(Tesla.Client.t() | nil, String.t()) :: {:error, Tesla.Client.t()} | {:ok, list()}
  def get_embedded_signing_url(client \\ client(), document_hash) do
    %{}
    |> method(:get)
    |> url("/document")
    |> add_credentials()
    |> add_param(:query, :document_hash, document_hash)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
  end

  @doc """
  Delete a Document which is still in draft or cancelled status.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - document_hash (String): Eversign Document Hash

  ## Returns

  - `{:ok, %Eversign.ErrorResponse{success: true}}` on success
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  """
  @spec delete_document(Tesla.Client.t() | nil, String.t()) :: {:ok, map()} | {:error, Tesla.Client.t()}
  def delete_document(client \\ client(), document_hash) do
    %{}
    |> method(:delete)
    |> url("/document")
    |> add_credentials()
    |> add_param(:query, :document_hash, document_hash)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
  end

  @doc """
  Cancels a Document.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - document_hash (String): Eversign Document Hash

  ## Returns

  - `{:ok, %Eversign.ErrorResponse{success: true}}` on success
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  """
  @spec cancel_document(Tesla.Client.t() | nil, String.t()) :: {:ok, map()} | {:error, Tesla.Client.t()}
  def cancel_document(client \\ client(), document_hash) do
    %{}
    |> method(:delete)
    |> url("/document")
    |> add_credentials()
    |> add_param(:query, :document_hash, document_hash)
    |> add_param(:query, :cancel, 1)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
  end

  @doc """
  Download a PDF of any document.

  ## Parameters

  - client (Tesla.Client): [optional] Tesla Client for REST Connection
  - document_hash (String): Eversign Document Hash
  - query (Keywords): [optional] Query parameters

  ## Returns

  - `{:ok, [] = pdf_bytes}` on success
  - `{:error, %Eversign.ErrorResponse{success:false, error: %{}}}` on failure

  ## Examples

      iex> Eversign.download_document("msFYActMfJHqNTKH8YSvF1", audit_trail: 1)
      {:ok, []}

  """
  @spec download_document(Tesla.Client.t() | nil, String.t(), keyword(String.t()) | nil) ::
          {:ok, binary()} | {:ok, map()} | {:error, Tesla.Client.t()}
  def download_document(client \\ client(), document_hash, query \\ []) do
    optional_params = %{
      :audit_trail => :query,
      :document_id => :query,
      :url_only => :query
    }

    %{}
    |> method(:get)
    |> url("/download_final_document")
    |> add_credentials()
    |> add_param(:query, :document_hash, document_hash)
    |> add_optional_params(optional_params, query)
    |> Enum.into([])
    |> (&Tesla.request(client, &1)).()
  end
end