lib/dwolla.ex

defmodule Dwolla do
  @moduledoc """
  An HTTP Client for Dwolla.

  [Dwolla API Docs](https://docsv2.dwolla.com)
  """

  use HTTPoison.Base

  alias Dwolla.Utils

  @token_endpoint "token"

  defmodule MissingClientSecretError do
    defexception message: """
                 Client secret is missing. Please add client_id to your config.exs file.

                 config :dwolla, client_id: "your_client_id"
                 """
  end

  defmodule MissingClientIdError do
    defexception message: """
                 Client Id is missing. Please add client_secret to your config.exs file.

                 config :dwolla, client_secret: "your_client_secret"
                 """
  end

  defmodule MissingRootUriError do
    defexception message: """
                 The root_uri is required to specify the Dwolla environment to which you are
                 making calls, i.e. development or production. Please configure
                 root_uri in your config.exs file.

                 config :dwolla, root_uri: "https://api-sandbox.dwolla.com/" (development)
                 config :dwolla, root_uri: "https://api.dwolla.com/" (production)
                 """
  end

  @doc """
  Gets credentials from configuration.
  """
  @spec get_cred() :: map | no_return
  def get_cred do
    require_dwolla_credentials()
  end

  @doc """
  Gets root URI from configuration.
  """
  @spec get_root_uri() :: String.t() | no_return
  def get_root_uri do
    require_root_uri()
  end

  @doc """
  Makes request with token.
  """
  @spec make_request_with_token(atom, String.t(), String.t(), map, map, Keyword.t()) ::
          {:ok, HTTPoison.Response.t()} | {:error, HTTPoison.Error.t() | {:invalid, binary}}
  def make_request_with_token(method, endpoint, token, body \\ %{}, headers \\ %{}, options \\ []) do
    rb = body |> Utils.to_camel_case() |> maybe_encode()
    rh = token |> get_request_headers() |> Map.merge(headers) |> Map.to_list()
    options = httpoison_request_options() ++ options
    request(method, endpoint, rb, rh, options)
  end

  defp maybe_encode({:multipart, _} = body), do: body
  defp maybe_encode(body), do: Poison.encode!(body)

  @doc """
  Makes request to OAuth endpoint with credentials.
  """
  @spec make_oauth_token_request(map, map, list) ::
          {:ok, HTTPoison.Response.t()} | {:error, HTTPoison.Error.t()}
  def make_oauth_token_request(params, cred, options \\ []) do
    rb = Utils.encode_params(params, cred)
    rh = cred |> get_request_headers() |> Map.to_list()
    options = httpoison_request_options() ++ options
    request(:post, @token_endpoint, rb, rh, options)
  end

  def process_url(endpoint) do
    require_root_uri() <> endpoint
  end

  def process_response_body(""), do: ""

  def process_response_body(body) do
    case Poison.decode(body) do
      {:ok, parsed_body} -> parsed_body
      {:error, _} -> {:invalid, body}
    end
  end

  defp get_request_headers(%{client_id: client_id, client_secret: client_secret}) do
    encoded_auth_params = Base.encode64("#{client_id}:#{client_secret}")

    Map.new()
    |> Map.put("Authorization", "Bearer #{encoded_auth_params}")
    |> Map.put("Content-Type", "application/x-www-form-urlencoded")
  end

  defp get_request_headers(access_token) do
    Map.new()
    |> Map.put("Authorization", "Bearer #{access_token}")
    |> Map.put("Accept", "application/vnd.dwolla.v1.hal+json")
    |> Map.put("Content-Type", "application/vnd.dwolla.v1.hal+json")
  end

  defp require_dwolla_credentials do
    case {get_client_id(), get_client_secret()} do
      {:not_found, _} ->
        raise MissingClientIdError

      {_, :not_found} ->
        raise MissingClientSecretError

      {client_id, client_secret} ->
        %{client_id: client_id, client_secret: client_secret}
    end
  end

  defp require_root_uri do
    case Application.get_env(:dwolla, :root_uri) || :not_found do
      :not_found -> raise MissingRootUriError
      value -> value
    end
  end

  defp httpoison_request_options do
    Application.get_env(:dwolla, :httpoison_options, [])
  end

  defp get_client_id do
    Application.get_env(:dwolla, :client_id) || :not_found
  end

  defp get_client_secret do
    Application.get_env(:dwolla, :client_secret) || :not_found
  end
end