Skip to main content

lib/rapyd/services/partner.ex

defmodule Rapyd.Services.Partner do
  @moduledoc """
  Rapyd Partner / PayFac — KYB onboarding and sub-merchant management.

  Covers: Organizations, KYB Applications, Settlement Bank Accounts,
  Application Templates, Industries, and Offerings.
  """

  alias Rapyd.{Client, Error}

  @v1 "/v1"

  defp http(%Client{http_client: mod} = client, method, path, body \\ nil) do
    mod.request(client, method, path, body, [])
  end

  defp qs(params) when map_size(params) == 0, do: ""

  defp qs(params) do
    params
    |> Enum.reject(fn {_, v} -> is_nil(v) end)
    |> Enum.map_join("&", fn {k, v} -> "#{k}=#{URI.encode_www_form(to_string(v))}" end)
    |> then(&if(&1 == "", do: "", else: "?" <> &1))
  end

  # ── Organizations ──────────────────────────────────────────────────────────

  @doc "Create a partner organization (merchant)."
  @spec create_organization(Client.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def create_organization(client, params) do
    http(client, :post, "#{@v1}/organizations", params)
  end

  @doc "Retrieve an organization."
  @spec get_organization(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def get_organization(client, org_id) do
    http(client, :get, "#{@v1}/organizations/#{org_id}")
  end

  @doc "List organizations."
  @spec list_organizations(Client.t(), map()) :: {:ok, list()} | {:error, Error.t()}
  def list_organizations(client, filters \\ %{}) do
    http(client, :get, "#{@v1}/organizations#{qs(filters)}")
  end

  @doc "Update an organization."
  @spec update_organization(Client.t(), String.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def update_organization(client, org_id, params) do
    http(client, :put, "#{@v1}/organizations/#{org_id}", params)
  end

  # ── KYB Applications ──────────────────────────────────────────────────────

  @doc "Create a KYB application."
  @spec create_application(Client.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def create_application(client, params) do
    http(client, :post, "#{@v1}/applications", params)
  end

  @doc "Retrieve a KYB application."
  @spec get_application(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def get_application(client, app_id) do
    http(client, :get, "#{@v1}/applications/#{app_id}")
  end

  @doc "List KYB applications."
  @spec list_applications(Client.t(), map()) :: {:ok, list()} | {:error, Error.t()}
  def list_applications(client, filters \\ %{}) do
    http(client, :get, "#{@v1}/applications#{qs(filters)}")
  end

  @doc "Update a KYB application."
  @spec update_application(Client.t(), String.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def update_application(client, app_id, params) do
    http(client, :put, "#{@v1}/applications/#{app_id}", params)
  end

  @doc "Submit a KYB application for review."
  @spec submit_application(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def submit_application(client, app_id) do
    http(client, :post, "#{@v1}/applications/#{app_id}/submit")
  end

  @doc "Upload a KYB document to an application."
  @spec upload_application_document(Client.t(), String.t(), map()) ::
          {:ok, map()} | {:error, Error.t()}
  def upload_application_document(client, app_id, params) do
    http(client, :post, "#{@v1}/applications/#{app_id}/documents", params)
  end

  # ── Settlement Bank Accounts ───────────────────────────────────────────────

  @doc "Link a settlement bank account to a merchant wallet."
  @spec create_settlement_bank_account(Client.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def create_settlement_bank_account(client, params) do
    http(client, :post, "#{@v1}/settlement_bank_accounts", params)
  end

  @doc "Retrieve a settlement bank account."
  @spec get_settlement_bank_account(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def get_settlement_bank_account(client, account_id) do
    http(client, :get, "#{@v1}/settlement_bank_accounts/#{account_id}")
  end

  @doc "List settlement bank accounts."
  @spec list_settlement_bank_accounts(Client.t(), map()) :: {:ok, list()} | {:error, Error.t()}
  def list_settlement_bank_accounts(client, filters \\ %{}) do
    http(client, :get, "#{@v1}/settlement_bank_accounts#{qs(filters)}")
  end

  @doc "Delete a settlement bank account."
  @spec delete_settlement_bank_account(Client.t(), String.t()) ::
          {:ok, map()} | {:error, Error.t()}
  def delete_settlement_bank_account(client, account_id) do
    http(client, :delete, "#{@v1}/settlement_bank_accounts/#{account_id}")
  end

  # ── Reference data ────────────────────────────────────────────────────────

  @doc "List supported KYB application templates."
  @spec list_application_templates(Client.t(), map()) :: {:ok, list()} | {:error, Error.t()}
  def list_application_templates(client, params \\ %{}) do
    http(client, :get, "#{@v1}/application_templates#{qs(params)}")
  end

  @doc "List industries (MCC codes) available for partner merchants."
  @spec list_industries(Client.t()) :: {:ok, list()} | {:error, Error.t()}
  def list_industries(client) do
    http(client, :get, "#{@v1}/industries")
  end

  @doc "List partner product offerings."
  @spec list_offerings(Client.t()) :: {:ok, list()} | {:error, Error.t()}
  def list_offerings(client) do
    http(client, :get, "#{@v1}/offerings")
  end

  @doc "Retrieve a specific product offering."
  @spec get_offering(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def get_offering(client, offering_id) do
    http(client, :get, "#{@v1}/offerings/#{offering_id}")
  end

  # ── Approve / Reject Application ──────────────────────────────────────────

  @doc "Approve a KYB application (internal/partner-admin action)."
  @spec approve_application(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def approve_application(client, app_id) do
    http(client, :post, "#{@v1}/applications/#{app_id}/approve")
  end

  @doc "Reject a KYB application."
  @spec reject_application(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def reject_application(client, app_id) do
    http(client, :post, "#{@v1}/applications/#{app_id}/reject")
  end

  @doc "Get a KYB application by wallet ID."
  @spec get_application_by_wallet(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
  def get_application_by_wallet(client, ewallet_id) do
    http(client, :get, "#{@v1}/applications?ewallet=#{URI.encode_www_form(ewallet_id)}")
  end

  # ── Update Settlement Bank Account ────────────────────────────────────────

  @doc "Update a settlement bank account."
  @spec update_settlement_bank_account(Client.t(), String.t(), map()) ::
          {:ok, map()} | {:error, Error.t()}
  def update_settlement_bank_account(client, account_id, params) do
    http(client, :put, "#{@v1}/settlement_bank_accounts/#{account_id}", params)
  end

  @doc "Upload a bank verification file for a settlement account."
  @spec upload_bank_verification_file(Client.t(), map()) :: {:ok, map()} | {:error, Error.t()}
  def upload_bank_verification_file(client, params) do
    http(client, :post, "#{@v1}/settlement_bank_accounts/verification", params)
  end

  @doc "Get an application template for a given country and application type."
  @spec get_application_template(Client.t(), String.t(), String.t()) ::
          {:ok, map()} | {:error, Error.t()}
  def get_application_template(client, country, app_type) do
    http(client, :get, "#{@v1}/application_templates?country=#{country}&type=#{app_type}")
  end

  # ── Merchant Account Scoping ──────────────────────────────────────────────

  @doc """
  Returns a new client struct scoped to a specific merchant organisation ID.

  The organisation ID is forwarded as the `merchant_account_id` query param on
  every request made with the returned client.

  ## Example

      partner_client = Partner.with_merchant_account_id(client, "org_abc123")
      Partner.list_organizations(partner_client)
  """
  @spec with_merchant_account_id(Client.t(), String.t()) :: Client.t()
  def with_merchant_account_id(%Client{} = client, org_id) do
    %Client{client | merchant_account_id: org_id}
  end
end