lib/eversign/phoenix_controller.ex

defmodule Eversign.PhoenixController do
  @moduledoc """
  Implements a PhoenixController that can be easily wired up and used.

  ## Examples

  ```elixir
  defmodule YourAppWeb.EversignController do
    use Eversign.PhoenixController

    def handle_document_change(document_hash, action, _time, _details) do
      Documents.get_by!(eversign_document_hash: document_hash)
      |> Documents.update_document(%{status: action})
    end

    def handle_document_complete(document_hash, pdf, _details) do
      Documents.get_by!(eversign_document_hash: document_hash)
      |> Documents.update_document(%{data: pdf, status: status})
    end
  end
  ```

  Put the following lines into your `router.ex` and configure the WebHook in the eversign Application Settings.

  ```elixir
    post "/callbacks/eversign", YourAppWeb.EversignController, :webhook
  ```

  """

  @doc """
  Triggers when a Eversign document changed.
  """
  @callback handle_document_change(String.t(), String.t(), String.t(), map()) :: any()

  @doc """
  Triggers when a Eversign document has been completed/signed by all signers.
  """
  @callback handle_document_complete(String.t(), binary(), map()) :: any()

  defmacro __using__(_) do
    quote do
      @moduledoc "Implements a PhoenixController with callbacks for Eversign."
      @behaviour Eversign.PhoenixController
      require Logger
      use Phoenix.Controller
      alias Plug.Conn

      @doc "default webhook that should match."
      @spec webhook(Conn.t(), map()) :: Conn.t()
      def webhook(conn, params) do
        parse_document(params)
        Conn.send_resp(conn, 200, "")
      end

      # parsing valid document state changes
      defp parse_document(
             %{
               "event_type" => "document_completed",
               "event_time" => time,
               "event_hash" => hash,
               "meta" =>
                 %{
                   "related_document_hash" => document_hash,
                   "related_user_id" => _user_id,
                   "related_business_id" => _business_id,
                   "related_app_id" => _app_id
                 } = _meta
             } = data
           ) do
        pdf = download_data(document_hash)
        handle_document_complete(document_hash, pdf, data)
      end

      defp parse_document(
             %{
               "event_type" => type,
               "event_time" => time,
               "event_hash" => hash,
               "meta" =>
                 %{
                   "related_document_hash" => document_hash,
                   "related_user_id" => _user_id,
                   "related_business_id" => _business_id,
                   "related_app_id" => _app_id
                 } = _meta
             } = data
           ),
           do: handle_document_change(document_hash, type, time, data)

      # failsafe for parsing bad documents
      defp parse_document(_), do: :ok

      # downloads the document from Eversign
      defp download_data(document_hash) do
        Logger.info("[Eversign] Downloading document #{document_hash}")

        document_hash
        |> Eversign.download_document()
        |> ok_data(document_hash)
      end

      # just returns the pdf document
      defp ok_data({:ok, pdf}, document_hash) do
        Logger.info("[Eversign] Successfully downloaded document #{document_hash}")
        pdf.body
      end

      # retries the request in 2 seconds
      defp ok_data({:error, error}, document_hash) do
        Logger.info("[Eversign] Retrying download of document #{document_hash} in 2 seconds: #{inspect(error)}")

        :timer.sleep(2_000)
        download_data(document_hash)
      end
    end
  end
end