lib/plaid/institutions.ex

defmodule Plaid.Institutions do
  @moduledoc """
  [Plaid Institutions API](https://plaid.com/docs/api/institutions/) calls and schema.
  """

  defmodule GetResponse do
    @moduledoc """
    [Plaid API /institutions/get response schema.](https://plaid.com/docs/api/institutions/#institutionsget)
    """

    @behaviour Plaid.Castable

    alias Plaid.Castable
    alias Plaid.Institution

    @type t :: %__MODULE__{
            institutions: [Institution.t()],
            total: integer(),
            request_id: String.t()
          }

    defstruct [
      :institutions,
      :total,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        institutions: Castable.cast_list(Institution, generic_map["institutions"]),
        total: generic_map["total"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get information about Plaid institutions.
    
  Does a `POST /institutions/get` call to list the supported Plaid
  institutions with their details.
    
  ## Params
  * `:count` - The total number of Institutions to return.
  * `:offset` - The number of Institutions to skip.
  * `:country_codes` - Array of country codes the institution supports.

  ## Options
  * `:products` - Filter based on which products they support.
  * `:routing_numbers` - Filter based on routing numbers.
  * `:oauth` - Filter institutions with or without OAuth login flows. 
  * `:include_optional_metadata` - When true, return the institution's homepage URL, logo and primary brand color.

  ## Examples

      Institutions.get(%{count: 25, offset: 0, country_codes: ["CA", "GB]}, client_id: "123", secret: "abc")
      {:ok, %Institutions.GetResponse{}}

  """
  @spec get(params, options, Plaid.config()) :: {:ok, GetResponse.t()} | {:error, Plaid.Error.t()}
        when params: %{
               required(:count) => integer(),
               required(:offset) => integer(),
               required(:country_codes) => [String.t()]
             },
             options: %{
               optional(:products) => [String.t()],
               optional(:routing_numbers) => [String.t()],
               optional(:oauth) => boolean(),
               optional(:include_optional_metadata) => boolean()
             }
  def get(params, options \\ %{}, config) do
    options_payload =
      Map.take(options, [:products, :routing_numbers, :oauth, :include_optional_metadata])

    payload =
      params
      |> Map.take([:count, :offset, :country_codes])
      |> Map.merge(%{options: options_payload})

    Plaid.Client.call(
      "/institutions/get",
      payload,
      GetResponse,
      config
    )
  end

  defmodule GetByIdResponse do
    @moduledoc """
    [Plaid API /institutions/get_by_id response schema.](https://plaid.com/docs/api/institutions/#institutionsget_by_id)
    """

    @behaviour Plaid.Castable

    alias Plaid.Castable
    alias Plaid.Institution

    @type t :: %__MODULE__{
            institution: Institution.t(),
            request_id: String.t()
          }

    defstruct [
      :institution,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        institution: Castable.cast(Institution, generic_map["institution"]),
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get information about a Plaid institution.

  Does a `POST /institutions/get_by_id` call to retrieve a Plaid
  institution by it's ID.

  ## Params
  * `institution_id` - The ID of the institution to get details about.
  * `country_codes` - Array of country codes the institution supports.

  ## Options
  * `:include_optional_metadata` - When true, return the institution's homepage URL, logo and primary brand color.
  * `:include_status` - When true, the response will include status information about the institution.

  ## Examples

      Institutions.get_by_id("ins_1", ["CA", "GB], client_id: "123", secret: "abc")
      {:ok, %Institutions.GetByIdResponse{}}

  """
  @spec get_by_id(String.t(), [String.t()], options, Plaid.config()) ::
          {:ok, GetByIdResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{
               optional(:products) => [String.t()],
               optional(:routing_numbers) => [String.t()],
               optional(:oauth) => boolean(),
               optional(:include_optional_metadata) => boolean()
             }
  def get_by_id(institution_id, country_codes, options \\ %{}, config) do
    options_payload = Map.take(options, [:include_optional_metadata, :include_status])

    payload =
      %{}
      |> Map.put(:institution_id, institution_id)
      |> Map.put(:country_codes, country_codes)
      |> Map.merge(%{options: options_payload})

    Plaid.Client.call(
      "/institutions/get_by_id",
      payload,
      GetByIdResponse,
      config
    )
  end

  defmodule SearchResponse do
    @moduledoc """
    [Plaid API /institutions/search response schema.](https://plaid.com/docs/api/institutions/#institutionssearch)
    """

    @behaviour Plaid.Castable

    alias Plaid.Castable
    alias Plaid.Institution

    @type t :: %__MODULE__{
            institutions: [Institution.t()],
            request_id: String.t()
          }

    defstruct [
      :institutions,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        institutions: Castable.cast_list(Institution, generic_map["institutions"]),
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get information about all Plaid institutions matching the search params.

  Does a `POST /institutions/search` call to list the supported Plaid
  institutions with their details based on your search query.

  ## Params
  * `:query` - The search query. Institutions with names matching the query are returned
  * `:products` - Filter the Institutions based on whether they support listed products.
  * `:country_codes` - Array of country codes the institution supports.

  ## Options
  * `:include_optional_metadata` - When true, return the institution's homepage URL, logo and primary brand color.
  * `:oauth` - Filter institutions with or without OAuth login flows.
  * `:account_filter` - Object allowing account type -> sub-type filtering.

  > See [Account Type Schema](https://plaid.com/docs/api/accounts/#account-type-schema) for more details on the `account_filter` option.

  ## Examples

      Institutions.search(%{query: "Ally", products: ["auth"], country_codes: ["US"]}, client_id: "123", secret: "abc")
      {:ok, %Institutions.SearchResponse{}}

  """
  @spec search(params, options, Plaid.config()) ::
          {:ok, SearchResponse.t()} | {:error, Plaid.Error.t()}
        when params: %{
               required(:query) => String.t(),
               required(:products) => [String.t()],
               required(:country_codes) => [String.t()]
             },
             options: %{
               optional(:include_optional_metadata) => boolean(),
               optional(:oauth) => boolean(),
               optional(:account_filter) => map()
             }
  def search(params, options \\ %{}, config) do
    options_payload = Map.take(options, [:oauth, :include_optional_metadata, :account_filter])

    payload =
      params
      |> Map.take([:query, :products, :country_codes])
      |> Map.merge(%{options: options_payload})

    Plaid.Client.call(
      "/institutions/search",
      payload,
      SearchResponse,
      config
    )
  end
end