lib/dwolla/customer.ex

defmodule Dwolla.Customer do
  @moduledoc """
  Functions for `customers` endpoint.
  """

  alias Dwolla.Utils

  defstruct id: nil, first_name: nil, last_name: nil, email: nil, type: nil,
            status: nil, created: nil, address1: nil, address2: nil, city: nil,
            phone: nil, postal_code: nil, state: nil, verify_beneficial_ownership: nil,
            certify_beneficial_ownership: nil

  @type t :: %__MODULE__{
    id: String.t,
    first_name: String.t,
    last_name: String.t,
    email: String.t,
    type: String.t,   # "unverified" | "personal" | "business" | "receive-only"
    status: String.t, # "unverified" | "suspended" | "retry" | "document" | "verified" | "suspended"
    created: String.t,
    address1: String.t,
    address2: String.t,
    city: String.t,
    phone: String.t,
    postal_code: String.t,
    state: String.t,
    verify_beneficial_ownership: boolean(),
    certify_beneficial_ownership: boolean()
  }
  @type token :: String.t
  @type id :: String.t
  @type params :: %{required(atom) => String.t | integer | map}
  @type error :: HTTPoison.Error.t | Dwolla.Errors.t | atom | tuple
  @type location :: %{id: String.t}

  @endpoint "customers"

  @unverified_customer ["first_name", "last_name", "email"]

  @verified_customer   @unverified_customer ++ ["type", "address1", "city",
                       "state", "postal_code", "date_of_birth", "ssn"]

  @verify              ["first_name", "last_name", "email", "type", "address1",
                        "city", "state", "postal_code", "date_of_birth", "ssn",
                        "phone"]

  @doc """
  Creates an unverified customer.

  Validates payload for required fields before calling Dwolla API.

  Parameters
  ```
  %{first_name: "Steve", last_name: "Rogers", email: "cap@example.com"}
  ```
  """
  @spec create_unverified(token, params) :: {:ok, location} | {:error, error}
  def create_unverified(token, params) do
    case Utils.validate_params(params, @unverified_customer) do
      :ok    -> create(token, params)
      :error -> {:error, :invalid_parameters}
    end
  end

  @doc """
  Creates a verified customer.

  Validates payload for required fields before calling Dwolla API.

  Parameters
  ```
  %{
    first_name: "Steve",
    last_name: "Rogers",
    email: "cap@example.com",
    type: "personal",
    address1: "1600 Pennsylvania Ave",
    city: "Washington",
    state: "DC",
    postal_code: "20500",
    date_of_birth: "1918-07-04",
    ssn: "1776"
  }
  ```
  """
  @spec create_verified(token, params) :: {:ok, location} | {:error, error}
  def create_verified(token, params) do
    case Utils.validate_params(params, @verified_customer) do
      :ok    -> create(token, params)
      :error -> {:error, :invalid_parameters}
    end
  end

  @doc """
  Creates a customer.

  See `Dwolla.Customer.create_unverified/2` and `Dwolla.Customer.create_verified/2`.
  """
  @spec create(token, params) :: {:ok, location} | {:error, error}
  def create(token, params) do
    headers = Utils.idempotency_header(params)
    Dwolla.make_request_with_token(:post, @endpoint, token, params, headers)
    |> Utils.handle_resp(:customer)
  end

  @doc """
  Updates a customer's metadata.
  """
  @spec update(token, id, params) :: {:ok, Dwolla.Customer.t} | {:error, error}
  def update(token, id, params) do
    endpoint = @endpoint <> "/#{id}"
    headers = Utils.idempotency_header(params)
    Dwolla.make_request_with_token(:post, endpoint, token, params, headers)
    |> Utils.handle_resp(:customer)
  end

  @doc """
  Updates a customer to `verified` status.

  Parameters
  ```
  %{
    first_name: "Steve",
    last_name: "Rogers",
    email: "cap@example.com",
    address1: "1600 Pennsylvania Ave",
    city: "Washington",
    state: "DC",
    postal_code: "20500",
    date_of_birth: "1918-07-04",
    ssn: "1776"
  }
  ```
  """
  @spec verify(token, id, params) :: {:ok, Dwolla.Customer.t} | {:error, error}
  def verify(token, id, params) do
    case Utils.validate_params(params, @verify) do
      :ok    -> update(token, id, params)
      :error -> {:error, :invalid_parameters}
    end
  end

  @doc """
  Suspends a customer.
  """
  @spec suspend(token, id) :: {:ok, Dwolla.Customer.t} | {:error, error}
  def suspend(token, id) do
    update(token, id, %{status: "suspended"})
  end

  @doc """
  Searches customer by first name, last name and email. Results paginated.

  Parameters (optional)
  ```
  %{limit: 50, offset: 0, search: "Steve"}
  ```
  """
  @spec search(token, params) :: {:ok, [Dwolla.Customer.t]} | {:error, error}
  def search(token, params \\ %{}) do
    endpoint =
      case Map.keys(params) do
        [] -> @endpoint
        _  ->
          encoded_params = params |> Utils.to_camel_case() |> Utils.encode_params()
          @endpoint <> "?" <> encoded_params
      end
    Dwolla.make_request_with_token(:get, endpoint, token)
    |> Utils.handle_resp(:customer)
  end

  @doc """
  Gets a customer by id.
  """
  @spec get(token, id) :: {:ok, Dwolla.Customer.t} | {:error, error}
  def get(token, id) do
    endpoint = @endpoint <> "/#{id}"
    Dwolla.make_request_with_token(:get, endpoint, token)
    |> Utils.handle_resp(:customer)
  end

  @doc """
  Creates a customer funding source.

  Parameters
  ```
  %{
    routing_number: "222222226",
    account_number: "123456789",
    type: "checking",
    name: "Ben's checking"
  }
  ```
  """
  @spec create_funding_source(token, id, params) :: {:ok, location} | {:error, error}
  def create_funding_source(token, id, params) do
    endpoint = @endpoint <> "/#{id}/funding-sources"
    headers = Utils.idempotency_header(params)
    Dwolla.make_request_with_token(:post, endpoint, token, params, headers)
    |> Utils.handle_resp(:funding_source)
  end

  @doc """
  Lists a customer's funding sources.
  """
  @spec list_funding_sources(token, id, boolean) :: {:ok, [Dwolla.FundingSource.t]} | {:error, error}
  def list_funding_sources(token, id, removed \\ true) do
    endpoint =
      case removed do
        true  -> @endpoint <> "/#{id}/funding-sources"
        false -> @endpoint <> "/#{id}/funding-sources?removed=false"
      end
    Dwolla.make_request_with_token(:get, endpoint, token)
    |> Utils.handle_resp(:funding_source)
  end

  @doc """
  Searchs a customer's transfers. Results paginated.

  Parameters
  ```
  %{start_date: "2017-04-01", end_date: "2017-04-30", status: "pending"}
  ```
  """
  @spec search_transfers(token, id, params) :: {:ok, %{transfers: [Dwolla.Transfer.t], total: integer()}} | {:error, error}
  def search_transfers(token, id, params \\ %{}) do
    endpoint =
      case Map.keys(params) do
        [] -> @endpoint <> "/#{id}/transfers"
        _  ->
          encoded_params = params |> Utils.to_camel_case() |> Utils.encode_params()
          @endpoint <> "/#{id}/transfers?" <> encoded_params
      end
    Dwolla.make_request_with_token(:get, endpoint, token)
    |> Utils.handle_resp(:transfer)
  end

  @doc """
  Creates a customer beneficial owner.

  Parameters
  ```
  %{
    first_name: "John",
    last_name: "Smith",
    date_of_birth: "1900-01-01",
    ssn: "000-00-0000",
    address: %{
      address1: "1 Main St.",
      address2: "Apt 1", # optional
      city: "New York",
      state_province_region: "NY",
      postal_code: "10019",
      country: "US
    }
  }
  ```
  """
  @spec create_beneficial_owner(token, id, params) :: {:ok, location} | {:error, error}
  def create_beneficial_owner(token, id, params) do
    endpoint = @endpoint <> "/#{id}/beneficial-owners"
    headers = Utils.idempotency_header(params)
    Dwolla.make_request_with_token(:post, endpoint, token, params, headers)
    |> Utils.handle_resp(:beneficial_owner)
  end

  @doc """
  List beneficial owners
  """
  @spec list_beneficial_owners(token, id) :: {:ok, [Dwolla.BeneficialOwner.t]} | {:error, error}
  def list_beneficial_owners(token, id) do
    endpoint = @endpoint <> "/#{id}/beneficial-owners"
    Dwolla.make_request_with_token(:get, endpoint, token)
    |> Utils.handle_resp(:beneficial_owner)
  end
end