Skip to main content

lib/triple.ex

defmodule Triple do
  @moduledoc """
  Elixir client for the [Triple](https://jointriple.com) transaction data
  enrichment API.

  ## Quick start

      client = Triple.new(api_key: System.fetch_env!("TRIPLE_API_KEY"))

      {:ok, enriched} =
        Triple.enrich_transaction(client, %{
          merchant_name: "AMZN MKTP UK",
          transaction_type: :CARD_TRANSACTION,
          transaction_id: Triple.Util.generate_transaction_id(),
          transaction_amount: 24.99,
          transaction_currency: "GBP"
        })

  `client` is a plain `Triple.Config` struct — pass it explicitly
  everywhere. There's no global or process-dictionary state, which keeps
  the library safe to use with multiple Triple accounts/environments
  (e.g. sandbox and production) side by side in the same application.

  ## Configuration

  Every option can be passed to `new/1` directly, or set application-wide:

      config :triple, api_key: System.fetch_env!("TRIPLE_API_KEY")

  See `Triple.Config` for the full list of options (timeouts, retries,
  custom `Req` options, an optional client-side rate limiter, etc).

  ## Error handling

  Every call returns `{:ok, result} | {:error, %Triple.Error{}}`. Each
  function also has a `!` counterpart that raises `Triple.Error` instead —
  e.g. `enrich_transaction!/2`. See `Triple.Error` for the fields available
  on failure (HTTP status, field-level validation errors, retry-after,
  etc).

  ## Context modules

  Every resource also has its own module if you'd rather call it directly:
  `Triple.Enrich`, `Triple.Brands`, `Triple.Feedback`, `Triple.Stocks`,
  `Triple.Cryptos`, `Triple.TLS`.
  """

  alias Triple.{Config, Error}
  alias Triple.Types.{Brand, Crypto, Stock, TLSCertificate}
  alias Triple.Types.Enrich.{V1, V2}

  @type client :: Config.t()

  @doc "Builds a client (a `Triple.Config` struct). See `Triple.Config.new/1` for all options."
  @spec new(keyword()) :: client()
  defdelegate new(opts \\ []), to: Config

  @doc "See `Triple.Enrich.transaction/2`."
  @spec enrich_transaction(client(), map() | keyword()) ::
          {:ok, V1.Response.t()} | {:error, Error.t()}
  defdelegate enrich_transaction(client, attrs), to: Triple.Enrich, as: :transaction

  @doc "See `Triple.Enrich.transaction!/2`."
  @spec enrich_transaction!(client(), map() | keyword()) :: V1.Response.t()
  defdelegate enrich_transaction!(client, attrs), to: Triple.Enrich, as: :transaction!

  @doc "See `Triple.Enrich.unstructured_transaction/2`."
  @spec enrich_unstructured_transaction(client(), map() | keyword()) ::
          {:ok, V2.Response.t()} | {:error, Error.t()}
  defdelegate enrich_unstructured_transaction(client, attrs),
    to: Triple.Enrich,
    as: :unstructured_transaction

  @doc "See `Triple.Enrich.unstructured_transaction!/2`."
  @spec enrich_unstructured_transaction!(client(), map() | keyword()) :: V2.Response.t()
  defdelegate enrich_unstructured_transaction!(client, attrs),
    to: Triple.Enrich,
    as: :unstructured_transaction!

  @doc "See `Triple.Brands.fetch/2`."
  @spec fetch_brand(client(), String.t()) :: {:ok, Brand.t()} | {:error, Error.t()}
  defdelegate fetch_brand(client, id), to: Triple.Brands, as: :fetch

  @doc "See `Triple.Brands.fetch!/2`."
  @spec fetch_brand!(client(), String.t()) :: Brand.t()
  defdelegate fetch_brand!(client, id), to: Triple.Brands, as: :fetch!

  @doc "See `Triple.Feedback.report/2`."
  @spec report_feedback(client(), map() | keyword()) :: {:ok, :no_content} | {:error, Error.t()}
  defdelegate report_feedback(client, attrs), to: Triple.Feedback, as: :report

  @doc "See `Triple.Feedback.report!/2`."
  @spec report_feedback!(client(), map() | keyword()) :: :no_content
  defdelegate report_feedback!(client, attrs), to: Triple.Feedback, as: :report!

  @doc "See `Triple.Stocks.fetch/3`."
  @spec fetch_stock(client(), String.t(), keyword()) :: {:ok, Stock.t()} | {:error, Error.t()}
  defdelegate fetch_stock(client, isin, opts \\ []), to: Triple.Stocks, as: :fetch

  @doc "See `Triple.Stocks.fetch!/3`."
  @spec fetch_stock!(client(), String.t(), keyword()) :: Stock.t()
  defdelegate fetch_stock!(client, isin, opts \\ []), to: Triple.Stocks, as: :fetch!

  @doc "See `Triple.Cryptos.fetch/2`."
  @spec fetch_crypto(client(), String.t()) :: {:ok, Crypto.t()} | {:error, Error.t()}
  defdelegate fetch_crypto(client, slug), to: Triple.Cryptos, as: :fetch

  @doc "See `Triple.Cryptos.fetch!/2`."
  @spec fetch_crypto!(client(), String.t()) :: Crypto.t()
  defdelegate fetch_crypto!(client, slug), to: Triple.Cryptos, as: :fetch!

  @doc "See `Triple.TLS.issue_certificate/2`."
  @spec issue_tls_certificate(client(), map() | keyword()) ::
          {:ok, TLSCertificate.t()} | {:error, Error.t()}
  defdelegate issue_tls_certificate(client, attrs), to: Triple.TLS, as: :issue_certificate

  @doc "See `Triple.TLS.issue_certificate!/2`."
  @spec issue_tls_certificate!(client(), map() | keyword()) :: TLSCertificate.t()
  defdelegate issue_tls_certificate!(client, attrs), to: Triple.TLS, as: :issue_certificate!
end