lib/issuing_authorization/issuing_authorization.ex

defmodule StarkInfra.IssuingAuthorization do
  alias __MODULE__, as: IssuingAuthorization
  alias StarkInfra.Error
  alias StarkInfra.Utils.JSON
  alias StarkInfra.Utils.Parse
  alias StarkInfra.Utils.Check
  alias StarkInfra.User.Project
  alias StarkInfra.User.Organization


  @moduledoc """
  Groups IssuingAuthorization related functions
  """

  @doc """
  An IssuingAuthorization presents purchase data to be analysed and answered with an approval or a declination.

  ## Attributes (return-only):
    - `:end_to_end_id` [string]: central bank's unique transaction ID. ex: "E79457883202101262140HHX553UPqeq"
    - `:amount` [integer]: IssuingPurchase value in cents. Minimum = 0. ex: 1234 (= R$ 12.34)
    - `:tax` [integer]: IOF amount taxed for international purchases. ex: 1234 (= R$ 12.34)
    - `:card_id` [string]: unique id returned when IssuingCard is created. ex: "5656565656565656"
    - `:issuer_amount` [integer]: issuer amount. ex: 1234 (= R$ 12.34)
    - `:issuer_currency_code` [string]: issuer currency code. ex: "USD"
    - `:merchant_amount` [integer]: merchant amount. ex: 1234 (= R$ 12.34)
    - `:merchant_currency_code` [string]: merchant currency code. ex: "USD"
    - `:merchant_category_code` [string]: merchant category code. ex: "fastFoodRestaurants"
    - `:merchant_country_code` [string]: merchant country code. ex: "USA"
    - `:acquirer_id` [string]: acquirer ID. ex: "5656565656565656"
    - `:merchant_id` [string]: merchant ID. ex: "5656565656565656"
    - `:merchant_name` [string]: merchant name. ex: "Google Cloud Platform"
    - `:merchant_fee` [integer]: merchant fee charged. ex: 200 (= R$ 2.00)
    - `:wallet_id` [string]: virtual wallet ID. ex: "googlePay"
    - `:method_code` [string]: method code. ex: "chip", "token", "server", "manual", "magstripe" or "contactless"
    - `:score` [float]: internal score calculated for the authenticity of the purchase. Nil in case of insufficient data. ex: 7.6
    - `:is_partial_allowed` [bool]: true if the the merchant allows partial purchases. ex: False
    - `:purpose` [string]: purchase purpose. ex: "purchase"
    - `:card_tags` [list of strings]: tags of the IssuingCard responsible for this purchase. ex: ["travel", "food"]
    - `:holder_tags` [list of strings]: tags of the IssuingHolder responsible for this purchase. ex: ["technology", "john snow"]
  """
  @enforce_keys [
    :id,
    :end_to_end_id,
    :amount,
    :tax,
    :card_id,
    :issuer_amount,
    :issuer_currency_code,
    :merchant_amount,
    :merchant_currency_code,
    :merchant_category_code,
    :merchant_country_code,
    :acquirer_id,
    :merchant_id,
    :merchant_name,
    :merchant_fee,
    :wallet_id,
    :method_code,
    :score,
    :is_partial_allowed,
    :purpose,
    :card_tags,
    :holder_tags
  ]
  defstruct [
    :id,
    :end_to_end_id,
    :amount,
    :tax,
    :card_id,
    :issuer_amount,
    :issuer_currency_code,
    :merchant_amount,
    :merchant_currency_code,
    :merchant_category_code,
    :merchant_country_code,
    :acquirer_id,
    :merchant_id,
    :merchant_name,
    :merchant_fee,
    :wallet_id,
    :method_code,
    :score,
    :is_partial_allowed,
    :purpose,
    :card_tags,
    :holder_tags
  ]

  @type t() :: %__MODULE__{}

  @doc """
  Create a single IssuingAuthorization struct received from IssuingAuthorization at the informed endpoint.
  If the provided digital signature does not check out with the StarkInfra public key, a
  starkinfra.error.InvalidSignatureError will be raised.

  ## Parameters (required):
    - `:content` [string]: response content from request received at user endpoint (not parsed)
    - `:signature` [string]: base-64 digital signature received at response header "Digital-Signature"

  ## Options
    - `cache_pid` [PID, default nil]: PID of the process that holds the public key cache, returned on previous parses. If not provided, a new cache process will be generated.
    - `:user` [Organization/Project, default nil]: Organization or Project struct returned from StarkInfra.project(). Only necessary if default project or organization has not been set in configs.

  ## Return:
    - Parsed IssuingAuthorization struct
  """
  @spec parse(
    content: binary,
    signature: binary,
    cache_pid: PID,
    user: Project.t() | Organization.t()
  )::
    {:ok, IssuingAuthorization.t()} |
    {:error, [error: Error.t()]}
  def parse(options) do
    %{content: content, signature: signature, cache_pid: cache_pid, user: user} =
      Enum.into(
        options |> Check.enforced_keys([:content, :signature]),
        %{cache_pid: nil, user: nil}
      )
    Parse.parse_and_verify(
      content: content,
      signature: signature,
      cache_pid: cache_pid,
      key: nil,
      resource_maker: &resource_maker/1,
      user: user
    )
  end

  @doc """
  Same as parse(), but it will unwrap the error tuple and raise in case of errors.
  """
  @spec parse!(
    content: binary,
    signature: binary,
    cache_pid: PID,
    user: Project.t() | Organization.t()
  ) :: any
  def parse!(options \\ []) do
    %{content: content, signature: signature, cache_pid: cache_pid, user: user} =
      Enum.into(
        options |> Check.enforced_keys([:content, :signature]),
        %{cache_pid: nil, user: nil}
      )
    Parse.parse_and_verify!(
      content: content,
      signature: signature,
      cache_pid: cache_pid,
      key: nil,
      resource_maker: &resource_maker/1,
      user: user
    )
  end

  @doc """
  Helps you respond IssuingAuthorization requests.

  ## Parameters (required):
    - `:status` [string]: sub-issuer response to the authorization. ex: "accepted" or "denied"

  ## Options
    - `:amount` [integer, default 0]: amount in cents that was authorized. ex: 1234 (= R$ 12.34)
    - `:reason` [string, default ""]: denial reason. ex: "other"
    - `:tags` [list of strings, default []]: tags to filter retrieved object. ex: ["tony", "stark"]

  ## Return:
    - Dumped JSON string that must be returned to us on the IssuingAuthorization request
  """
  @spec response!(
    status: binary,
    amount: integer,
    reason: binary,
    tags: [binary]
  ) :: any
  def response!(status, options \\ []) do
    options = options ++ [status: status]
    JSON.encode!(%{authorization:
      Enum.into(options |> Check.enforced_keys([:status]), %{amount: 0, reason: "", tags: []})
      |> Enum.filter(fn {_, v} -> v != nil end)
      |> Enum.into(%{})
    })
  end

  @doc false
  def resource() do
    {
      "IssuingAuthorization",
      &resource_maker/1
    }
  end

  @doc false
  def resource_maker(json) do
    %IssuingAuthorization{
      id: json[:id],
      end_to_end_id: json[:end_to_end_id],
      amount: json[:amount],
      tax: json[:tax],
      card_id: json[:card_id],
      issuer_amount: json[:issuer_amount],
      issuer_currency_code: json[:issuer_currency_code],
      merchant_amount: json[:merchant_amount],
      merchant_currency_code: json[:merchant_currency_code],
      merchant_category_code: json[:merchant_category_code],
      merchant_country_code: json[:merchant_country_code],
      acquirer_id: json[:acquirer_id],
      merchant_id: json[:merchant_id],
      merchant_name: json[:merchant_name],
      merchant_fee: json[:merchant_fee],
      wallet_id: json[:wallet_id],
      method_code: json[:method_code],
      score: json[:score],
      is_partial_allowed: json[:is_partial_allowed],
      purpose: json[:purpose],
      card_tags: json[:card_tags],
      holder_tags: json[:holder_tags]
    }
  end
end