Solaris

Hex.pmDocsLicense: MIT

Production-grade Elixir client for the Solaris Embedded Finance API.

Covers the full API surface — Onboarding, KYC, Digital Banking, SEPA Transfers, Cards, Lending, and Webhooks — with idiomatic Elixir: typed errors, auto-paginating streams, telemetry, OAuth2 token management, retries, and rate limiting.

Features

Installation

def deps do
[
{:solaris, "~> 1.0"}
]
end

Configuration

# config/runtime.exs
config :solaris,
client_id: System.fetch_env!("SOLARIS_CLIENT_ID"),
client_secret: System.fetch_env!("SOLARIS_CLIENT_SECRET"),
environment: :sandbox, # :sandbox | :production
timeout: 30_000, # HTTP timeout in ms
max_retries: 3, # Retry count on transient errors
webhook_secret: System.get_env("SOLARIS_WEBHOOK_SECRET")

Quick Start

# 1. Onboard a person
{:ok, person} = Solaris.Onboarding.Persons.create(%{
first_name: "Jane",
last_name: "Doe",
email: "jane@example.com",
birth_date: "1990-01-15",
nationality: "DE",
address: %{
line_1: "Unter den Linden 1",
postal_code: "10117",
city: "Berlin",
country: "DE"
}
})
# 2. Register and verify mobile number (for 2FA)
{:ok, _} = Solaris.Onboarding.Persons.create_mobile_number(person["id"], "+491701234567")
{:ok, _} = Solaris.Onboarding.Persons.authorize_mobile_number(person["id"])
{:ok, _} = Solaris.Onboarding.Persons.confirm_mobile_number(person["id"], "123456")
# 3. Start KYC
{:ok, identification} = Solaris.Onboarding.KYC.create_person_identification(person["id"], %{
method: "idnow"
})
# redirect customer to identification["url"]
# 4. After KYC — check account
{:ok, accounts} = Solaris.Banking.Accounts.list_person_accounts(person["id"])
account = List.first(accounts)
# 5. Initiate a SEPA transfer (returns 202 + change_request_id for SCA)
{:ok, result} = Solaris.Banking.SEPA.create_person_credit_transfer(
person["id"],
account["id"],
%{
recipient_iban: "DE89370400440532013000",
recipient_name: "Max Mustermann",
amount: 10_000,
currency: "EUR",
reference: "Invoice #123"
}
)
# 6. Complete the SCA change request via SMS OTP
change_request_id = result["change_request_id"]
{:ok, _} = Solaris.ChangeRequests.authorize_with_sms(change_request_id)
{:ok, _} = Solaris.ChangeRequests.confirm_with_otp(change_request_id, "483920")

Error Handling

All functions return {:ok, result} or {:error, %Solaris.Error{}}.

case Solaris.Banking.Accounts.get_balance(account_id) do
{:ok, %{"balance" => %{"amount" => amount, "currency" => currency}}} ->
IO.puts("Balance: #{amount} #{currency}")
{:error, %Solaris.Error{code: :not_found}} ->
Logger.warning("Account not found")
{:error, %Solaris.Error{code: :unauthorized, request_id: req_id}} ->
Logger.error("Auth failed, request_id: #{req_id}")
{:error, %Solaris.Error{code: :rate_limited}} ->
# Automatic retries handle this, but you can also catch it
:backoff
end

Pagination & Streaming

# Fetch one page
{:ok, page} = Solaris.Onboarding.Persons.list(per_page: 50)
# Stream ALL persons across all pages (lazy)
Solaris.Onboarding.Persons.stream()
|> Stream.filter(fn p -> p["status"] == "ACTIVE" end)
|> Stream.each(fn p -> process(p) end)
|> Stream.run()
# Fetch all into memory (use with care for large datasets)
{:ok, all_persons} = Solaris.Pagination.all(fn cursor ->
Solaris.Onboarding.Persons.list(after: cursor)
|> case do
{:ok, page} -> {:ok, Solaris.Pagination.from_response(page)}
err -> err
end
end)

Webhooks

Phoenix Integration

# endpoint.ex — preserve raw body for signature verification
plug Plug.Parsers,
parsers: [:json],
json_decoder: Jason,
body_reader: {Solaris.Webhooks.Plug.BodyReader, :read_body, []}
# router.ex
post "/webhooks/solaris", Solaris.Webhooks.Plug,
handler: MyApp.SolarisHandler,
secret: System.get_env("SOLARIS_WEBHOOK_SECRET")
# my_app/solaris_handler.ex
defmodule MyApp.SolarisHandler do
@behaviour Solaris.Webhooks.Handler
@impl true
def handle_event("BOOKING", %{"booking" => booking}, _event) do
MyApp.Ledger.record(booking)
:ok
end
def handle_event("IDENTIFICATION", payload, _event) do
case payload["identification"]["status"] do
"successful" -> MyApp.KYC.complete(payload["person_id"])
"failed" -> MyApp.KYC.reject(payload["person_id"])
_ -> :ok
end
end
# Always add a catch-all for forward compatibility
def handle_event(_type, _payload, _event), do: :ok
end

Manual Verification

raw_body = get_raw_body(conn)
signature = get_req_header(conn, "solaris-webhook-signature")
case Solaris.Webhooks.verify_and_parse(raw_body, signature, webhook_secret) do
{:ok, event} ->
Solaris.Webhooks.dispatch(event, MyApp.SolarisHandler)
send_resp(conn, 200, "ok")
{:error, :invalid_signature} ->
send_resp(conn, 401, "unauthorized")
end

Telemetry

# Attach the default logger (for development)
Solaris.Telemetry.attach_default_logger(:debug)
# Use with telemetry_metrics
defmodule MyApp.Metrics do
def metrics do
Solaris.Telemetry.metrics()
# Returns distribution/counter metrics for all Solaris events
end
end

Events emitted:

Consumer Loan Flow

# 1. Create application
{:ok, app} = Solaris.Lending.ConsumerLoans.create_application(person_id, %{
amount: 10_000_00, currency: "EUR", term_months: 36, purpose: "CONSUMER_GOODS"
})
# 2. Wait for CONSUMER_LOAN_APPLICATION webhook with status "OFFERED"
# 3. Download SECCI (legally required before signing)
{:ok, pdf} = Solaris.Lending.ConsumerLoans.get_secci(person_id, app["id"], offer_id)
present_to_customer(pdf)
# 4. Get final contract
{:ok, contract_pdf} = Solaris.Lending.ConsumerLoans.get_contract(person_id, app["id"], offer_id)
# 5. Create loan (after customer signs)
{:ok, loan} = Solaris.Lending.ConsumerLoans.create_loan(person_id, app["id"], %{
offer_id: offer_id, signing_id: signing_id
})

Sandbox Testing

# Simulate card authorization (3DS flow)
Solaris.Cards.sandbox_simulate_authorization(card_id, %{amount: 5000, currency: "EUR"})
# Robot-based KYC identification
Solaris.Onboarding.KYC.sandbox_identify_with_robot("AUTOTEST-APPROVED")
# Simulate expired ID document
Solaris.Onboarding.Persons.simulate_id_document_expiry(person_id)
# Set cash operation status
Solaris.Banking.Transactions.sandbox_set_cash_operation_status(account_id, op_id, "PAID")

Module Reference

ModuleDescription
Solaris.Onboarding.PersonsPerson CRUD, mobile numbers, tax IDs, documents
Solaris.Onboarding.BusinessesBusiness CRUD, legal reps, beneficial owners
Solaris.Onboarding.KYCIdentification sessions, signings, screener hits
Solaris.Banking.AccountsAccounts, balances, bookings, savings, IBANs
Solaris.Banking.SEPASCT, Instant SCT, SDD mandates
Solaris.Banking.TransactionsCash ops, top-ups, remittances, payouts
Solaris.CardsIssuance, lifecycle, tokenization, 3DS
Solaris.Lending.LoansLoan servicing, repayment, dunning
Solaris.Lending.ConsumerLoansLoan applications, offers, SECCI
Solaris.Lending.OverdraftOverdraft facilities
Solaris.Lending.SplitpayBNPL / installment plans
Solaris.Lending.TradeFinanceBusiness trade credit lines
Solaris.ChangeRequestsSCA/2FA completion flow
Solaris.WebhooksSignature verification, dispatch
Solaris.Webhooks.PlugPhoenix/Plug webhook endpoint
Solaris.PaginationCursor pagination, streaming
Solaris.TelemetryTelemetry events and metrics

License

MIT — see LICENSE.