# -*- coding: utf-8 -*- # Copyright(C) 2018 Roger Philibert # # This file is part of weboob. # # weboob is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # weboob is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with weboob. If not, see . from __future__ import unicode_literals from datetime import date, timedelta from weboob.browser.filters.standard import ( CleanDecimal, CleanText, DateTime, Currency, Format, ) from weboob.capabilities.base import empty from weboob.browser.filters.json import Dict from weboob.browser.exceptions import ClientError from weboob.exceptions import BrowserIncorrectPassword from weboob.browser.browsers import APIBrowser from weboob.capabilities.bank import Account, Transaction class SwileBrowser(APIBrowser): BASEURL = 'https://customer-api.swile.co' def __init__(self, login, password, *args, **kwargs): """SwileBrowser needs login and password to fetch Swile API""" super(SwileBrowser, self).__init__(*args, **kwargs) # self.session.headers are the HTTP headers for Swile API requests self.session.headers['x-api-key'] = '644a4ef497286a229aaf8205c2dc12a9086310a8' self.session.headers['x-lunchr-app-version'] = 'b6c6ca66c79ca059222779fe8f1ac98c8485b9f0' self.session.headers['x-lunchr-platform'] = 'web' # self.credentials is the HTTP POST data used in self._auth() self.credentials = { 'user': { 'email': login, 'password': password, } } def _auth(self): """Authenticate to Swile API using self.credentials. If authentication succeeds, authorization header is set in self.headers and response's json payload is returned unwrapped into dictionary. """ try: response = self.open('/api/v0/users/login', data=self.credentials) except ClientError as e: json = e.response.json() if e.response.status_code == 401: message = json['result']['error']['message'] raise BrowserIncorrectPassword(message) raise e json = Dict('user')(response.json()) self.session.headers['Authorization'] = 'Bearer ' + Dict('token')(json) return json def get_account(self): json = self._auth() account = Account(id=Dict('id')(json)) account.number = account.id # weboob.capabilities.bank.BaseAccount account.bank_name = 'Swile' account.type = Account.TYPE_CHECKING # Check if account have a card balance = Dict('meal_voucher_info/balance/value', default=None)(json) if empty(balance): return account.balance = CleanDecimal.SI(balance)(json) account.label = Format('%s %s', CleanText(Dict('first_name')), CleanText(Dict('last_name')))(json) account.currency = Currency(Dict('meal_voucher_info/balance/currency/iso_3'))(json) account.cardlimit = CleanDecimal.SI(Dict('meal_voucher_info/daily_balance/value'))(json) yield account def iter_history(self, account): # make sure we have today's transactions before = date.today() + timedelta(days=1) for page in range(200): # limit pagination response = self.open( '/api/v0/payments_history', params={ 'per': 20, 'before': before.isoformat(), # don't pass page= param, it works but # it's slower than the before= param }, ) json = response.json() if len(Dict('payments_history')(json)) == 0: break transaction = None for payment in Dict('payments_history')(json): if 'refunding_transaction' in payment: refund = self._parse_transaction(payment['refunding_transaction']) refund.type = Transaction.TYPE_CARD yield refund transaction = self._parse_transaction(payment) if transaction: # this is a millisecond-precise datetime (with a timezone). # fortunately, the api excludes transactions occuring at the exact datetime we pass. # if the page boundary is hit on transactions occurring at the same datetime, we might lose some of them though. before = transaction.date yield transaction if transaction is None: break else: raise Exception("that's a lot of transactions, probable infinite loop?") def _parse_transaction(self, payment): transaction = Transaction() transaction_id = Dict('transaction_number', default=None)(payment) # Check if transaction_id is None which indicates failed transaction if transaction_id is None: return transaction.id = transaction_id transaction.date = DateTime(Dict('executed_at'))(payment) transaction.rdate = DateTime(Dict('created_at'))(payment) types = { 'ORDER': Transaction.TYPE_CARD, # order on swile website 'LUNCHR_CARD_PAYMENT': Transaction.TYPE_CARD, # pay in shop 'MEAL_VOUCHER_CREDIT': Transaction.TYPE_DEPOSIT, # type can be null for refunds } transaction.type = types.get(Dict('type')(payment), Transaction.TYPE_UNKNOWN) transaction.label = Dict('name')(payment) transaction.amount = CleanDecimal(Dict('amount/value'))(payment) return transaction