lib/glific_web/controllers/exotel_controller.ex

defmodule GlificWeb.ExotelController do
  @moduledoc """
  The controller to process events received from exotel
  """

  use GlificWeb, :controller
  require Logger

  alias Glific.{Contacts, Flows, Partners, Repo}

  @doc """
  First implementation of processing optin contact callback from exotel
  for digital green. Will need to make it more generic for broader use case
  across other NGOs

  We use the callto and directon parameters to ensure a valid call from exotel
  """
  @spec optin(Plug.Conn.t(), map) :: Plug.Conn.t()
  def optin(
        %Plug.Conn{assigns: %{organization_id: organization_id}} = conn,
        %{
          "CallFrom" => exotel_from,
          "CallTo" => _exotel_call_to,
          "To" => exotel_to
        } = params
      ) do
    Logger.info("exotel #{inspect(params)}")

    organization = Partners.organization(organization_id)

    Repo.put_process_state(organization.id)

    credentials = organization.services["exotel"]

    if is_nil(credentials) do
      log_error("exotel credentials missing")
    else
      keys = credentials.keys

      {phone, ngo_exotel_phone} =
        if keys["direction"] == "incoming",
          do: {exotel_from, exotel_to},
          else: {exotel_to, exotel_from}

      if ngo_exotel_phone == credentials.secrets["phone"] do
        # first create and optin the contact
        attrs = %{
          phone: clean_phone(phone),
          method: "Exotel",
          organization_id: organization_id
        }

        result = Contacts.optin_contact(attrs)

        # then start  the intro flow
        case result do
          {:ok, contact} ->
            {:ok, flow_id} = Glific.parse_maybe_integer(keys["flow_id"])
            Flows.start_contact_flow(flow_id, contact)

          {:error, error} ->
            log_error(error)
        end
      else
        log_error("exotel credentials mismatch")
      end
    end

    # always return 200 and an empty response
    json(conn, "")
  end

  def optin(conn, params) do
    Logger.info("exotel unhandled #{inspect(params)}")
    json(conn, "")
  end

  @country_code "91"

  @spec clean_phone(String.t()) :: String.t()
  defp clean_phone(phone) when is_binary(phone),
    do: @country_code <> String.slice(phone, -10, 10)

  defp clean_phone(phone), do: phone

  @spec log_error(String.t()) :: any
  defp log_error(message) do
    # log this error and send to appsignal also
    Logger.error(message)
    {_, stacktrace} = Process.info(self(), :current_stacktrace)
    Appsignal.send_error(:error, message, stacktrace)
  end
end