defmodule Solaris.Onboarding.KYC do
@moduledoc """
KYC identification flows for persons and businesses.
Supports: VideoIdent (IDnow), Bankident, AutoIdent (re-KYC), Fourthline.
## Supported Identification Methods
- `"idnow"` — Video identification via IDnow
- `"bank_ident"` — Identification via existing bank account
- `"fourthline"` — Fourthline mobile SDK identification
- `"auto_ident"` — Automated re-identification
## Identification Flow
1. Create an identification session → get `url` to redirect user
2. User completes identification on external provider
3. Solaris sends `IDENTIFICATION` webhook on completion
4. Poll or handle webhook for final status
## Signing Flow
Some products require the person to digitally sign documents:
1. Create a signing via the appropriate identification method
2. User completes the signing
3. Receive `SIGNING_COMPLETED` webhook
"""
alias Solaris.{Client, Error}
# ── Person Identifications ─────────────────────────────────────────────────
@doc """
Creates a new identification session for a person.
Returns a URL to redirect the user to complete the identification.
## Examples
{:ok, identification} = Solaris.Onboarding.KYC.create_person_identification("cper_123", %{
method: "idnow",
language: "de"
})
redirect_to(identification["url"])
"""
@spec create_person_identification(String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_person_identification(person_id, attrs, opts \\ []) do
Client.post("/v1/persons/#{person_id}/identifications", attrs, opts)
end
@doc "Lists all identification sessions for a person."
@spec list_person_identifications(String.t(), keyword()) ::
{:ok, list(map())} | {:error, Error.t()}
def list_person_identifications(person_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/identifications", opts)
end
@doc "Retrieves a specific identification session."
@spec get_person_identification(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def get_person_identification(person_id, identification_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/identifications/#{identification_id}", opts)
end
# ── Person Signings ────────────────────────────────────────────────────────
@doc """
Initiates a document signing for a person.
## Examples
{:ok, signing} = Solaris.Onboarding.KYC.create_person_signing("cper_123", %{
document_id: "cdoc_456",
method: "idnow"
})
"""
@spec create_person_signing(String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_person_signing(person_id, attrs, opts \\ []) do
Client.post("/v1/persons/#{person_id}/signings", attrs, opts)
end
@doc "Lists all signings for a person."
@spec list_person_signings(String.t(), keyword()) ::
{:ok, list(map())} | {:error, Error.t()}
def list_person_signings(person_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/signings", opts)
end
@doc "Retrieves a specific signing."
@spec get_person_signing(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def get_person_signing(person_id, signing_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/signings/#{signing_id}", opts)
end
# ── Business Identifications ────────────────────────────────────────────────
@doc "Creates a business identification session."
@spec create_business_identification(String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_business_identification(business_id, attrs, opts \\ []) do
Client.post("/v1/businesses/#{business_id}/identifications", attrs, opts)
end
@doc "Lists all identification sessions for a business."
@spec list_business_identifications(String.t(), keyword()) ::
{:ok, list(map())} | {:error, Error.t()}
def list_business_identifications(business_id, opts \\ []) do
Client.get("/v1/businesses/#{business_id}/identifications", opts)
end
@doc "Retrieves a specific business identification session."
@spec get_business_identification(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def get_business_identification(business_id, identification_id, opts \\ []) do
Client.get("/v1/businesses/#{business_id}/identifications/#{identification_id}", opts)
end
# ── Business Screener ───────────────────────────────────────────────────────
@doc "Lists screener hits for a business (AML/sanctions results)."
@spec list_screener_hits(String.t(), keyword()) :: {:ok, list(map())} | {:error, Error.t()}
def list_screener_hits(business_id, opts \\ []) do
Client.get("/v1/businesses/#{business_id}/screener_hits", opts)
end
@doc "Retrieves a specific screener hit."
@spec get_screener_hit(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def get_screener_hit(business_id, hit_id, opts \\ []) do
Client.get("/v1/businesses/#{business_id}/screener_hits/#{hit_id}", opts)
end
@doc "Acknowledges/resolves a screener hit."
@spec resolve_screener_hit(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def resolve_screener_hit(business_id, hit_id, attrs, opts \\ []) do
Client.patch("/v1/businesses/#{business_id}/screener_hits/#{hit_id}", attrs, opts)
end
# ── IDnow Sandbox Helpers ──────────────────────────────────────────────────
@doc """
**Sandbox only.** Runs through the identification process using a robot.
Use `X-AUTOTEST-{robot_name}` as the person's first or last name.
"""
@spec sandbox_identify_with_robot(String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def sandbox_identify_with_robot(robot_name, opts \\ []) do
guard_sandbox!()
Client.get(
"/idnow/identify_with_robot",
Keyword.put(opts, :query, robot: robot_name)
)
end
@doc """
**Sandbox only.** Runs through the signing process using a robot.
"""
@spec sandbox_sign_with_robot(String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def sandbox_sign_with_robot(robot_name, opts \\ []) do
guard_sandbox!()
Client.get(
"/idnow/sign_with_robot",
Keyword.put(opts, :query, robot: robot_name)
)
end
# ── Private ────────────────────────────────────────────────────────────────
defp guard_sandbox! do
if Solaris.Config.environment() == :production do
raise ArgumentError, "This endpoint is only available in the Sandbox environment"
end
end
end