Commit 8bc6dc03 authored by Romain Bignon's avatar Romain Bignon

Update of modules

parent 999860a6
......@@ -23,7 +23,9 @@ from collections import OrderedDict
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 @@ class AmazonModule(Module, CapDocument):
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 DocumentsPage(LoggedPage, HTMLPage):
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 @@ class DocumentsPage(LoggedPage, HTMLPage):
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 @@ class Login2Page(LoginPage):
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 @@ class Login2Page(LoginPage):
} ]
}
}
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 @@ class Login2Page(LoginPage):
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 @@ from .pages import (
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 @@ class ErrorPage(LoggedPage, HTMLPage):
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 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
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 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
)
)
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 CenetLoanPage(LoggedPage, CenetJsonPage):
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 @@ class IndexPage(LoggedPage, HTMLPage):
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 @@ class IndexPage(LoggedPage, HTMLPage):
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 @@ class CragrAPI(LoginBrowser):
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 @@ class CragrAPI(LoginBrowser):
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 @@ class CragrAPI(LoginBrowser):
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 @@ class CragrAPI(LoginBrowser):
# 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 @@ ACCOUNT_TYPES = {
'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 @@ class CreditDuNordBrowser(LoginBrowser):
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.profiles import Wget
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 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin):
# 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 @@ class CreditMutuelBrowser(LoginBrowser, StatesMixin):
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.standard import (
)
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 @@ class item_account_generic(ItemElement):
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 @@ class InternalTransferPage(LoggedPage, HTMLPage):
'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 @@ from weboob.browser.filters.json import Dict
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 @@ class INGVirtKeyboard(SimpleVirtualKeyboard):
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 @@ class INGVirtKeyboard(SimpleVirtualKeyboard):
% (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 @@ class LoginPage(JsonPage):
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 @@ class CreditAccountsPage(LoggedPage, JsonPage):
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 functools import wraps
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 @@ class IngAPIBrowser(LoginBrowser):
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 @@ class IngAPIBrowser(LoginBrowser):
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 hashlib
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 @@ class IngBrowser(LoginBrowser):
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 @@ class INGModule(Module, CapBankWealth, CapBankTransfer, CapDocument, CapProfile)
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):
......
......@@ -5,16 +5,16 @@
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# 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.
#
# This weboob module 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 Affero General Public License for more details.
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
......
......@@ -5,16 +5,16 @@
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or mo