Initial commit
This commit is contained in:
145
starling
Executable file
145
starling
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env -S python3 -ttuI
|
||||
# -*- python -*-
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import requests
|
||||
import sys
|
||||
import types
|
||||
|
||||
base_url = 'https://api.starlingbank.com/api/v2'
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
class StarlingClient:
|
||||
|
||||
def __init__(self):
|
||||
self.tokens = self.read_tokens('main', 'payments')
|
||||
|
||||
def read_tokens(self, *names):
|
||||
tokens = {}
|
||||
for name in names:
|
||||
with io.open(f'tokens/{name}.txt') as fh:
|
||||
tokens[name] = fh.read().rstrip()
|
||||
return tokens
|
||||
|
||||
def get(self, path, token='main'):
|
||||
url = f'{base_url}/{path}'
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.tokens[token]}',
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
return json.loads(response.text, object_hook=lambda obj: types.SimpleNamespace(**obj))
|
||||
|
||||
### Low-level API wrappers ###
|
||||
|
||||
def account_holder(self):
|
||||
return self.get('account-holder')
|
||||
|
||||
def account_holder_individual(self):
|
||||
return self.get('account-holder/individual')
|
||||
|
||||
def accounts(self):
|
||||
return self.get('accounts')
|
||||
|
||||
def balance(self, accountUid):
|
||||
return self.get(f'accounts/{accountUid}/balance')
|
||||
|
||||
def payees(self):
|
||||
return self.get('payees')
|
||||
|
||||
### Mid-level methods to munge the data from the low-level calls ###
|
||||
|
||||
def formatted_balance(self, accountUid):
|
||||
balance = self.balance(accountUid).effectiveBalance
|
||||
if balance.currency == 'GBP':
|
||||
symbol = '£'
|
||||
elif balance.currency == 'EUR':
|
||||
symbol = '€'
|
||||
else:
|
||||
sys.exit(f'ERROR: Unsupported currency {balance.currency}')
|
||||
major_units = int(balance.minorUnits / 100)
|
||||
minor_units = balance.minorUnits % 100
|
||||
return f'{symbol}{major_units}.{minor_units:02d}'
|
||||
|
||||
### High-level methods which correspond to actions ###
|
||||
|
||||
def default_action(self):
|
||||
holder = self.account_holder()
|
||||
holder_type = holder.accountHolderType
|
||||
if holder_type == 'INDIVIDUAL':
|
||||
details = self.account_holder_individual()
|
||||
else:
|
||||
sys.exit(f'ERROR: Unsupported account holder type {holder_type}')
|
||||
print('Customer details:')
|
||||
print(f' Name: {details.firstName} {details.lastName}')
|
||||
print(f' Email: <{details.email}>')
|
||||
print(f' Phone: {details.phone}')
|
||||
accounts = self.accounts().accounts
|
||||
count = len(accounts)
|
||||
if count == 0:
|
||||
print('This customer has no bank accounts.')
|
||||
else:
|
||||
if count == 1:
|
||||
print('This customer has one bank account:')
|
||||
else:
|
||||
print(f'This customer has {count} bank accounts:')
|
||||
for account in accounts:
|
||||
balance = self.formatted_balance(account.accountUid)
|
||||
print(f' {account.name} ({account.accountType}): {balance}')
|
||||
|
||||
def list_payees(self):
|
||||
payees = self.payees().payees
|
||||
count = len(payees)
|
||||
if count == 0:
|
||||
print('There are no payees for this customer.')
|
||||
else:
|
||||
if count == 1:
|
||||
print('There is one payee for this customer:')
|
||||
else:
|
||||
print(f'There are {count} payees for this customer:')
|
||||
for payee in payees:
|
||||
print(f' {payee.payeeName} ({payee.payeeType})')
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage='%(prog)s [options] [action [arg ...]]',
|
||||
description='Interacts with Starling Bank\'s API to manage bank accounts.',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''recognised actions and args:
|
||||
- show customer and account details
|
||||
payees - list the customer's payees
|
||||
contacts <domain> [<name>] - get/set contacts for domain
|
||||
expire <domain> - allow domain to auto-expire
|
||||
expiry - list domains with expiry info
|
||||
|
||||
To show the current lock state use "%(prog)s show <domain> status".
|
||||
''')
|
||||
|
||||
parser.add_argument('-t', '--test', action='store_true', default=False,
|
||||
help='use test servers rather than live servers')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', default=False,
|
||||
help='print XML requests and responses')
|
||||
parser.add_argument('action', help='which action to perform', nargs='*')
|
||||
args = parser.parse_args()
|
||||
|
||||
client = StarlingClient()
|
||||
|
||||
action_args = args.action
|
||||
if len(action_args) == 0:
|
||||
client.default_action()
|
||||
else:
|
||||
action = action_args.pop(0)
|
||||
count = len(action_args)
|
||||
if action == 'payees':
|
||||
if count > 0:
|
||||
parser.error(f'Too many arguments for "{action}" action')
|
||||
client.list_payees()
|
||||
else:
|
||||
parser.error(f'Unknown action "{action}"')
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
Reference in New Issue
Block a user