Implement fetching account balances

This commit is contained in:
2022-04-09 19:59:22 +01:00
parent dc1b11bf1e
commit d93eb8e936
7 changed files with 163 additions and 16 deletions

View File

@@ -87,3 +87,16 @@ class Sinks(util.Base):
# {"EUR": {"IBAN": "xxx", "BIC": "xxx"},
# "GBP": {"SORT": "04-04-04", "ACCOUNT": "1922-2993"}}
# self.markets.distribute_account_details(currencies, account_infos)
def get_total_usd(self):
"""
Get the total balance of our accounts in USD.
"""
total_nordigen = self.nordigen.get_total_map()
total_truelayer = self.truelayer.get_total_map()
# Yes, we can save an API call by merging but I think this is clearer
total_nordigen_usd = self.money.multiple_to_usd(total_nordigen)
total_truelayer_usd = self.money.multiple_to_usd(total_truelayer)
return total_truelayer_usd + total_nordigen_usd

View File

@@ -5,7 +5,7 @@ from twisted.internet.task import LoopingCall
import requests
from simplejson.errors import JSONDecodeError
from json import dumps, loads
from lib.serde.nordigen import TXRoot, AccessToken, Institutions, Agreement, Requisitions, AccountDetails
from lib.serde.nordigen import TXRoot, AccessToken, Institutions, Agreement, Requisitions, AccountDetails, AccountBalancesRoot
from serde import ValidationError
# Project imports
@@ -242,9 +242,6 @@ class Nordigen(util.Base):
continue
accounts = self.get_accounts(req["id"])
for account_id in accounts:
# if not account_id in self.banks:
# print("account_id", account_id, "not in self.banks!")
# continue
account_info = self.get_account(account_id)
if not account_info:
continue
@@ -281,7 +278,55 @@ class Nordigen(util.Base):
headers = {"accept": "application/json", "Authorization": f"Bearer {self.token}"}
path = f"{settings.Nordigen.Base}/accounts/{account_id}/transactions/"
r = requests.get(path, headers=headers)
obj = TXRoot.from_json(r.content)
try:
obj = TXRoot.from_json(r.content)
except ValidationError as err:
self.log.error(f"Validation error: {err}")
return
parsed = obj.to_dict()["transactions"]["booked"]
self.normalise_transactions(parsed)
return parsed
def get_balance(self, account_id):
"""
Get the balance and currency of an account.
:param account_id: the account ID
:return: tuple of (currency, amount)
:rtype: tuple
"""
headers = {"accept": "application/json", "Authorization": f"Bearer {self.token}"}
path = f"{settings.Nordigen.Base}/accounts/{account_id}/balances/"
r = requests.get(path, headers=headers)
try:
obj = AccountBalancesRoot.from_json(r.content)
except ValidationError as err:
self.log.error(f"Validation error: {err}")
return
parsed = obj.to_dict()["balances"]
total = 0
currency = None
for entry in parsed:
if currency:
if not currency == entry["balanceAmount"]["currency"]:
self.log.error("Different currencies in balance query.")
return
total += float(entry["balanceAmount"]["amount"])
currency = entry["balanceAmount"]["currency"]
return (currency, total)
def get_total_map(self):
"""
Return a dictionary keyed by currencies with the amounts as values.
:return: dict keyed by currency, values are amounts
:rtype: dict
"""
totals = {}
for account_id in self.banks:
currency, amount = self.get_balance(account_id)
if not amount:
continue
if currency in totals:
totals[currency] += amount
else:
totals[currency] = amount
return totals

View File

@@ -6,7 +6,9 @@ import requests
from simplejson.errors import JSONDecodeError
from time import time
from json import dumps, loads
from lib.serde.truelayer import AccountBalancesRoot
import urllib
from serde import ValidationError
# Project imports
from settings import settings
@@ -274,8 +276,55 @@ class TrueLayer(util.Base):
parsed = r.json()
except JSONDecodeError:
self.log.error(f"Error parsing transactions response: {r.content}")
return False
return (False, False)
if "results" in parsed:
return parsed["results"]
else:
return False
return (False, False)
def get_balance(self, bank, account_id):
"""
Get the balance of an account.
:param bank: the bank to check
:param account_id: the account ID
:return: tuple of (currency, amount)
:rtype: tuple
"""
token = self.get_key(bank)
headers = {"Authorization": f"Bearer {token}"}
path = f"{settings.TrueLayer.DataBase}/accounts/{account_id}/balance"
r = requests.get(path, headers=headers)
try:
obj = AccountBalancesRoot.from_json(r.content)
except ValidationError as err:
self.log.error(f"Validation error: {err}")
return
parsed = obj.to_dict()["results"]
total = 0
currency = None
for entry in parsed:
if currency:
if not currency == entry["currency"]:
self.log.error("Different currencies in balance query.")
return
total += entry["available"]
currency = entry["currency"]
return (currency, total)
def get_total_map(self):
"""
Return a dictionary keyed by currencies with the amounts as values.
:return: dict keyed by currency, values are amounts
:rtype: dict
"""
totals = {}
for bank in self.banks:
for account_id in self.banks[bank]:
currency, amount = self.get_balance(bank, account_id)
if not amount:
continue
if currency in totals:
totals[currency] += amount
else:
totals[currency] = amount
return totals