|
|
@ -5,6 +5,8 @@ from twisted.internet.task import LoopingCall
|
|
|
|
import requests
|
|
|
|
import requests
|
|
|
|
from simplejson.errors import JSONDecodeError
|
|
|
|
from simplejson.errors import JSONDecodeError
|
|
|
|
from json import dumps, loads
|
|
|
|
from json import dumps, loads
|
|
|
|
|
|
|
|
from lib.serde.nordigen import TXRoot, AccessToken, Institutions, Agreement, Requisitions, AccountDetails
|
|
|
|
|
|
|
|
from serde import ValidationError
|
|
|
|
|
|
|
|
|
|
|
|
# Project imports
|
|
|
|
# Project imports
|
|
|
|
from settings import settings
|
|
|
|
from settings import settings
|
|
|
@ -57,15 +59,13 @@ class Nordigen(util.Base):
|
|
|
|
path = f"{settings.Nordigen.Base}/token/new/"
|
|
|
|
path = f"{settings.Nordigen.Base}/token/new/"
|
|
|
|
r = requests.post(path, headers=headers, data=dumps(data))
|
|
|
|
r = requests.post(path, headers=headers, data=dumps(data))
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
obj = AccessToken.from_json(r.content)
|
|
|
|
except JSONDecodeError:
|
|
|
|
except ValidationError as err:
|
|
|
|
self.log.error(f"Error parsing access token response: {r.content}")
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
if "access" in parsed:
|
|
|
|
parsed = obj.to_dict()
|
|
|
|
self.token = parsed["access"]
|
|
|
|
self.token = parsed["access"]
|
|
|
|
self.log.info("Refreshed access token")
|
|
|
|
self.log.info("Refreshed access token")
|
|
|
|
else:
|
|
|
|
|
|
|
|
self.log.error(f"Access token not in response: {parsed}")
|
|
|
|
|
|
|
|
if not self.authed:
|
|
|
|
if not self.authed:
|
|
|
|
self.__authed__()
|
|
|
|
self.__authed__()
|
|
|
|
self.authed = True
|
|
|
|
self.authed = True
|
|
|
@ -84,10 +84,17 @@ class Nordigen(util.Base):
|
|
|
|
path = f"{settings.Nordigen.Base}/institutions/?country={country}"
|
|
|
|
path = f"{settings.Nordigen.Base}/institutions/?country={country}"
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
parsed_pre = r.json()
|
|
|
|
except JSONDecodeError:
|
|
|
|
except JSONDecodeError:
|
|
|
|
self.log.error(f"Error parsing institutions response: {r.content}")
|
|
|
|
self.log.error(f"Error parsing institutions response: {r.content}")
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
parsed = {"institutions": parsed_pre}
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
obj = Institutions.from_dict(parsed)
|
|
|
|
|
|
|
|
except ValidationError as err:
|
|
|
|
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
parsed = obj.to_dict()["institutions"]
|
|
|
|
new_list = []
|
|
|
|
new_list = []
|
|
|
|
if filter_name:
|
|
|
|
if filter_name:
|
|
|
|
for i in parsed:
|
|
|
|
for i in parsed:
|
|
|
@ -96,21 +103,6 @@ class Nordigen(util.Base):
|
|
|
|
return new_list
|
|
|
|
return new_list
|
|
|
|
return parsed
|
|
|
|
return parsed
|
|
|
|
|
|
|
|
|
|
|
|
def create_agreement(self, institution_id):
|
|
|
|
|
|
|
|
"""Create an agreement to access an institution.
|
|
|
|
|
|
|
|
:param institution_id: ID of the institution
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
headers = {"accept": "application/json", "Authorization": f"Bearer {self.token}"}
|
|
|
|
|
|
|
|
path = f"{settings.Nordigen.Base}/agreements/enduser"
|
|
|
|
|
|
|
|
data = {"institution_id": institution_id}
|
|
|
|
|
|
|
|
r = requests.post(path, headers=headers, data=dumps(data))
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
parsed = r.json()
|
|
|
|
|
|
|
|
except JSONDecodeError:
|
|
|
|
|
|
|
|
self.log.error(f"Error parsing agreement response: {r.content}")
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
return parsed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_link(self, institution_id):
|
|
|
|
def build_link(self, institution_id):
|
|
|
|
"""Create a link to access an institution.
|
|
|
|
"""Create a link to access an institution.
|
|
|
|
:param institution_id: ID of the institution
|
|
|
|
:param institution_id: ID of the institution
|
|
|
@ -120,10 +112,11 @@ class Nordigen(util.Base):
|
|
|
|
data = {"institution_id": institution_id, "redirect": settings.Nordigen.CallbackURL}
|
|
|
|
data = {"institution_id": institution_id, "redirect": settings.Nordigen.CallbackURL}
|
|
|
|
r = requests.post(path, headers=headers, data=data)
|
|
|
|
r = requests.post(path, headers=headers, data=data)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
obj = Agreement.from_json(r.content)
|
|
|
|
except JSONDecodeError:
|
|
|
|
except ValidationError as err:
|
|
|
|
self.log.error(f"Error parsing link response: {r.content}")
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
|
|
|
|
parsed = obj.to_dict()
|
|
|
|
if "link" in parsed:
|
|
|
|
if "link" in parsed:
|
|
|
|
return parsed["link"]
|
|
|
|
return parsed["link"]
|
|
|
|
return False
|
|
|
|
return False
|
|
|
@ -150,14 +143,16 @@ class Nordigen(util.Base):
|
|
|
|
path = f"{settings.Nordigen.Base}/requisitions"
|
|
|
|
path = f"{settings.Nordigen.Base}/requisitions"
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
obj = Requisitions.from_json(r.content)
|
|
|
|
except JSONDecodeError:
|
|
|
|
except ValidationError as err:
|
|
|
|
self.log.error(f"Error parsing requisitions response: {r.content}")
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
|
|
|
|
parsed = obj.to_dict()
|
|
|
|
if "results" in parsed:
|
|
|
|
if "results" in parsed:
|
|
|
|
return parsed["results"]
|
|
|
|
return parsed["results"]
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self.log.error(f"Results not in requisitions response: {parsed}")
|
|
|
|
self.log.error(f"Results not in requisitions response: {parsed}")
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_accounts(self, requisition):
|
|
|
|
def get_accounts(self, requisition):
|
|
|
@ -168,10 +163,11 @@ class Nordigen(util.Base):
|
|
|
|
path = f"{settings.Nordigen.Base}/requisitions/{requisition}/"
|
|
|
|
path = f"{settings.Nordigen.Base}/requisitions/{requisition}/"
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
obj = Agreement.from_json(r.content)
|
|
|
|
except JSONDecodeError:
|
|
|
|
except ValidationError as err:
|
|
|
|
self.log.error(f"Error parsing accounts response: {r.content}")
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
|
|
|
|
parsed = obj.to_dict()
|
|
|
|
if "accounts" in parsed:
|
|
|
|
if "accounts" in parsed:
|
|
|
|
return parsed["accounts"]
|
|
|
|
return parsed["accounts"]
|
|
|
|
return False
|
|
|
|
return False
|
|
|
@ -184,14 +180,11 @@ class Nordigen(util.Base):
|
|
|
|
path = f"{settings.Nordigen.Base}/accounts/{account_id}/details/"
|
|
|
|
path = f"{settings.Nordigen.Base}/accounts/{account_id}/details/"
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
r = requests.get(path, headers=headers)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parsed = r.json()
|
|
|
|
obj = AccountDetails.from_json(r.content)
|
|
|
|
except JSONDecodeError:
|
|
|
|
except ValidationError as err:
|
|
|
|
self.log.error(f"Error parsing account response: {r.content}")
|
|
|
|
self.log.error(f"Validation error: {err}")
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
# if "accounts" in parsed:
|
|
|
|
parsed = obj.to_dict()["account"]
|
|
|
|
# return parsed["accounts"]
|
|
|
|
|
|
|
|
# return False
|
|
|
|
|
|
|
|
parsed = parsed["account"]
|
|
|
|
|
|
|
|
if "bban" in parsed and parsed["currency"] == "GBP":
|
|
|
|
if "bban" in parsed and parsed["currency"] == "GBP":
|
|
|
|
sort_code = parsed["bban"][0:6]
|
|
|
|
sort_code = parsed["bban"][0:6]
|
|
|
|
account_number = parsed["bban"][6:]
|
|
|
|
account_number = parsed["bban"][6:]
|
|
|
@ -252,3 +245,16 @@ class Nordigen(util.Base):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
to_return[req["institution_id"]] = [account_info]
|
|
|
|
to_return[req["institution_id"]] = [account_info]
|
|
|
|
return to_return
|
|
|
|
return to_return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_transactions(self, account_id):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Get all transactions for an account.
|
|
|
|
|
|
|
|
:param account_id: account to fetch transactions for
|
|
|
|
|
|
|
|
:return: list of transactions
|
|
|
|
|
|
|
|
:rtype: dict
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
return obj.to_dict()["transactions"]["booked"]
|
|
|
|