Current section
Files
Jump to
Current section
Files
lib/panda_doc/phoenix_controller.ex
defmodule PandaDoc.PhoenixController do
@moduledoc """
Implements a PhoenixController that can be easily wired up and used.
## Examples
```elixir
defmodule YourAppWeb.PandaDocController do
use PandaDoc.PhoenixController
def handle_document_change(id, status, _details) do
id
|> Documents.get_by_pandadoc_id!()
|> Documents.update_document(%{status: status})
end
def handle_document_complete(id, pdf, status, _details) do
id
|> Documents.get_by_pandadoc_id!()
|> Documents.update_document(%{data: pdf, status: status})
end
end
```
"""
@doc """
Triggers when a PandaDoc document changed.
"""
@callback handle_document_change(String.t(), String.t(), map()) :: any()
@doc """
Triggers when a PandaDoc document has been completed/signed.
"""
@callback handle_document_complete(String.t(), binary(), String.t(), map()) :: any()
defmacro __using__(_) do
quote do
@moduledoc "Implements a PhoenixController with callbacks for PandaDoc."
@behaviour PandaDoc.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, %{"_json" => data}) do
Enum.each(data, &parse_document/1)
Conn.send_resp(conn, 200, "")
end
@doc "fallback webhook that should not match."
@spec webhook(Conn.t(), map()) :: Conn.t()
def webhook(conn, _), do: Conn.send_resp(conn, 406, "")
# parsing valid document state changes
defp parse_document(%{
"event" => "document_state_changed",
"data" =>
%{
"id" => id,
"status" => status
} = details
}) do
spawn(fn ->
case status do
"document.completed" ->
pdf = download_data(id)
handle_document_complete(id, pdf, status, details)
_ ->
handle_document_change(id, status, details)
end
end)
end
# failsafe for parsing bad documents
defp parse_document(_), do: :ok
# downloads the document from pandadoc
defp download_data(id) do
if Mix.env() == :test do
# no way of testing the pandadoc api programatically
Logger.info("[PandaDoc] Using dummy data for tests")
:crypto.strong_rand_bytes(128)
else
# we wait here since PandaDoc is not the fastest
Logger.info("[PandaDoc] Downloading document #{id} in 30 seconds")
:timer.sleep(30_000)
id
|> PandaDoc.download_document()
|> ok_data(id)
end
end
# just returns the pdf document
defp ok_data({:ok, pdf}, id) do
Logger.info("[PandaDoc] Successfully downloaded document #{id}")
pdf
end
# retries the request in 2 seconds
defp ok_data({:error, error}, id) do
Logger.info(
"[PandaDoc] Retrying download of document #{id} in 2 seconds: #{inspect(error)}"
)
:timer.sleep(2_000)
download_data(id)
end
end
end
end