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