Skip to content
This repository has been archived by the owner on Sep 9, 2022. It is now read-only.

Commit

Permalink
Merge dc15816 into 71eb9f5
Browse files Browse the repository at this point in the history
  • Loading branch information
tducret committed Oct 30, 2019
2 parents 71eb9f5 + dc15816 commit bfd03d6
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
29 changes: 29 additions & 0 deletions README.md
Expand Up @@ -55,6 +55,35 @@ If you don't have a Revolut token yet, the tool will allow you to obtain one.

⚠️ **If you don't receive a SMS when trying to get a token, you need to logout from the app on your Smartphone.**

## Pulling transactions

```bash
Usage: revolut_transactions.py [OPTIONS]

Get the account balances on Revolut

Options:
-t, --token TEXT your Revolut token (or set the env var
REVOLUT_TOKEN)
-l, --language TEXT language ("fr" or "en"), for the csv header and
separator
-t, --from_date [%Y-%m-%d] transactions lookback date in YYYY-MM-DD format
(ex: "2019-10-26"). Default 30 days back
-r, --reverse reverse the order of the transactions displayed
--help Show this message and exit.
```

Example output :

```csv
Date-time,Description,Amount,Currency
08/26/2019 21:31:00,Card Delivery Fee,-59.99,SEK
09/14/2019 12:50:07,donkey.bike **pending**,0.0,SEK
09/14/2019 13:03:15,Top-Up by *6458,200.0,SEK
09/30/2019 16:19:19,Reward user for the invite,200.0,SEK
10/12/2019 23:51:02,Tiptapp Reservation,-250.0,SEK
```

## TODO

- [ ] Document revolutbot.py
Expand Down
132 changes: 132 additions & 0 deletions revolut/__init__.py
Expand Up @@ -12,6 +12,7 @@
__version__ = '0.0.9' # Should be the same in setup.py

_URL_GET_ACCOUNTS = "https://api.revolut.com/user/current/wallet"
_URL_GET_TRANSACTIONS = 'https://api.revolut.com/user/current/transactions'
_URL_QUOTE = "https://api.revolut.com/quote/"
_URL_EXCHANGE = "https://api.revolut.com/exchange"
_URL_GET_TOKEN_STEP1 = "https://api.revolut.com/signin"
Expand All @@ -27,6 +28,12 @@

_VAULT_ACCOUNT_TYPE = "SAVINGS"
_ACTIVE_ACCOUNT = "ACTIVE"
_TRANSACTION_COMPLETED = "COMPLETED"
_TRANSACTION_FAILED = "FAILED"
_TRANSACTION_PENDING = "PENDING"
_TRANSACTION_REVERTED = "REVERTED"
_TRANSACTION_DECLINED = "DECLINED"


# The amounts are stored as integer on Revolut.
# They apply a scale factor depending on the currency
Expand Down Expand Up @@ -175,6 +182,25 @@ def get_account_balances(self):
self.account_balances = Accounts(account_balances)
return self.account_balances

def get_account_transactions(self, from_date):
""" Get the account transactions and return as json """
wallet_id = self.get_wallet_id()
from_date_ts = from_date.timestamp()
path = _URL_GET_TRANSACTIONS + '?from={from_date_ts}&walletId={wallet_id}'.format(
from_date_ts=int(from_date_ts),
wallet_id=self.get_wallet_id()
)
ret = self.client._get(path)
raw_transactions = json.loads(ret.text)
transactions = AccountTransactions(raw_transactions)
return transactions

def get_wallet_id(self):
""" Get the main wallet_id """
ret = self.client._get(_URL_GET_ACCOUNTS)
raw = json.loads(ret.text)
return raw.get('id')

def quote(self, from_amount, to_currency):
if type(from_amount) != Amount:
raise TypeError("from_amount must be with the Amount type")
Expand Down Expand Up @@ -328,6 +354,112 @@ def csv(self, lang="fr"):
return csv_str.replace(".", ",") if lang_is_fr else csv_str


class AccountTransaction:
""" Class to handle an account transaction """
def __init__(
self,
transactions_type,
state,
started_date,
completed_date,
amount,
fee,
description,
account_id
):
self.transactions_type = transactions_type
self.state = state
self.started_date = started_date
self.completed_date = completed_date
self.amount = amount
self.fee = fee
self.description = description
self.account_id = account_id

def __str__(self):
return "{description}: {amount}".format(
description=self.description,
amount=str(self.amount)
)

def get_datetime__str(self, date_format="%d/%m/%Y %H:%M:%S"):
""" 'Pending' transactions do not have 'completed_date' yet
so return 'started_date' instead """
timestamp = self.completed_date if self.completed_date \
else self.started_date
# Convert from timestamp to datetime
dt = datetime.fromtimestamp(
timestamp / 1000
)
dt_str = dt.strftime(date_format)
return dt_str

def get_description(self):
# Adding 'pending' for processing transactions
description = self.description
if self.state == _TRANSACTION_PENDING:
description = '{} **pending**'.format(description)
return description

def get_amount__str(self):
""" Convert amount to float and return string representation """
return str(self.amount.real_amount)


class AccountTransactions:
""" Class to handle the account transactions """

def __init__(self, account_transactions):
self.raw_list = account_transactions
self.list = [
AccountTransaction(
transactions_type=transaction.get("type"),
state=transaction.get("state"),
started_date=transaction.get("startedDate"),
completed_date=transaction.get("completedDate"),
amount=Amount(revolut_amount=transaction.get('amount'),
currency=transaction.get('currency')),
fee=transaction.get('fee'),
description=transaction.get('description'),
account_id=transaction.get('account').get('id')
)
for transaction in self.raw_list
]

def __len__(self):
return len(self.list)

def csv(self, lang="fr", reverse=False):
lang_is_fr = lang == "fr"
if lang_is_fr:
csv_str = "Date-heure (DD/MM/YYYY HH:MM:ss);Description;Montant;Devise"
date_format = "%d/%m/%Y %H:%M:%S"
else:
csv_str = "Date-time (MM/DD/YYYY HH:MM:ss),Description,Amount,Currency"
date_format = "%m/%d/%Y %H:%M:%S"

# Europe uses 'comma' as decimal separator,
# so it can't be used as delimiter:
delimiter = ";" if lang_is_fr else ","

# Do not export declined or failed payments
transaction_list = list(reversed(self.list)) if reverse else self.list
for account_transaction in transaction_list:
if account_transaction.state not in [
_TRANSACTION_DECLINED,
_TRANSACTION_FAILED,
_TRANSACTION_REVERTED
]:

csv_str += "\n" + delimiter.join((
account_transaction.get_datetime__str(date_format),
account_transaction.get_description(),
account_transaction.get_amount__str(),
account_transaction.amount.currency
))
return csv_str.replace(".", ",") if lang_is_fr else csv_str


def get_token_step1(device_id, phone, password, simulate=False):
""" Function to obtain a Revolut token (step 1 : send a code by sms) """
if not simulate:
Expand Down
54 changes: 54 additions & 0 deletions revolut_transactions.py
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import click
import json
import sys

from datetime import datetime
from datetime import timedelta
from getpass import getpass

from revolut import Revolut, __version__, get_token_step1, get_token_step2


_CLI_DEVICE_ID = 'revolut_cli'
_URL_GET_TRANSACTIONS = 'https://api.revolut.com/user/current/transactions'


@click.command()
@click.option(
'--token', '-t',
envvar="REVOLUT_TOKEN",
type=str,
help='your Revolut token (or set the env var REVOLUT_TOKEN)',
)
@click.option(
'--language', '-l',
type=str,
help='language ("fr" or "en"), for the csv header and separator',
default='fr'
)
@click.option(
'--from_date', '-t',
type=click.DateTime(formats=["%Y-%m-%d"]),
help='transactions lookback date in YYYY-MM-DD format (ex: "2019-10-26"). Default 30 days back',
default=(datetime.now()-timedelta(days=30)).strftime("%Y-%m-%d")
)
@click.option(
'--reverse', '-r',
is_flag=True,
help='reverse the order of the transactions displayed',
)
def main(token, language, from_date, reverse):
""" Get the account balances on Revolut """
if token is None:
print("You don't seem to have a Revolut token. Use 'revolut_cli' to obtain one")
sys.exit()

rev = Revolut(device_id=_CLI_DEVICE_ID, token=token)
account_transactions = rev.get_account_transactions(from_date)
print(account_transactions.csv(lang=language, reverse=reverse))

if __name__ == "__main__":
main()

0 comments on commit bfd03d6

Please sign in to comment.