Current section
Files
Jump to
Current section
Files
lib/http.ex
defmodule Braintree.HTTP do
@moduledoc """
Base client for all server interaction, used by all endpoint specific
modules. This request wrapper coordinates the remote server, headers,
authorization and SSL options.
Using `Braintree.HTTP` requires the presence of three config values:
* merchant_id - Braintree merchant id
* private_key - Braintree private key
* public_key - Braintree public key
All three must have values set or a `Braintree.ConfigError` will be raised
at runtime. All those config values support the `{:system, "VAR_NAME"}`
as a value - in that case the value will be read from the system environment
with `System.get_env("VAR_NAME")`.
"""
require Logger
alias Braintree.XML.{Decoder, Encoder}
@endpoints [
production: "https://api.braintreegateway.com/merchants/",
sandbox: "https://api.sandbox.braintreegateway.com/merchants/"
]
@cacertfile "/certs/api_braintreegateway_com.ca.crt"
@headers [
{"Accept", "application/xml"},
{"User-Agent", "Braintree Elixir/0.1"},
{"Accept-Encoding", "gzip"},
{"X-ApiVersion", "4"},
{"Content-Type", "application/xml"}
]
@doc """
Centralized request handling function. All convenience structs use this
function to interact with the Braintree servers. This function can be used
directly to supplement missing functionality.
## Example
defmodule MyApp.Disbursement do
alias Braintree.HTTP
def disburse(params \\ %{}) do
HTTP.request(:get, "disbursements", params)
end
end
"""
@spec request(atom, binary, binary | Map.t) ::
{:ok, Map.t} | {:error, Map.t} | {:error, integer} | {:error, binary}
def request(method, path, body \\ "") do
response = :hackney.request(method, build_url(path), build_headers(), encode_body(body), build_options())
case response do
{:ok, code, _headers, body} when code >= 200 and code <= 399 ->
{:ok, decode_body(body)}
{:ok, 401, _headers, _body} ->
{:error, :unauthorized}
{:ok, 404, _headers, _body} ->
{:error, :not_found}
{:ok, _code, _headers, body} ->
{:error, decode_body(body)}
{:error, reason} ->
{:error, reason}
end
end
for method <- ~w(get delete post put)a do
def unquote(method)(path, payload \\ %{}) do
request(unquote(method), path, payload)
end
end
## Helper Functions
@doc false
@spec build_url(binary) :: binary
def build_url(path) do
environment = Braintree.get_env(:environment, :sandbox)
merchant_id = Braintree.get_env(:merchant_id)
Keyword.fetch!(@endpoints, environment) <> merchant_id <> "/" <> path
end
@doc false
@spec encode_body(binary | Map.t) :: binary
def encode_body(body) when body == "" or body == %{}, do: ""
def encode_body(body), do: Encoder.dump(body)
@doc false
@spec decode_body(binary) :: Map.t
def decode_body(body) do
body
|> :zlib.gunzip
|> String.strip
|> Decoder.load
rescue
ErlangError -> Logger.error("unprocessable response")
end
@doc false
@spec basic_auth(binary, binary) :: binary
def basic_auth(user, pass) do
"Basic " <> :base64.encode("#{user}:#{pass}")
end
@doc false
@spec build_headers() :: [tuple]
def build_headers do
public = Braintree.get_env(:public_key)
private = Braintree.get_env(:private_key)
[{"Authorization", basic_auth(public, private)} | @headers]
end
@doc false
@spec build_options() :: [...]
def build_options do
cacertfile = Path.join(:code.priv_dir(:braintree), @cacertfile)
http_opts = Braintree.get_env(:http_options, [])
[:with_body, ssl_options: [cacertfile: cacertfile]] ++ http_opts
end
end