Skip to content

fintech-sdk/yapily_client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

YapilyClient

Hex.pm Hex Docs API v12

Unofficial Elixir client for the Yapily Open Banking API v12.
Connect to 2,000+ banks across the UK and Europe.

Installation

# mix.exs
{:yapily_client, "~> 1.0.0"}

Configuration

# config/runtime.exs — read credentials at runtime, never hard-code
config :yapily_client,
  app_key:    System.fetch_env!("YAPILY_APP_KEY"),
  app_secret: System.fetch_env!("YAPILY_APP_SECRET")

Or build a config struct directly:

config = YapilyClient.Config.new!(
  app_key:    System.fetch_env!("YAPILY_APP_KEY"),
  app_secret: System.fetch_env!("YAPILY_APP_SECRET")
)

Quick start

# 1. List supported banks
{:ok, institutions} = YapilyClient.Institutions.list(config)

# 2. Start an account authorisation (redirect flow)
{:ok, auth} = YapilyClient.Authorisations.create_account(config, %{
  institution_id:      "monzo",
  application_user_id: "your-internal-user-id",
  callback:            "https://yourapp.com/callback",
  feature_scope_list:  ["ACCOUNTS", "TRANSACTIONS"],
  one_time_token:      true
})
# Redirect the user to:
auth.authorisation_url

# 3. Exchange the token from your callback URL
{:ok, consent} = YapilyClient.Consents.exchange_one_time_token(config, token)

# 4. Read accounts
{:ok, accounts} = YapilyClient.Accounts.list(config, consent.id)

# 5. Stream transactions lazily
YapilyClient.Transactions.stream(config, consent.id, account_id)
|> Stream.filter(&(&1.currency == "GBP"))
|> Enum.take(100)

Payments

Domestic

{:ok, payment} = YapilyClient.Payments.create(config, consent_token, %{
  type:                    YapilyClient.payment_type(:domestic),
  payment_idempotency_id:  YapilyClient.idempotency_key("invoice-001"),
  amount:                  100.00,
  currency:                "GBP",
  recipient: %{
    name: "Jane Smith",
    account_identifications: [
      %{type: "SORT_CODE",      identification: "200000"},
      %{type: "ACCOUNT_NUMBER", identification: "55779911"}
    ]
  },
  reference: "Invoice-001"
})

International

{:ok, _} = YapilyClient.Payments.create(config, consent_token, %{
  type:                   YapilyClient.payment_type(:international),
  payment_idempotency_id: YapilyClient.idempotency_key(),
  amount:    500.00,
  currency:  "GBP",
  recipient: %{
    name: "Maria Müller",
    address: %{country: "DE"},
    account_identifications: [
      %{type: "IBAN", identification: "DE89370400440532013000"},
      %{type: "BIC",  identification: "COBADEFFXXX"}
    ]
  },
  international_payment: %{
    currency_of_transfer: "EUR",
    charge_bearer: "DEBT",   # DEBT | CRED | SHAR | FOLLOWING
    priority:      "NORMAL", # NORMAL | URGENT
    purpose:       "GDDS"    # ISO 20022 purpose code
  }
})

Periodic (standing order)

{:ok, _} = YapilyClient.Payments.create(config, consent_token, %{
  type:                    YapilyClient.payment_type(:domestic_periodic),
  payment_idempotency_id:  YapilyClient.idempotency_key(),
  payment_date_time:       "2025-01-01T09:00:00Z",
  amount:   1_200.00,
  currency: "GBP",
  recipient: %{name: "Landlord", account_identifications: [...]},
  periodic_payment: %{
    frequency:         YapilyClient.frequency(:monthly),
    execution_day:     1,
    interval_month:    1,
    number_of_payments: 12          # or: final_payment_date_time: "2025-12-01T09:00:00Z"
  }
})

Variable Recurring Payments (VRP)

# 1. Authorise once
{:ok, vrp} = YapilyClient.VRP.create_sweeping_authorisation(config, %{
  institution_id:      "monzo",
  application_user_id: "user-id",
  callback:            "https://yourapp.com/vrp-callback",
  control_parameters: %{
    currency:                  "GBP",
    maximum_individual_amount: 500.00,
    periodic_limits: [
      %{maximum_amount: 2_000.00, currency: "GBP",
        period_type: "Month", period_alignment: "Calendar"}
    ]
  }
})
# Redirect user to: vrp.authorisation_url

# 2. Sweep repeatedly — no re-auth
{:ok, payment} = YapilyClient.VRP.create_payment(config, consent_token, vrp.id, %{
  amount:    250.00,
  currency:  "GBP",
  recipient: %{name: "Savings", account_identifications: [...]},
  reference: "Monthly Sweep"
})

Error handling

case YapilyClient.Accounts.list(config, consent_token) do
  {:ok, accounts} ->
    accounts

  {:error, err} when YapilyClient.Error.not_found?(err) ->
    handle_not_found()

  {:error, err} when YapilyClient.Error.unauthorized?(err) ->
    handle_unauthorized()

  {:error, err} when YapilyClient.Error.rate_limited?(err) ->
    handle_rate_limit()

  {:error, err} when YapilyClient.Error.vop_rejected?(err) ->
    handle_vop_failure()

  {:error, err} when YapilyClient.Error.insufficient_funds?(err) ->
    handle_insufficient_funds()

  {:error, err} when YapilyClient.Error.retryable?(err) ->
    retry_later()

  {:error, %YapilyClient.Error.APIError{status: s, code: c, trace_id: t}} ->
    Logger.error("API error #{s} #{c} (trace: #{t})")

  {:error, %YapilyClient.Error.EnhancedAPIError{issues: issues, tracing_id: t}} ->
    Enum.each(issues, &Logger.error("[#{&1.code}] #{&1.type}: #{&1.message}"))
    Logger.error("trace: #{t}")

  {:error, %YapilyClient.Error.ValidationError{field: f, message: m}} ->
    {:error, "#{f}: #{m}"}
end

Consent polling (Fibonacci back-off)

case YapilyClient.ConsentPoller.wait_for_authorisation(config, consent_id) do
  {:ok, %{status: "AUTHORIZED"} = consent} -> proceed(consent)
  {:ok, %{status: status}}                 -> handle_failure(status)
  {:error, :timed_out}                     -> show_timeout()
end

Delays: 1 s → 1 s → 2 s → 3 s → 5 s → 8 s → 13 s → 21 s → 34 s (~88 s total).

Webhook verification

defmodule MyAppWeb.WebhookController do
  use MyAppWeb, :controller

  def handle(conn, _params) do
    raw_body  = conn.assigns.raw_body
    signature = get_req_header(conn, "x-yapily-signature") |> List.first()
    secret    = System.get_env("YAPILY_WEBHOOK_SECRET")

    case YapilyClient.Webhook.verify(raw_body, secret, signature) do
      :ok ->
        process_event(conn.body_params)
        send_resp(conn, 200, "ok")

      {:error, reason} ->
        send_resp(conn, 401, Atom.to_string(reason))
    end
  end
end

Testing

# config/test.exs
config :yapily_client, http_client: YapilyClient.HTTP.MockClient

# test/test_helper.exs
Mox.defmock(YapilyClient.HTTP.MockClient, for: YapilyClient.HTTP.Behaviour)

# In your test
import Mox

test "lists accounts" do
  config = YapilyClient.Config.new!(app_key: "k", app_secret: "s")

  expect(YapilyClient.HTTP.MockClient, :request, fn _config, :get, "/accounts", _opts ->
    {:ok, %{"data" => [%{"id" => "acc-1", "type" => "CURRENT", ...}]}}
  end)

  assert {:ok, [acc]} = YapilyClient.Accounts.list(config, "consent-token")
  assert acc.id == "acc-1"
end

Service reference

Module Methods Description
YapilyClient.Institutions list/1, get/2 Supported banks
YapilyClient.Accounts list/2, get/3 Account detail
YapilyClient.Transactions list/4, list_all/4, stream/4, list_real_time/3 Transaction history
YapilyClient.Payments create/3, get/3 All 6 payment types
YapilyClient.BulkPayments create/3, get_status/3 Batch payments
YapilyClient.Consents list/2, get/2, delete/3, extend/3, exchange_oauth2_code/2, exchange_one_time_token/2 Consent lifecycle
YapilyClient.Authorisations 14 functions All auth flows
YapilyClient.FinancialData 10 functions Balances, statements, identity
YapilyClient.Users list/2, create/2, get/2, delete/2, update/3 PSU management
YapilyClient.VRP 5 functions Variable Recurring Payments
YapilyClient.Notifications 4 functions Event subscriptions
YapilyClient.DataPlus 6 functions Transaction enrichment
YapilyClient.HostedPages 11 functions Yapily-hosted UIs
YapilyClient.Constraints 2 functions Institution constraints
YapilyClient.ApplicationManagement 8 functions App management
YapilyClient.Webhooks 5 functions Webhook management
YapilyClient.Beneficiaries 11 functions VoP flows
YapilyClient.Validate get_identity/2, validate_ownership/4 Ownership verification
YapilyClient.ConsentPoller wait_for_authorisation/2,3 Fibonacci polling
YapilyClient.Webhook verify/3, valid?/3 Signature verification

License

MIT

About

Production grade Elixir client for the Yapily Open Banking API v12 — payments, data, VRP, hosted pages, enrichment, and more.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages