lib/api.ex

defmodule Mercadopago.API do
  require Logger

  @moduledoc """
  Documentation for Mercadopago.API. This module is about the base HTTP functionality
  """
  @base_url "https://api.mercadopago.com"

  @doc """
  Requests an OAuth token from Mercadopago, returns a tuple containing the token and seconds till expiry.

  Possible returns:

  - {:ok, data}
  - {:error, reason}

  ## Examples

      iex> Mercadopago.API.create_token()
      {:ok, {..}}

  """  
  def create_token() do
    params = %{
      "client_id" => Mercadopago.Config.get.client_id,
      "client_secret" => Mercadopago.Config.get.client_secret,
      "grant_type" => "client_credentials"
    }

    headers = [
      {"content-type", "application/x-www-form-urlencoded"}
    ]

    case HTTPoison.post(base_url() <> "/oauth/token", Poison.encode!(params), headers) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        token = body |> Poison.decode!()

        Logger.info("Access token obtained successfully.")
        {:ok, {token["access_token"], token["expires_in"]}}

      {:error, %HTTPoison.Error{reason: reason}} ->
        Logger.error("Error getting access token: #{inspect(reason)}")
        {:error, reason}
    end
  end

  @doc """
  Make an HTTP GET request to the correct API, adding the authentication required header.

  Possible returns:

  - {:ok, data}
  - {:ok, :not_found}
  - {:ok, :no_content}
  - {:error, :bad_network}
  - {:error, reason}

  ## Examples

      iex> Mercadopago.API.get(url)
      {:ok, {...}}

  """  
  def get(url) do
    case HTTPoison.get(base_url() <> url, headers()) do
      {:ok, %{status_code: 404}} ->
        {:error, :not_found}
      {:ok, %{status_code: 401}} ->
        {:error, :unauthorised}        
      {:ok, %{status_code: 400}} ->
        {:error, :not_found}
      {:ok, %{status_code: 204}} ->
        {:ok, :no_content}
      {:ok, %{body: body, status_code: 200}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}        
      {:ok, %{body: body}}->
        {:error, body}
      _ ->
        {:error, :bad_network}
    end
  end

  @doc """
  Make an HTTP POST request to the correct API, adding the authentication required header.

  ## Examples

      iex> Mercadopago.API.post(url, data)
      {:ok, {...}}

  """
  @spec post(String.t, map | list | nil) :: {:ok, map | :not_found | :no_content | nil} | {:error, :unauthorised | :bad_network | any}
  def post(url, data) do
    {:ok, data} = Poison.encode(data)
    case HTTPoison.post(base_url() <> url, data, headers()) do
      {:ok, %{status_code: 404}} ->
        {:error, :not_found}
      {:ok, %{status_code: 401}} ->
        {:error, :unauthorised}        
      {:ok, %{status_code: 400}} ->
        {:error, :bad_request}        
      {:ok, %{status_code: 204}} ->
        {:ok, nil}        
      {:ok, %{body: body, status_code: 201}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}           
      {:ok, %{body: body, status_code: 200}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}     
      {:ok, %{body: body}} = resp ->
        IO.inspect resp
        {:error, body}
      _ ->
        {:error, :bad_network}
    end
  end  

  @doc """
  Make an HTTP PUT request to the correct API, adding the authentication required header.

  ## Examples

      iex> Mercadopago.API.put(url, data)
      {:ok, {...}}

  """
  @spec put(String.t, map | list | nil) :: {:ok, map | :not_found | :no_content | nil} | {:error, :unauthorised | :bad_network | any}
  def put(url, data) do
    {:ok, data} = Poison.encode(data)
    case HTTPoison.put(base_url() <> url, data, headers()) do
      {:ok, %{status_code: 404}} ->
        {:error, :not_found}
      {:ok, %{status_code: 401}} ->
        {:error, :unauthorised}        
      {:ok, %{status_code: 400}} ->
        {:error, :bad_request}        
      {:ok, %{status_code: 204}} ->
        {:ok, nil}        
      {:ok, %{body: body, status_code: 201}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}           
      {:ok, %{body: body, status_code: 200}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}     
      {:ok, %{body: body}} = resp ->
        IO.inspect resp
        {:error, body}
      _ ->
        {:error, :bad_network}
    end
  end    

  @doc """
  Make an HTTP DELETE request to the correct API, adding the authentication required header.

  ## Examples

      iex> Mercadopago.API.delete(url)
      {:ok, {...}}

  """  
  def delete(url) do
    case HTTPoison.delete(base_url() <> url, headers()) do
      {:ok, %{status_code: 404}} ->
        {:error, :not_found}
      {:ok, %{status_code: 401}} ->
        {:error, :unauthorised}        
      {:ok, %{status_code: 400}} ->
        {:error, :not_found}
      {:ok, %{status_code: 204}} ->
        {:ok, :no_content}
      {:ok, %{body: body, status_code: 200}} ->
        {:ok, Poison.decode!(body, keys: :atoms)}        
      {:ok, %{body: body}}->
        {:error, body}
      _ ->
        {:error, :bad_network}
    end
  end

  defp headers() do
    [
      {"Authorization", "Bearer #{mercadopago_token()}"},
      {"Content-Type", "application/json"}
    ]
  end

  defp base_url,
    do: @base_url

  defp mercadopago_token,
    do: Mercadopago.get_token()

end