Skip to content
Commits on Source (24)
......@@ -23,7 +23,9 @@
from weboob.capabilities.bill import DocumentTypes, CapDocument, Subscription, Document, SubscriptionNotFound, DocumentNotFound
from weboob.capabilities.base import find_object, NotAvailable
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.compat import urljoin
from weboob.tools.value import ValueBackendPassword, Value
from weboob.tools.pdf import html_to_pdf
from .browser import AmazonBrowser
from .en.browser import AmazonEnBrowser
......@@ -93,3 +95,14 @@ def download_document(self, document):
return
return self.browser.open(document.url).content
def download_document_pdf(self, document):
if not isinstance(document, Document):
document = self.get_document(document)
if document.url is NotAvailable:
return
if document.format == 'pdf':
return self.browser.open(document.url).content
url = urljoin(self.browser.BASEURL, document.url)
return html_to_pdf(self.browser, url=url)
......@@ -138,7 +138,7 @@ class item(ItemElement):
klass = Bill
load_details = Field('_pre_url') & AsyncLoad
obj__simple_id = CleanText('.//div[has-class("actions")]//span[has-class("value")]')
obj__simple_id = CleanText('.//span[contains(text(), "N° de commande")]/following-sibling::span')
obj_id = Format('%s_%s', Env('subid'), Field('_simple_id'))
obj__pre_url = Format('/gp/shared-cs/ajax/invoice/invoice.html?orderId=%s&relatedRequestId=%s&isADriveSubscription=&isHFC=',
Field('_simple_id'), Env('request_id'))
......@@ -165,7 +165,7 @@ def obj_url(self):
async_page = Async('details').loaded_page(self)
url = Link('//a[contains(@href, "download")]|//a[contains(@href, "generated_invoices")]', default=NotAvailable)(async_page.doc)
if not url:
url = Link('//a[contains(text(), "Imprimer un récapitulatif de commande")]')(self)
url = Link('//a[contains(text(), "Imprimer un récapitulatif de commande")]')(async_page.doc)
return url
def obj_format(self):
......
......@@ -319,7 +319,7 @@ def on_load(self):
r = self.browser.open(self.request_url)
doc = r.json()
self.form_id, = [(k, v[0]['id']) for k, v in doc['step']['validationUnits'][0].items() if v[0]['type'] == 'PASSWORD_LOOKUP']
self.form_id, = [(k, v[0]['id'], v[0]['type']) for k, v in doc['step']['validationUnits'][0].items() if v[0]['type'] in ('PASSWORD_LOOKUP', 'IDENTIFIER')]
def login(self, login, password):
payload = {
......@@ -332,10 +332,22 @@ def login(self, login, password):
} ]
}
}
url = self.request_url + '/step'
headers = {'Content-Type': 'application/json'}
r = self.browser.open(url, data=json.dumps(payload), headers=headers)
if self.form_id[2] == 'IDENTIFIER':
del payload['validate'][self.form_id[0]][0]['password']
payload['validate'][self.form_id[0]][0]['type'] = 'IDENTIFIER'
doc = self.browser.open(url, json=payload).json()
form_id, = [(k, v[0]['id'], v[0]['type']) for k, v in doc['validationUnits'][0].items() if v[0]['type'] in ('PASSWORD',)]
payload = {
'validate': {
form_id[0]: [{
'id': self.form_id[1],
'password': password,
'type': 'PASSWORD',
}]
}
}
r = self.browser.open(url, json=payload)
doc = r.json()
self.logger.debug('doc = %s', doc)
......@@ -349,8 +361,7 @@ def login(self, login, password):
payload = {'validate': doc['validationUnits'][0]}
url = self.request_url + '/step'
headers = {'Content-Type': 'application/json'}
r = self.browser.open(url, data=json.dumps(payload), headers=headers)
r = self.browser.open(url, json=payload)
doc = r.json()
self.logger.debug('doc = %s', doc)
......
......@@ -34,6 +34,7 @@
TokenPage, MoveUniversePage, SwitchPage,
LoansPage, AccountsPage, IbanPage, LifeInsurancesPage,
SearchPage, ProfilePage, EmailsPage, ErrorPage,
ErrorCodePage,
)
__all__ = ['BredBrowser']
......@@ -59,6 +60,7 @@ class BredBrowser(LoginBrowser):
search = URL('/transactionnel/services/applications/operations/getSearch/', SearchPage)
profile = URL('/transactionnel/services/rest/User/user', ProfilePage)
emails = URL('/transactionnel/services/applications/gestionEmail/getAdressesMails', EmailsPage)
error_code = URL('/.*\?errorCode=.*', ErrorCodePage)
def __init__(self, accnum, login, password, *args, **kwargs):
kwargs['username'] = login
......
......@@ -325,3 +325,15 @@ def on_load(self):
if '/pages-gestion-des-erreurs/message-tiers-oppose' in self.url:
# need a case to retrieve the error message
raise ActionNeeded("Impossible de se connecter au compte car l'identification en 2 étapes a été activée")
class ErrorCodePage(HTMLPage):
def on_load(self):
code = re.search(r'\/\?errorCode=(\d+)', self.url).group(1)
page = self.browser.open('/particuliers/compte-bancaire/comptes-en-ligne/bredconnect-compte-ligne?errorCode=%s' % code).page
# invalid login/password
if code == '20100':
msg = CleanText('//label[contains(@class, "error")]')(page.doc)
raise BrowserIncorrectPassword(msg)
assert False, 'The % error is not handled.' % code
......@@ -481,6 +481,10 @@ def _get_history(self, info):
self.page.go_history(info)
# ensure we are on the correct history page
if 'netpro' in self.page.url and not self.page.is_history_of(info['id']):
self.page.go_history_netpro(info)
info['link'] = [info['link']]
for i in range(self.HISTORY_MAX_PAGE):
......@@ -745,6 +749,9 @@ def init_transfer(self, account, recipient, transfer):
)
)
if 'netpro' in self.url:
return self.page.create_transfer(account, recipient, transfer)
self.page.continue_transfer(account.label, recipient.label, transfer.label)
return self.page.update_transfer(transfer, account, recipient)
......
......@@ -129,12 +129,17 @@ class item(ItemElement):
obj_label = CleanText(Dict('Libelle'))
obj_total_amount = CleanDecimal(Dict('MontantInitial/Valeur'))
obj_currency = Currency(Dict('MontantInitial/Devise'))
obj_balance = CleanDecimal(Dict('CapitalRestantDu/Valeur'))
obj_type = Account.TYPE_LOAN
obj_duration = CleanDecimal(Dict('Duree'))
obj_rate = CleanDecimal.French(Dict('Taux'))
obj_next_payment_amount = CleanDecimal(Dict('MontantProchaineEcheance/Valeur'))
def obj_balance(self):
balance = CleanDecimal(Dict('CapitalRestantDu/Valeur'))(self)
if balance > 0:
balance *= -1
return balance
def obj_subscription_date(self):
sub_date = Dict('DateDebutEffet')(self)
if sub_date:
......
......@@ -589,6 +589,12 @@ def go_loan_list(self):
form.submit()
def is_history_of(self, account_id):
"""
Check whether the displayed history is for the correct account
"""
return bool(self.doc.xpath('//option[@value="%s" and @selected]' % account_id))
def go_history(self, info, is_cbtab=False):
form = self.get_form(id='main')
......@@ -601,6 +607,20 @@ def go_history(self, info, is_cbtab=False):
fix_form(form)
return form.submit()
def go_history_netpro(self, info, ):
"""
On the netpro website the go_history() does not work.
Even from a web browser the site does not work, and display the history of the first account
We use a different post to go through and display the history we need
"""
form = self.get_form(id='main')
form['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$HISTORIQUE_COMPTE$m_ExDropDownList'
form['MM$HISTORIQUE_COMPTE$m_ExDropDownList'] = info['id']
form['__EVENTTARGET'] = 'MM$HISTORIQUE_COMPTE$m_ExDropDownList'
fix_form(form)
return form.submit()
def get_form_to_detail(self, transaction):
m = re.match('.*\("(.*)", "(DETAIL_OP&[\d]+).*\)\)', transaction._link)
# go to detailcard page
......
......@@ -208,6 +208,26 @@ def get_security_form(self):
form = self.page.get_login_form(self.username, keypad_password, keypad_id)
return form
def get_account_iban(self, account_index, account_category, weboob_account_id):
"""
Fetch an IBAN for a given account
It may fail from time to time (error 500 or 403)
"""
params = {
'compteIdx': int(account_index),
'grandeFamilleCode': int(account_category),
}
try:
self.account_iban.go(params=params)
except (ClientError, ServerError):
self.logger.warning('Request to IBAN failed for account id "%s"', weboob_account_id)
return NotAvailable
iban = self.page.get_iban()
if is_iban_valid(iban):
return iban
return NotAvailable
@need_login
def check_space_connection(self, contract):
# Going to a specific space often returns a 500 error
......@@ -278,14 +298,8 @@ def get_accounts_list(self):
loan_ids.update(self.page.get_loan_ids())
if main_account.type == Account.TYPE_CHECKING:
params = {
'compteIdx': int(main_account._index),
'grandeFamilleCode': 1,
}
self.account_iban.go(params=params)
iban = self.page.get_iban()
if is_iban_valid(iban):
main_account.iban = iban
main_account.iban = self.get_account_iban(main_account._index, 1, main_account.id)
if main_account.id not in all_accounts:
all_accounts[main_account.id] = main_account
yield main_account
......@@ -294,14 +308,7 @@ def get_accounts_list(self):
if empty(account.balance):
account.balance = account_balances.get(account._id_element_contrat, NotAvailable)
if account.type == Account.TYPE_CHECKING:
params = {
'compteIdx': int(account._index),
'grandeFamilleCode': int(account._category),
}
self.account_iban.go(params=params)
iban = self.page.get_iban()
if is_iban_valid(iban):
account.iban = iban
account.iban = self.get_account_iban(account._index, account._category, account.id)
# Loans have a specific ID that we need to fetch
# so the backend can match loans properly.
......@@ -319,12 +326,12 @@ def get_accounts_list(self):
# Once again, this request tends to crash often.
try:
self.cards.go()
except ClientError:
except (ServerError, ClientError):
self.logger.warning('Request to cards failed, we try again')
try:
self.check_space_connection(contract)
self.cards.go()
except ClientError:
except (ServerError, ClientError):
self.logger.warning('Request to cards failed twice, cards of this space will be skipped.')
if self.cards.is_here():
......
......@@ -119,6 +119,7 @@ class ContractsPage(LoggedPage, HTMLPage):
'PRET PERSO': Account.TYPE_LOAN,
'P. ENTREPR': Account.TYPE_LOAN,
'P. HABITAT': Account.TYPE_LOAN,
'P. CONV.': Account.TYPE_LOAN,
'PRET 0%': Account.TYPE_LOAN,
'INV PRO': Account.TYPE_LOAN,
'TRES. PRO': Account.TYPE_LOAN,
......
......@@ -81,6 +81,9 @@ def do_login(self):
if not self.logged:
raise BrowserIncorrectPassword()
if self.page.doc.xpath('//head[title="Authentification"]/script[contains(text(), "_pageLabel=reinitialisation_mot_de_passe")]'):
raise BrowserPasswordExpired()
def _iter_accounts(self):
self.loans.go(account_type=self.account_type, loans_page_label=self.loans_page_label)
for a in self.page.get_list():
......
......@@ -32,7 +32,7 @@
from weboob.browser.url import URL
from weboob.browser.pages import FormNotFound
from weboob.browser.exceptions import ClientError, ServerError
from weboob.exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable
from weboob.exceptions import BrowserIncorrectPassword, AuthMethodNotImplemented, BrowserUnavailable, NoAccountsException
from weboob.capabilities.bank import Account, AddRecipientStep, Recipient
from weboob.tools.capabilities.bank.investments import create_french_liquidity
from weboob.capabilities import NotAvailable
......@@ -242,12 +242,14 @@ def get_accounts_list(self):
# Populate accounts from old website
if not self.is_new_website:
self.accounts.stay_or_go(subbank=self.currentSubBank)
has_no_account = self.page.has_no_account()
self.accounts_list.extend(self.page.iter_accounts())
self.iban.go(subbank=self.currentSubBank).fill_iban(self.accounts_list)
self.por.go(subbank=self.currentSubBank).add_por_accounts(self.accounts_list)
# Populate accounts from new website
else:
self.new_accounts.stay_or_go(subbank=self.currentSubBank)
has_no_account = self.page.has_no_account()
self.accounts_list.extend(self.page.iter_accounts())
self.iban.go(subbank=self.currentSubBank).fill_iban(self.accounts_list)
self.por.go(subbank=self.currentSubBank).add_por_accounts(self.accounts_list)
......@@ -261,6 +263,8 @@ def get_accounts_list(self):
excluded_label = ['etalis', 'valorisation totale']
self.accounts_list = [acc for acc in self.accounts_list if not any(w in acc.label.lower() for w in excluded_label)]
if has_no_account and not self.accounts_list:
raise NoAccountsException(has_no_account)
return self.accounts_list
......
......@@ -35,7 +35,7 @@
)
from weboob.browser.filters.html import Link, Attr, TableCell, ColumnNotFound
from weboob.exceptions import (
BrowserIncorrectPassword, ParseError, NoAccountsException, ActionNeeded, BrowserUnavailable,
BrowserIncorrectPassword, ParseError, ActionNeeded, BrowserUnavailable,
AuthMethodNotImplemented,
)
from weboob.capabilities import NotAvailable
......@@ -356,12 +356,8 @@ def is_revolving(self, label):
class AccountsPage(LoggedPage, HTMLPage):
def on_load(self):
super(AccountsPage, self).on_load()
no_account_message = CleanText('//td[contains(text(), "Votre contrat de banque à distance ne vous donne accès à aucun compte.")]')(self.doc)
if no_account_message:
raise NoAccountsException(no_account_message)
def has_no_account(self):
return CleanText('//td[contains(text(), "Votre contrat de banque à distance ne vous donne accès à aucun compte.")]')(self.doc)
@method
class iter_accounts(ListElement):
......@@ -1397,7 +1393,9 @@ def check_errors(self):
'Montant maximum autorisé au crédit pour ce compte',
'Débit interdit sur ce compte',
'Virement interdit sur compte clos',
"L'intitulé du virement ne peut contenir le ou les caractères suivants",]
"L'intitulé du virement ne peut contenir le ou les caractères suivants",
'La date ne peut être inférieure à la date du jour. Veuillez la corriger',
]
for message in messages:
if message in content:
......
......@@ -38,7 +38,6 @@ class ErehsbcModule(AbstractModule, CapBank):
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Identifiant', masked=False),
ValueBackendPassword('password', label='Code secret', regexp='^(\d{6})$'),
ValueBackendPassword('secret', label=u'Réponse secrète'),
Value('otp', label=u'Code de sécurité', default='', regexp='^(\d{6})$'))
BROWSER = ErehsbcBrowser
......
......@@ -19,11 +19,11 @@
from .login import LoginPage
from .accounts_page import AccountsPage, HistoryPage, ComingPage
from .transfer_page import DebitAccountsPage, CreditAccountsPage
from .transfer_page import DebitAccountsPage, CreditAccountsPage, TransferPage
from .profile_page import ProfilePage
__all__ = ['LoginPage', 'AccountsPage',
'HistoryPage', 'ComingPage',
'DebitAccountsPage', 'CreditAccountsPage',
'DebitAccountsPage', 'CreditAccountsPage', 'TransferPage',
'ProfilePage']
......@@ -27,10 +27,21 @@
class INGVirtKeyboard(SimpleVirtualKeyboard):
# from parent
tile_margin = 3
margin = (0, 4, 0, 0)
convert = 'RGB'
# for children
safe_tile_margin = 10
small_img_size = (100, 40)
alter_img_params = {
'radius': 2,
'percent': 135,
'threshold': 3,
'limit_pixel': 160
}
symbols = {
'0': ('117b18365105224c7207d3ec0ce7516f',),
'1': ('112a72c31ebdf0cdafb84e67c6e1f8f2',),
......@@ -50,10 +61,14 @@ def alter_image(self):
self.original_image = self.image
# create miniature of image to get more reliable hash
self.image = self.image.resize((100, 40), resample=Image.BILINEAR)
self.image = self.image.resize(self.small_img_size, resample=Image.BILINEAR)
# See ImageFilter.UnsharpMask from Pillow
self.image = self.image.filter(ImageFilter.UnsharpMask(radius=2, percent=135, threshold=3))
self.image = Image.eval(self.image, lambda px: 0 if px <= 160 else 255)
self.image = self.image.filter(ImageFilter.UnsharpMask(
radius=self.alter_img_params['radius'],
percent=self.alter_img_params['percent'],
threshold=self.alter_img_params['threshold'])
)
self.image = Image.eval(self.image, lambda px: 0 if px <= self.alter_img_params['limit_pixel'] else 255)
def password_tiles_coord(self, password):
# get image original size to get password coord
......@@ -73,7 +88,7 @@ def password_tiles_coord(self, password):
% (digit, self.path))
formatted_password = []
safe_margin = 10
safe_margin = self.safe_tile_margin
for tile in password_tiles:
# default matching_symbol is str(range(cols*rows))
x0 = (int(tile.matching_symbol) % self.cols) * tile_width
......@@ -101,7 +116,7 @@ def get_password_coord(self, img, password):
pin_position = Dict('pinPositions')(self.doc)
image = BytesIO(img)
vk = INGVirtKeyboard(image, 5, 2, browser=self.browser)
password_radom_coords = vk.password_tiles_coord(password)
vk = INGVirtKeyboard(image, cols=5, rows=2, browser=self.browser)
password_random_coords = vk.password_tiles_coord(password)
# pin positions (website side) start at 1, our positions start at 0
return [password_radom_coords[index-1] for index in pin_position]
return [password_random_coords[index-1] for index in pin_position]
......@@ -20,15 +20,46 @@
from __future__ import unicode_literals
from datetime import datetime
from io import BytesIO
from weboob.browser.pages import LoggedPage, JsonPage
from weboob.browser.elements import method, DictElement, ItemElement
from weboob.browser.filters.json import Dict
from weboob.browser.filters.standard import (
Env, Field,
)
from weboob.browser.filters.standard import Env, Field, Date
from weboob.capabilities.bank import Recipient
from .login import INGVirtKeyboard
class TransferINGVirtKeyboard(INGVirtKeyboard):
# from grand parent
tile_margin = 5
margin = None
convert = 'RGB'
# from parent
safe_tile_margin = 50
small_img_size = (125, 50) # original image size is (2420, 950)
alter_img_params = {
'radius': 2,
'percent': 150,
'threshold': 3,
'limit_pixel': 125
}
symbols = {
'0': 'e3e62175aa1a5ef8dc67639194caa880',
'1': '80245727e4e5f123fd64bbb1fa80dde0',
'2': '62cfc40429652190c996db741ac90830',
'3': 'bb2f87d32f688679745fe95ac31b80fd',
'4': 'a4b5e16c64817deb12ca6311cb98e59a',
'5': '56a8f3b4f068f9e2f93c4daa3a53dc17',
'6': 'b50f7e4a375153b9f6b029dc9b0a7e64',
'7': 'd52320c62c6157d0cadbb7a186153628',
'8': 'dd3fb25fc7f0765610b0ffe47da85330',
'9': 'ca55399a5b36da3fedcd1dbb73d72a2f'
}
class DebitAccountsPage(LoggedPage, JsonPage):
def get_debit_accounts_uid(self):
......@@ -59,3 +90,26 @@ def obj_category(self):
if Field('_is_internal_recipient')(self):
return 'Interne'
return 'Externe'
class TransferPage(LoggedPage, JsonPage):
@property
def suggested_date(self):
return Date(Dict('pinValidateResponse/executionSuggestedDate'), dayfirst=True)(self.doc)
def get_password_coord(self, password):
assert Dict('pinValidateResponse', default=None)(self.doc), "Transfer virtualkeyboard position has failed"
pin_position = Dict('pinValidateResponse/pinPositions')(self.doc)
image_url = '/secure/api-v1%s' % Dict('pinValidateResponse/keyPadUrl')(self.doc)
image = BytesIO(self.browser.open(image_url, headers={'Referer': self.browser.absurl('/secure/transfers/new')}).content)
vk = TransferINGVirtKeyboard(image, cols=5, rows=2, browser=self.browser)
password_random_coords = vk.password_tiles_coord(password)
# pin positions (website side) start at 1, our positions start at 0
return [password_random_coords[index-1] for index in pin_position]
@property
def transfer_is_validated(self):
return Dict('acknowledged')(self.doc)
......@@ -26,10 +26,11 @@
from weboob.browser import LoginBrowser, URL
from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded
from weboob.browser.exceptions import ClientError
from weboob.capabilities.bank import TransferBankError, TransferInvalidAmount
from .api import (
LoginPage, AccountsPage, HistoryPage, ComingPage,
DebitAccountsPage, CreditAccountsPage,
DebitAccountsPage, CreditAccountsPage, TransferPage,
ProfilePage,
)
from .web import StopPage, ActionNeededPage
......@@ -104,6 +105,8 @@ class IngAPIBrowser(LoginBrowser):
# transfer
credit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts/(?P<account_uid>.*)/creditAccounts', CreditAccountsPage)
debit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts', DebitAccountsPage)
init_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/validate', TransferPage)
exec_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/execute/pin', TransferPage)
# profile
informations = URL(r'/secure/api-v1/customer/info', ProfilePage)
......@@ -113,13 +116,14 @@ def __init__(self, *args, **kwargs):
super(IngAPIBrowser, self).__init__(*args, **kwargs)
self.old_browser = IngBrowser(*args, **kwargs)
self.transfer_data = None
def handle_login_error(self, r):
error_page = r.response.json()
assert 'error' in error_page, "Something went wrong in login"
error = error_page['error']
if error['code'] == 'AUTHENTICATION.INVALID_PIN_CODE':
if error['code'] in ('AUTHENTICATION.INVALID_PIN_CODE', 'AUTHENTICATION.INVALID_CIF_AND_BIRTHDATE_COMBINATION'):
raise BrowserIncorrectPassword(error['message'])
elif error['code'] in ('AUTHENTICATION.ACCOUNT_INACTIVE', 'AUTHENTICATION.ACCOUNT_LOCKED',
'AUTHENTICATION.NO_COMPLETE_ACCOUNT_FOUND'):
......@@ -314,13 +318,55 @@ def iter_recipients(self, account):
for recipient in self.page.iter_recipients(acc_uid=account._uid):
yield recipient
def handle_transfer_errors(self, r):
error_page = r.response.json()
assert 'error' in error_page, "Something went wrong, transfer is not created"
error = error_page['error']
error_msg = error['message']
if error['code'] == 'TRANSFER.INVALID_AMOUNT_MINIMUM':
raise TransferInvalidAmount(message=error_msg)
elif error['code'] == 'INPUT_INVALID' and len(error['values']):
for value in error['values']:
error_msg = '%s %s %s.' % (error_msg, value, error['values'][value])
raise TransferBankError(message=error_msg)
@need_to_be_on_website('api')
@need_login
def init_transfer(self, account, recipient, transfer):
raise NotImplementedError()
data = {
'amount': transfer.amount,
'executionDate': transfer.exec_date.strftime('%Y-%m-%d'),
'keyPadSize': {'width': 3800, 'height': 1520},
'label': transfer.label,
'fromAccount': account._uid,
'toAccount': recipient.id
}
try:
self.init_transfer_page.go(json=data, headers={'Referer': self.absurl('/secure/transfers/new')})
except ClientError as e:
self.handle_transfer_errors(e)
assert self.page.suggested_date == transfer.exec_date, "Transfer date is not valid"
self.transfer_data = data
self.transfer_data.pop('keyPadSize')
self.transfer_data['clickPositions'] = self.page.get_password_coord(self.password)
return transfer
@need_to_be_on_website('api')
@need_login
def execute_transfer(self, transfer):
raise NotImplementedError()
headers = {
'Referer': self.absurl('/secure/transfers/new'),
'Accept': 'application/json, text/plain, */*'
}
self.exec_transfer_page.go(json=self.transfer_data, headers=headers)
assert self.page.transfer_is_validated, "Transfer is not validated"
return transfer
############# CapDocument #############
@need_login
......
......@@ -23,6 +23,7 @@
import time
import json
from decimal import Decimal
from requests.exceptions import SSLError
from weboob.browser import LoginBrowser, URL, need_login
......@@ -179,6 +180,11 @@ def get_market_balance(self, account):
self.accountspage.go()
self.where = 'start'
if account.balance == Decimal('0'):
# some market accounts link with null balance redirect to logout page
# avoid it because it can crash iter accounts
return
self.change_space(account._space)
data = self.get_investments_data(account)
......
......@@ -19,13 +19,15 @@
from __future__ import unicode_literals
from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound
from decimal import Decimal
from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound, RecipientNotFound
from weboob.capabilities.bill import (
CapDocument, Bill, Subscription,
SubscriptionNotFound, DocumentNotFound, DocumentTypes,
)
from weboob.capabilities.profile import CapProfile
from weboob.capabilities.base import find_object
from weboob.capabilities.base import find_object, strict_find_object
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword, ValueDate
......@@ -99,10 +101,18 @@ def iter_transfer_recipients(self, account):
return self.browser.iter_recipients(account)
def init_transfer(self, transfer, **params):
raise NotImplementedError()
self.logger.info('Going to do a new transfer')
account = strict_find_object(self.iter_accounts(), id=transfer.account_id, error=AccountNotFound)
recipient = strict_find_object(self.iter_transfer_recipients(account), id=transfer.recipient_id, error=RecipientNotFound)
transfer.amount = Decimal(transfer.amount).quantize(Decimal('.01'))
return self.browser.init_transfer(account, recipient, transfer)
def execute_transfer(self, transfer, **params):
raise NotImplementedError()
return self.browser.execute_transfer(transfer)
############# CapDocument #############
def iter_subscription(self):
......