lib/plaid/payment_initiation.ex

defmodule Plaid.PaymentInitiation do
  @moduledoc """
  [Plaid Payment Initiation API](https://plaid.com/docs/api/products/#payment-initiation-uk-and-europe) calls and schema.
  """

  alias Plaid.Castable

  alias Plaid.PaymentInitiation.{
    Address,
    Amount,
    BACS,
    Payment,
    Recipient,
    Schedule
  }

  defmodule CreateRecipientResponse do
    @moduledoc """
    [Plaid API /payment_initiation/recipient/create response schema.](https://plaid.com/docs/api/products/#payment_initiationrecipientcreate)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            recipient_id: String.t(),
            request_id: String.t()
          }

    defstruct [
      :recipient_id,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        recipient_id: generic_map["recipient_id"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Create a recipient for payment initiation.

  Does a `POST /payment_initiation/recipient/create` call which creates a payment
  recipient for payment initiation. 

  The endpoint is idempotent: if a request has already been made with the
  same payment details, Plaid will return the same `recipient_id`.

  ## Params

  * `name` - The name of the recipient.

  ## Options

  * `:iban` - The International Bank Account Number (IBAN) for the recipient.
  * `:bacs` - The sort code of the account.
  * `:address` - The address of the payment recipient.

  If `:bacs` data is not provided, `:iban` becomes required.

  ## Examples

      PaymentInitiation.create_recipient("Wonder Wallet", client_id: "123", secret: "abc")
      {:ok, %PaymentInitiation.CreateRecipientResponse{}}

  """
  @spec create_recipient(name :: String.t(), options, Plaid.config()) ::
          {:ok, CreateRecipientResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{
               optional(:iban) => String.t(),
               optional(:bacs) => BACS.t(),
               optional(:address) => Address.t()
             }
  def create_recipient(name, options \\ %{}, config) do
    options_payload = Map.take(options, [:iban, :bacs, :address])

    payload =
      %{}
      |> Map.put(:name, name)
      |> Map.merge(options_payload)

    Plaid.Client.call(
      "/payment_initiation/recipient/create",
      payload,
      CreateRecipientResponse,
      config
    )
  end

  defmodule GetRecipientResponse do
    @moduledoc """
    [Plaid API /payment_initiation/recipient/get response schema.](https://plaid.com/docs/api/products/#payment_initiationrecipientget)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            recipient_id: String.t(),
            name: String.t(),
            address: Address.t() | nil,
            iban: String.t() | nil,
            bacs: BACS.t() | nil,
            request_id: String.t()
          }

    defstruct [
      :recipient_id,
      :name,
      :address,
      :iban,
      :bacs,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        recipient_id: generic_map["recipient_id"],
        name: generic_map["name"],
        address: Castable.cast(Address, generic_map["address"]),
        iban: generic_map["iban"],
        bacs: Castable.cast(BACS, generic_map["bacs"]),
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get a recipient for payment initiation.

  Does a `POST /payment_initiation/recipient/get` call to
  get details about a payment recipient.

  ## Params

  * `recipient_id` - The ID of the recipient.

  ## Examples

      PaymentInitiation.get_recipient("recipient-id-sandbox-123xxx", client_id: "123", secret: "abc")
      {:ok, %PaymentInitiation.GetRecipientResponse{}}

  """
  @spec get_recipient(recipient_id :: String.t(), Plaid.config()) ::
          {:ok, CreateRecipientResponse.t()} | {:error, Plaid.Error.t()}
  def get_recipient(recipient_id, config) do
    Plaid.Client.call(
      "/payment_initiation/recipient/get",
      %{recipient_id: recipient_id},
      GetRecipientResponse,
      config
    )
  end

  defmodule ListRecipientsResponse do
    @moduledoc """
    [Plaid API /payment_initiation/recipient/list response schema.](https://plaid.com/docs/api/products/#payment_initiationrecipientlist)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            recipients: [Recipient.t()],
            request_id: String.t()
          }

    defstruct [
      :recipients,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        recipients: Castable.cast_list(Recipient, generic_map["recipients"]),
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  List the payment recipients that you have previously created.

  Does a `POST /payment_initiation/recipient/list` call to
  list all recipients you have previously created.


  ## Examples

      PaymentInitiation.list_recipients(client_id: "123", secret: "abc")
      {:ok, %PaymentInitiation.ListRecipientsResponse{}}

  """
  @spec list_recipients(Plaid.config()) ::
          {:ok, ListRecipientsResponse.t()} | {:error, Plaid.Error.t()}
  def list_recipients(config) do
    Plaid.Client.call(
      "/payment_initiation/recipient/list",
      ListRecipientsResponse,
      config
    )
  end

  defmodule CreatePaymentResponse do
    @moduledoc """
    [Plaid API /payment_initiation/payment/create response schema.](https://plaid.com/docs/api/products/#payment_initiationpaymentcreate)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            payment_id: String.t(),
            status: String.t(),
            request_id: String.t()
          }

    defstruct [
      :payment_id,
      :status,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        payment_id: generic_map["payment_id"],
        status: generic_map["status"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Create a payment for a recipient.

  Does a `POST /payment_initiation/payment/create` call which creates
  a one-time or standing (recurring) payment for a recipient.

  ## Params

  * `recipient_id` - The ID of the recipient the payment is for.
  * `reference` - A reference for the payment.
  * `amount` - A payment amount.

  ## Options

  * `:schedule` - The schedule that the payment will be executed on.

  ## Examples

      PaymentInitiation.create_payment(
        "recipient-id-prod-123xxx",
        "Purchase Order  123",
        %PaymentInitiation.Amount{currency: "GBP", value: 200},
        %{
          schedule: %PaymentInitiation.Schedule{
            interval: "WEEKLY",
            interval_execution_day: 2,
            start_date: "2021-01-01",
            end_date: "2021-01-31"
          }
        },
        client_id: "123",
        secret: "abc"
      )
      {:ok, %PaymentInitiation.CreateRecipientResponse{}}

  """
  @spec create_payment(
          recipient_id :: String.t(),
          reference :: String.t(),
          amount :: Amount.t(),
          options,
          Plaid.config()
        ) :: {:ok, CreatePaymentResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{optional(:schedule) => Schedule.t()}
  def create_payment(recipient_id, reference, amount, options \\ %{}, config) do
    payload =
      %{}
      |> Map.put(:recipient_id, recipient_id)
      |> Map.put(:reference, reference)
      |> Map.put(:amount, amount)
      |> Plaid.Util.maybe_put(:schedule, options)

    Plaid.Client.call(
      "/payment_initiation/payment/create",
      payload,
      CreatePaymentResponse,
      config
    )
  end

  defmodule GetPaymentResponse do
    @moduledoc """
    [Plaid API /payment_initiation/payment/get response schema.](https://plaid.com/docs/api/products/#payment_initiationpaymentget)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            payment_id: String.t(),
            payment_token: String.t(),
            amount: Amount.t(),
            status: String.t(),
            recipient_id: String.t(),
            reference: String.t(),
            last_status_update: String.t(),
            schedule: Schedule.t() | nil,
            adjusted_reference: String.t() | nil,
            payment_expiration_time: String.t() | nil,
            request_id: String.t()
          }

    defstruct [
      :payment_id,
      :payment_token,
      :amount,
      :status,
      :recipient_id,
      :reference,
      :last_status_update,
      :schedule,
      :adjusted_reference,
      :payment_expiration_time,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        payment_id: generic_map["payment_id"],
        payment_token: generic_map["payment_token"],
        amount: Castable.cast(Amount, generic_map["amount"]),
        status: generic_map["status"],
        recipient_id: generic_map["recipient_id"],
        reference: generic_map["reference"],
        last_status_update: generic_map["last_status_update"],
        schedule: Castable.cast(Schedule, generic_map["schedule"]),
        adjusted_reference: generic_map["adjust_reference"],
        payment_expiration_time: generic_map["payment_expiration_time"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get payment details.

  Does a `POST /payment_initiation/payment/create` call to get
  details about a payment.

  ## Params

  * `payment_id` - The payment_id returned from /payment_initiation/payment/create.

  ## Examples

      PaymentInitiation.get_payment(
        "payment-id-prod-123xxx",
        client_id: "123",
        secret: "abc"
      )
      {:ok, %PaymentInitiation.GetPaymentResponse{}}

  """
  @spec get_payment(payment_id :: String.t(), Plaid.config()) ::
          {:ok, GetPaymentResponse.t()} | {:error, Plaid.Error.t()}
  def get_payment(payment_id, config) do
    Plaid.Client.call(
      "/payment_initiation/payment/get",
      %{payment_id: payment_id},
      GetPaymentResponse,
      config
    )
  end

  defmodule ListPaymentsResponse do
    @moduledoc """
    [Plaid API /payment_initiation/payment/list response schema.](https://plaid.com/docs/api/products/#payment_initiationpaymentlist)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            payments: [Payment.t()],
            next_cursor: String.t(),
            request_id: String.t()
          }

    defstruct [
      :payments,
      :next_cursor,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        payments: Castable.cast_list(Payment, generic_map["payments"]),
        next_cursor: generic_map["next_cursor"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  List payments.

  Does a `POST /payment_initiation/payment/list` call to get
  all created payments.

  ## Options

  * `:count` - The maximum number of payments to return.
  * `:cursor` - A date string in RFC 3339 format. Only payments created before the cursor will be returned.

  ## Examples

      PaymentInitiation.list_payments(
        client_id: "123",
        secret: "abc"
      )
      {:ok, %PaymentInitiation.ListPaymentsResponse{}}

  """
  @spec list_payments(options, Plaid.config()) ::
          {:ok, ListPaymentsResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{
               optional(:count) => integer(),
               optional(:cursor) => String.t()
             }
  def list_payments(options \\ %{}, config) do
    payload = Map.take(options, [:count, :cursor])

    Plaid.Client.call(
      "/payment_initiation/payment/list",
      payload,
      ListPaymentsResponse,
      config
    )
  end
end