11
11
12
12
__version__ = '0.1.4.dev0' # Should be the same in setup.py
13
13
14
- _URL_GET_ACCOUNTS = "https://api.revolut.com/user/current/wallet"
15
- _URL_GET_TRANSACTIONS = 'https://api.revolut.com/user/current/transactions'
16
- _URL_QUOTE = "https://api.revolut.com/quote/"
17
- _URL_EXCHANGE = "https://api.revolut.com/exchange"
18
- _URL_GET_TOKEN_STEP1 = "https://api.revolut.com/signin"
19
- _URL_GET_TOKEN_STEP2 = "https://api.revolut.com/signin/confirm"
14
+ API_BASE = "https://api.revolut.com"
15
+ _URL_GET_ACCOUNTS = API_BASE + "/user/current/wallet"
16
+ _URL_GET_TRANSACTIONS_LAST = API_BASE + "/user/current/transactions/last"
17
+ _URL_QUOTE = API_BASE + "/quote/"
18
+ _URL_EXCHANGE = API_BASE + "/exchange"
19
+ _URL_GET_TOKEN_STEP1 = API_BASE + "/signin"
20
+ _URL_GET_TOKEN_STEP2 = API_BASE + "/signin/confirm"
20
21
21
22
_DEFAULT_TOKEN_FOR_SIGNIN = "QXBwOlM5V1VuU0ZCeTY3Z1dhbjc="
22
23
@@ -132,7 +133,7 @@ class Client:
132
133
""" Do the requests with the Revolut servers """
133
134
def __init__ (self , token , device_id ):
134
135
self .session = requests .session ()
135
- self .headers = {
136
+ self .session . headers = {
136
137
'Host' : 'api.revolut.com' ,
137
138
'X-Api-Version' : '1' ,
138
139
'X-Client-Version' : '6.34.3' ,
@@ -141,22 +142,20 @@ def __init__(self, token, device_id):
141
142
'Authorization' : 'Basic ' + token ,
142
143
}
143
144
144
- def _get (self , url , expected_status_code = 200 ):
145
- ret = self .session .get (url = url , headers = self . headers )
145
+ def _get (self , url , * , expected_status_code = 200 , ** kwargs ):
146
+ ret = self .session .get (url = url , ** kwargs )
146
147
if ret .status_code != expected_status_code :
147
148
raise ConnectionError (
148
- 'Status code {status } for url {url }\n {content }' .format (
149
- status = ret .status_code , url = url , content = ret .text ))
149
+ 'Status code {} for url {}\n {}' .format (
150
+ ret .status_code , url , ret .text ))
150
151
return ret
151
152
152
- def _post (self , url , post_data , expected_status_code = 200 ):
153
- ret = self .session .post (url = url ,
154
- headers = self .headers ,
155
- json = post_data )
153
+ def _post (self , url , * , expected_status_code = 200 , ** kwargs ):
154
+ ret = self .session .post (url = url , ** kwargs )
156
155
if ret .status_code != expected_status_code :
157
156
raise ConnectionError (
158
- 'Status code {status } for url {url }\n {content }' .format (
159
- status = ret .status_code , url = url , content = ret .text ))
157
+ 'Status code {} for url {}\n {}' .format (
158
+ ret .status_code , url , ret .text ))
160
159
return ret
161
160
162
161
@@ -168,7 +167,7 @@ def get_account_balances(self):
168
167
""" Get the account balance for each currency
169
168
and returns it as a dict {"balance":XXXX, "currency":XXXX} """
170
169
ret = self .client ._get (_URL_GET_ACCOUNTS )
171
- raw_accounts = json . loads ( ret .text )
170
+ raw_accounts = ret .json ( )
172
171
173
172
account_balances = []
174
173
for raw_account in raw_accounts .get ("pockets" ):
@@ -183,22 +182,29 @@ def get_account_balances(self):
183
182
self .account_balances = Accounts (account_balances )
184
183
return self .account_balances
185
184
186
- def get_account_transactions (self , from_date ):
187
- """ Get the account transactions and return as json """
188
- from_date_ts = from_date .timestamp ()
189
- path = _URL_GET_TRANSACTIONS + '?from={from_date_ts}&walletId={wallet_id}' .format (
190
- from_date_ts = int (from_date_ts ) * 1000 ,
191
- wallet_id = self .get_wallet_id ()
192
- )
193
- ret = self .client ._get (path )
194
- raw_transactions = json .loads (ret .text )
195
- transactions = AccountTransactions (raw_transactions )
196
- return transactions
185
+ def get_account_transactions (self , from_date = None , to_date = None ):
186
+ """Get the account transactions."""
187
+ raw_transactions = []
188
+ params = {}
189
+ if to_date :
190
+ params ['to' ] = int (to_date .timestamp ()) * 1000
191
+ if from_date :
192
+ params ['from' ] = int (from_date .timestamp ()) * 1000
193
+
194
+ while True :
195
+ ret = self .client ._get (_URL_GET_TRANSACTIONS_LAST , params = params )
196
+ ret_transactions = ret .json ()
197
+ if not ret_transactions :
198
+ break
199
+ params ['to' ] = ret_transactions [- 1 ]['startedDate' ]
200
+ raw_transactions .extend (ret_transactions )
201
+
202
+ return AccountTransactions (raw_transactions )
197
203
198
204
def get_wallet_id (self ):
199
205
""" Get the main wallet_id """
200
206
ret = self .client ._get (_URL_GET_ACCOUNTS )
201
- raw = json . loads ( ret .text )
207
+ raw = ret .json ( )
202
208
return raw .get ('id' )
203
209
204
210
def quote (self , from_amount , to_currency ):
@@ -213,7 +219,7 @@ def quote(self, from_amount, to_currency):
213
219
to_currency ,
214
220
from_amount .revolut_amount ))
215
221
ret = self .client ._get (url_quote )
216
- raw_quote = json . loads ( ret .text )
222
+ raw_quote = ret .json ( )
217
223
quote_obj = Amount (revolut_amount = raw_quote ["to" ]["amount" ],
218
224
currency = to_currency )
219
225
return quote_obj
@@ -257,8 +263,8 @@ def exchange(self, from_amount, to_currency, simulate=False):
257
263
"updatedDate":123456789}]'
258
264
raw_exchange = json .loads (simu )
259
265
else :
260
- ret = self .client ._post (url = _URL_EXCHANGE , post_data = data )
261
- raw_exchange = json . loads ( ret .text )
266
+ ret = self .client ._post (_URL_EXCHANGE , json = data )
267
+ raw_exchange = ret .json ( )
262
268
263
269
if raw_exchange [0 ]["state" ] == "COMPLETED" :
264
270
amount = raw_exchange [0 ]["counterpart" ]["amount" ]
@@ -462,15 +468,12 @@ def csv(self, lang="fr", reverse=False):
462
468
463
469
def get_token_step1 (device_id , phone , password , simulate = False ):
464
470
""" Function to obtain a Revolut token (step 1 : send a code by sms/email) """
465
- if not simulate :
466
- c = Client (device_id = device_id , token = _DEFAULT_TOKEN_FOR_SIGNIN )
467
- data = {"phone" : phone , "password" : password }
468
- ret = c ._post (url = _URL_GET_TOKEN_STEP1 ,
469
- post_data = data ,
470
- expected_status_code = 200 )
471
- channel = ret .json ().get ('channel' )
472
- else :
473
- channel = "SMS"
471
+ if simulate :
472
+ return "SMS"
473
+ c = Client (device_id = device_id , token = _DEFAULT_TOKEN_FOR_SIGNIN )
474
+ data = {"phone" : phone , "password" : password }
475
+ ret = c ._post (_URL_GET_TOKEN_STEP1 , json = data )
476
+ channel = ret .json ().get ("channel" )
474
477
return channel
475
478
476
479
@@ -497,18 +500,25 @@ def get_token_step2(device_id, phone, code, simulate=False):
497
500
c = Client (device_id = device_id , token = _DEFAULT_TOKEN_FOR_SIGNIN )
498
501
code = code .replace ("-" , "" ) # If the user would put -
499
502
data = {"phone" : phone , "code" : code }
500
- ret = c ._post (url = _URL_GET_TOKEN_STEP2 , post_data = data )
503
+ ret = c ._post (_URL_GET_TOKEN_STEP2 , json = data )
501
504
raw_get_token = ret .json ()
505
+ return raw_get_token
502
506
503
- if raw_get_token .get ("thirdFactorAuthAccessToken" ):
504
- raise KeyError (
505
- "Token generation with a third factor authentication (selfie) "
506
- "is not currently supported by this package"
507
- )
508
507
509
- user_id = raw_get_token ["user" ]["id" ]
510
- access_token = raw_get_token ["accessToken" ]
511
- token_to_encode = '{}:{}' .format (user_id , access_token ).encode ('ascii' )
508
+ def extract_token (json_response ):
509
+ user_id = json_response ["user" ]["id" ]
510
+ access_token = json_response ["accessToken" ]
511
+ token_to_encode = "{}:{}" .format (user_id , access_token ).encode ("ascii" )
512
512
# Ascii encoding required by b64encode function : 8 bits char as input
513
513
token = base64 .b64encode (token_to_encode )
514
- return token .decode ('ascii' )
514
+ return token .decode ("ascii" )
515
+
516
+
517
+ def signin_biometric (device_id , phone , access_token , selfie_filepath ):
518
+ files = {"selfie" : open (selfie_filepath , "rb" )}
519
+ c = Client (device_id = device_id , token = _DEFAULT_TOKEN_FOR_SIGNIN )
520
+ c .session .auth = (phone , access_token )
521
+ res = c ._post (API_BASE + "/biometric-signin/selfie" , files = files )
522
+ biometric_id = res .json ()["id" ]
523
+ res = c ._post (API_BASE + "/biometric-signin/confirm/" + biometric_id )
524
+ return res .json ()
0 commit comments