Class: OpenBankingIO::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/open_banking_io/client.rb

Overview

Server-to-server client for open-banking.io.

Authenticates with an API key (X-Api-Key) and decrypts the zero-knowledge data envelopes locally with the exported private key – the service only ever returns ciphertext it cannot read.

Constant Summary collapse

DEFAULT_OPEN_TIMEOUT =
15
DEFAULT_READ_TIMEOUT =
60

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_base_url:, api_key:, private_key_pkcs8:) ⇒ Client

Returns a new instance of Client.

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
# File 'lib/open_banking_io/client.rb', line 54

def initialize(api_base_url:, api_key:, private_key_pkcs8:)
  raise ArgumentError, "api_base_url is required" if blank?(api_base_url)
  raise ArgumentError, "api_key is required" if blank?(api_key)
  raise ArgumentError, "private_key_pkcs8 is required" if blank?(private_key_pkcs8)

  @base_uri = URI.parse(api_base_url.to_s.sub(%r{/+\z}, "") + "/")
  @api_key = api_key
  @private_key = Envelope.load_private_key(private_key_pkcs8)
end

Class Method Details

.from_credentials(path_or_json) ⇒ Object

Builds a client from a credentials-bundle JSON string or a path to a bundle file.

Raises:

  • (ArgumentError)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/open_banking_io/client.rb', line 33

def self.from_credentials(path_or_json)
  raw = if File.file?(path_or_json.to_s)
          File.read(path_or_json)
        else
          path_or_json
        end

  bundle = JSON.parse(raw)
  api_base_url = bundle["apiBaseUrl"].to_s
  api_key = bundle["apiKey"]
  raise ArgumentError, "The credentials bundle has no apiKey" if api_key.nil? || api_key.empty?

  enc_key = bundle["encryptionKey"] || {}
  private_key = enc_key["privateKey"] || enc_key["privateKeyPkcs8B64"]
  if private_key.nil? || private_key.to_s.empty?
    raise ArgumentError, "The credentials bundle has no encryption private key"
  end

  new(api_base_url: api_base_url, api_key: api_key, private_key_pkcs8: private_key)
end

Instance Method Details

#get_accountsObject

Lists the user’s accounts with all sensitive fields decrypted.



65
66
67
# File 'lib/open_banking_io/client.rb', line 65

def get_accounts
  .map { |w| (w) }
end

#get_connectionsObject

Lists the user’s bank connections.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/open_banking_io/client.rb', line 83

def get_connections
  get_json("api/connections").map do |c|
    Connection.new(
      session_id: c["sessionId"] || "",
      aspsp_name: c["aspspName"] || "",
      aspsp_country: c["aspspCountry"] || "",
      valid_until: c["validUntil"],
      status: c["status"] || "",
      account_count: c["accountCount"] || 0,
      last_synced_at: c["lastSyncedAt"],
      psu_type: c["psuType"]
    )
  end
end

#get_transactions(account_id, from: nil, to: nil, limit: nil, offset: nil) ⇒ Object

Returns a page of an account’s statement, newest first, with decrypted fields.



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/open_banking_io/client.rb', line 70

def get_transactions(, from: nil, to: nil, limit: nil, offset: nil)
  params = {}
  params["from"] = from unless from.nil?
  params["to"] = to unless to.nil?
  params["limit"] = limit unless limit.nil?
  params["offset"] = offset unless offset.nil?

  page = get_json("api/accounts/#{}/transactions", params)
  items = (page["items"] || []).map { |t| map_transaction(t) }
  TransactionPage.new(items: items, total: page["total"] || 0)
end

#sync(account_id) ⇒ Object

Triggers an online sync of one account.

Decrypts that account’s Enable Banking uid and posts it, so the service can fetch fresh data without ever holding the uid in plaintext.

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/open_banking_io/client.rb', line 102

def sync()
   = .find { |a| a["id"] ==  }
  raise ArgumentError, "Account #{} not found" if .nil?

  uid = decrypt_uid()
  if uid.nil?
    raise ArgumentError, "Account has no active session (reconnect required) -- cannot sync"
  end

  result = post_json("api/accounts/#{}/sync", { "uid" => uid })
  SyncResult.new(
    new_transactions: result["newTransactions"] || 0,
    total_fetched: result["totalFetched"] || 0
  )
end

#sync_allObject

Triggers an online sync of every account that has an active session.



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/open_banking_io/client.rb', line 119

def sync_all
  items = []
  .each do |a|
    uid = decrypt_uid(a)
    items << { "accountId" => a["id"], "uid" => uid } unless uid.nil?
  end

  result = post_json("api/sync", { "items" => items })
  SyncAllResult.new(
    accounts: result["accounts"] || 0,
    new_transactions: result["newTransactions"] || 0
  )
end