Packages

Production-grade Elixir client for the Solaris Embedded Finance API. Covers Onboarding (Persons & Businesses), KYC, Digital Banking, SEPA Transfers, Cards, Lending, and Webhooks with full telemetry, retries, rate-limiting, OAuth2 token management, and typed error handling.

Current section

Files

Jump to
solaris lib solaris banking sepa.ex
Raw

lib/solaris/banking/sepa.ex

defmodule Solaris.Banking.SEPA do
@moduledoc """
SEPA payment operations: Credit Transfers (SCT), Instant Credit Transfers,
and Direct Debits (SDD).
## SEPA Credit Transfer (SCT)
Standard EU credit transfers; typically settle next business day.
## Instant SCT
Real-time transfers (within seconds). Requires:
1. Checking recipient bank reachability first
2. 2FA / change request authorization
3. Webhook `INSTANT_SEPA_CREDIT_TRANSFER_FAILED` for failures
## SEPA Direct Debit (SDD)
Pull payments from customer accounts. Requires:
- A signed mandate from the customer
- Mandate must be active before first SDD
## Amounts
All amounts are in **cents** (smallest currency unit). EUR 10.00 = `1000`.
"""
alias Solaris.{Client, Error, Pagination}
# ── SEPA Credit Transfers (Standard) ───────────────────────────────────────
@doc """
Creates a SEPA Credit Transfer for a person's account.
Triggers a change request (SCA) — returns `202` with `change_request_id`
if not using a trusted IBAN.
## Examples
{:ok, transfer} = Solaris.Banking.SEPA.create_person_credit_transfer(
"cper_123",
"cacc_456",
%{
recipient_iban: "DE89370400440532013000",
recipient_name: "Max Mustermann",
amount: 10_000,
currency: "EUR",
reference: "Invoice #123",
end_to_end_id: "E2E-REF-123"
}
)
"""
@spec create_person_credit_transfer(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_person_credit_transfer(person_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/persons/#{person_id}/accounts/#{account_id}/transactions/sepa_credit_transfer",
attrs,
opts
)
end
@doc "Lists SEPA credit transfers for a person's account."
@spec list_person_credit_transfers(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def list_person_credit_transfers(person_id, account_id, opts \\ []) do
query = Pagination.to_query(opts)
Client.get(
"/v1/persons/#{person_id}/accounts/#{account_id}/transactions/sepa_credit_transfer",
Keyword.put(opts, :query, query)
)
end
@doc """
Creates a SEPA Credit Transfer for a business account.
## Examples
{:ok, transfer} = Solaris.Banking.SEPA.create_business_credit_transfer(
"cbac_123",
"cacc_456",
%{
recipient_iban: "DE89370400440532013000",
recipient_name: "Vendor GmbH",
amount: 250_000,
currency: "EUR",
reference: "PO-2024-001"
}
)
"""
@spec create_business_credit_transfer(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_business_credit_transfer(business_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/businesses/#{business_id}/accounts/#{account_id}/transactions/sepa_credit_transfer",
attrs,
opts
)
end
@doc "Lists SEPA credit transfers for a business account."
@spec list_business_credit_transfers(String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def list_business_credit_transfers(business_id, account_id, opts \\ []) do
query = Pagination.to_query(opts)
Client.get(
"/v1/businesses/#{business_id}/accounts/#{account_id}/transactions/sepa_credit_transfer",
Keyword.put(opts, :query, query)
)
end
# ── Instant SEPA Credit Transfers ──────────────────────────────────────────
@doc """
Checks if a recipient bank is reachable for Instant SEPA.
Must be called before initiating an Instant SCT.
## Examples
{:ok, result} = Solaris.Banking.SEPA.check_instant_reachability("DE89370400440532013000")
if result["reachable"], do: proceed_with_instant_transfer()
"""
@spec check_instant_reachability(String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def check_instant_reachability(iban, opts \\ []) do
Client.get(
"/v1/instant_sepa_credit_transfer_reachability",
Keyword.put(opts, :query, iban: iban)
)
end
@doc """
Creates an Instant SEPA Credit Transfer for a person.
Requires 2FA/change request. Monitor `INSTANT_SEPA_CREDIT_TRANSFER_FAILED`
webhook for failures.
## Examples
{:ok, transfer} = Solaris.Banking.SEPA.create_instant_person_transfer(
"cper_123",
"cacc_456",
%{
recipient_iban: "DE89370400440532013000",
recipient_name: "Max Mustermann",
amount: 5_000,
currency: "EUR",
reference: "Fast payment"
}
)
"""
@spec create_instant_person_transfer(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_instant_person_transfer(person_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/persons/#{person_id}/accounts/#{account_id}/transactions/instant_sepa_credit_transfer",
attrs,
opts
)
end
@doc "Creates an Instant SEPA Credit Transfer for a business."
@spec create_instant_business_transfer(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_instant_business_transfer(business_id, account_id, attrs, opts \\ []) do
url =
"/v1/businesses/#{business_id}/accounts/#{account_id}" <>
"/transactions/instant_sepa_credit_transfer"
Client.post(url, attrs, opts)
end
@doc "Creates an Instant SCT to a reference account."
@spec create_instant_reference_account_transfer(String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_instant_reference_account_transfer(account_id, attrs, opts \\ []) do
body = Map.merge(attrs, %{rail: "sepa_instant"})
url = "/v1/accounts/#{account_id}/transactions/reference_account_payout"
Client.post(url, body, opts)
end
# ── SEPA Direct Debits ──────────────────────────────────────────────────────
@doc """
Creates a SEPA Direct Debit mandate for a person.
The mandate must be signed before the first SDD can be initiated.
## Examples
{:ok, mandate} = Solaris.Banking.SEPA.create_person_mandate(
"cper_123",
"cacc_456",
%{
creditor_iban: "DE89370400440532013000",
creditor_name: "Creditor GmbH",
mandate_reference: "MREF-001",
creditor_identifier: "DE98ZZZ09999999999"
}
)
"""
@spec create_person_mandate(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_person_mandate(person_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/persons/#{person_id}/accounts/#{account_id}/mandates",
attrs,
opts
)
end
@doc "Lists mandates for a person's account."
@spec list_person_mandates(String.t(), String.t(), keyword()) ::
{:ok, list(map())} | {:error, Error.t()}
def list_person_mandates(person_id, account_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/accounts/#{account_id}/mandates", opts)
end
@doc "Retrieves a specific mandate."
@spec get_person_mandate(String.t(), String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def get_person_mandate(person_id, account_id, mandate_id, opts \\ []) do
Client.get("/v1/persons/#{person_id}/accounts/#{account_id}/mandates/#{mandate_id}", opts)
end
@doc "Signs a mandate, activating it for SDD use."
@spec sign_person_mandate(String.t(), String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def sign_person_mandate(person_id, account_id, mandate_id, opts \\ []) do
Client.post(
"/v1/persons/#{person_id}/accounts/#{account_id}/mandates/#{mandate_id}/sign",
%{},
opts
)
end
@doc "Creates a SEPA Direct Debit from a person's account."
@spec create_person_direct_debit(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_person_direct_debit(person_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/persons/#{person_id}/accounts/#{account_id}/transactions/sepa_direct_debit",
attrs,
opts
)
end
@doc "Creates a SEPA Direct Debit mandate for a business."
@spec create_business_mandate(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_business_mandate(business_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/businesses/#{business_id}/accounts/#{account_id}/mandates",
attrs,
opts
)
end
@doc "Lists mandates for a business account."
@spec list_business_mandates(String.t(), String.t(), keyword()) ::
{:ok, list(map())} | {:error, Error.t()}
def list_business_mandates(business_id, account_id, opts \\ []) do
Client.get("/v1/businesses/#{business_id}/accounts/#{account_id}/mandates", opts)
end
@doc "Creates a SEPA Direct Debit from a business account."
@spec create_business_direct_debit(String.t(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t()}
def create_business_direct_debit(business_id, account_id, attrs, opts \\ []) do
Client.post(
"/v1/businesses/#{business_id}/accounts/#{account_id}/transactions/sepa_direct_debit",
attrs,
opts
)
end
end