diff --git a/modules/bp/browser.py b/modules/bp/browser.py index 445370efc2bbbf5b401698d3777b88f28fc6453f..d2970cd6134b6365997e8ae0cd229671f4a78c09 100644 --- a/modules/bp/browser.py +++ b/modules/bp/browser.py @@ -129,6 +129,11 @@ class BPBrowser(LoginBrowser, StatesMixin): transfer_choose = URL(r'/voscomptes/canalXHTML/virement/mpiaiguillage/init-saisieComptes.ea', TransferChooseAccounts) transfer_complete = URL(r'/voscomptes/canalXHTML/virement/mpiaiguillage/soumissionChoixComptes-saisieComptes.ea', r'/voscomptes/canalXHTML/virement/virementSafran_national/init-creerVirementNational.ea', + # The two following urls are obtained after a redirection made after a form + # No parameters or data seem to change that the website go back to the evious folder, using ".." + # We can't do much since it is finaly handled by the module requests + r'/voscomptes/canalXHTML/virement/mpiaiguillage/\.\./virementSafran_national/init-creerVirementNational.ea', + r'/voscomptes/canalXHTML/virement/mpiaiguillage/\.\./virementSafran_sepa/init-creerVirementSepa.ea', r'/voscomptes/canalXHTML/virement/virementSafran_sepa/init-creerVirementSepa.ea', CompleteTransfer) transfer_confirm = URL(r'/voscomptes/canalXHTML/virement/virementSafran_pea/validerVirementPea-virementPea.ea', @@ -464,9 +469,11 @@ def iter_recipients(self, account_id): @need_login def init_transfer(self, account, recipient, amount, transfer): self.transfer_choose.stay_or_go() - self.page.init_transfer(account.id, recipient._value) - assert self.transfer_complete.is_here() - self.page.complete_transfer(amount, transfer) + self.page.init_transfer(account.id, recipient._value, amount) + + assert self.transfer_complete.is_here(), 'An error occured while validating the first part of the transfer.' + self.page.complete_transfer(transfer) + return self.page.handle_response(account, recipient, amount, transfer.label) @need_login diff --git a/modules/bp/module.py b/modules/bp/module.py index 23657ae278a337a3ce2ffff2ee01d5fb578009f8..5e8a1b46c463d53c2dd202dd73fcd2494bbf2bd1 100644 --- a/modules/bp/module.py +++ b/modules/bp/module.py @@ -19,6 +19,7 @@ from decimal import Decimal +from datetime import timedelta from weboob.capabilities.bank import CapBankWealth, CapBankTransferAddRecipient, Account, AccountNotFound, RecipientNotFound from weboob.capabilities.contact import CapContact from weboob.capabilities.base import find_object, strict_find_object, NotAvailable @@ -105,6 +106,9 @@ def transfer_check_label(self, old, new): old = old.encode('latin-1', errors="xmlcharrefreplace").decode('latin-1') return super(BPModule, self).transfer_check_label(old, new) + def transfer_check_date(self, old_exec_date, new_exec_date): + return old_exec_date <= new_exec_date <= old_exec_date + timedelta(days=2) + def execute_transfer(self, transfer, **params): return self.browser.execute_transfer(transfer) diff --git a/modules/bp/pages/transfer.py b/modules/bp/pages/transfer.py index e67431b9afe91005adfbe126ef02ae09605c0ba9..d4e0b69edcc38a25e475c31d816522d207c7cd94 100644 --- a/modules/bp/pages/transfer.py +++ b/modules/bp/pages/transfer.py @@ -25,7 +25,7 @@ TransferBankError, Transfer, TransferStep, NotAvailable, Recipient, AccountNotFound, AddRecipientBankError ) -from weboob.capabilities.base import find_object +from weboob.capabilities.base import find_object, empty from weboob.browser.pages import LoggedPage from weboob.browser.filters.standard import CleanText, Env, Regexp, Date, CleanDecimal from weboob.browser.filters.html import Attr, Link @@ -114,21 +114,23 @@ def parse(self, el): if self.env['id'] in self.parent.objects: # user add two recipients with same iban... raise SkipItem() - def init_transfer(self, account_id, recipient_value): + def init_transfer(self, account_id, recipient_value, amount): matched_values = [Attr('.', 'value')(option) for option in self.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option') \ if account_id in CleanText('.')(option)] assert len(matched_values) == 1 - form = self.get_form(xpath='//form[@class="formvirement"]') + form = self.get_form(xpath='//form[@class="choix-compte"]') form['donneesSaisie.idxCompteReceveur'] = recipient_value form['donneesSaisie.idxCompteEmetteur'] = matched_values[0] + form['donneesSaisie.montant'] = amount form.submit() class CompleteTransfer(LoggedPage, CheckTransferError): - def complete_transfer(self, amount, transfer): + def complete_transfer(self, transfer): form = self.get_form(xpath='//form[@method]') - form['montant'] = amount if 'commentaire' in form and transfer.label: + # for this bank the 'commentaire' is not a real label + # but a reason of transfer form['commentaire'] = transfer.label form['dateVirement'] = transfer.exec_date.strftime('%d/%m/%Y') form.submit() @@ -136,7 +138,10 @@ def complete_transfer(self, amount, transfer): class TransferConfirm(LoggedPage, CheckTransferError): def is_here(self): - return not CleanText('//p[contains(text(), "Vous pouvez le consulter dans le menu")]')(self.doc) + return ( + not CleanText('//p[contains(text(), "Vous pouvez le consulter dans le menu")]')(self.doc) + or self.doc.xpath('//input[@title="Confirmer la demande de virement"]') + ) def double_auth(self, transfer): code_needed = CleanText('//label[@for="code_securite"]')(self.doc) @@ -148,15 +153,21 @@ def confirm(self): form.submit() def handle_response(self, account, recipient, amount, reason): - account_txt = CleanText('//form//dl/dt[span[contains(text(), "biter")]]/following::dd[1]', replace=[(' ', '')])(self.doc) - recipient_txt = CleanText('//form//dl/dt[span[contains(text(), "diter")]]/following::dd[1]', replace=[(' ', '')])(self.doc) + # handle error + error_msg = CleanText('//div[@id="blocErreur"]')(self.doc) + if error_msg: + raise TransferBankError(message=error_msg) + + account_txt = CleanText('//form//h3[contains(text(), "débiter")]//following::span[1]', replace=[(' ', '')])(self.doc) + recipient_txt = CleanText('//form//h3[contains(text(), "créditer")]//following::span[1]', replace=[(' ', '')])(self.doc) assert account.id in account_txt or ''.join(account.label.split()) == account_txt, 'Something went wrong' assert recipient.id in recipient_txt or ''.join(recipient.label.split()) == recipient_txt, 'Something went wrong' - r_amount = CleanDecimal('//form//dl/dt[span[contains(text(), "Montant")]]/following::dd[1]', replace_dots=True)(self.doc) - exec_date = Date(CleanText('//form//dl/dt[span[contains(text(), "Date")]]/following::dd[1]'), dayfirst=True)(self.doc) - currency = FrenchTransaction.Currency('//form//dl/dt[span[contains(text(), "Montant")]]/following::dd[1]')(self.doc) + amount_element = self.doc.xpath('//h3[contains(text(), "Montant du virement")]//following::span[@class="price"]')[0] + r_amount = CleanDecimal.French('.')(amount_element) + exec_date = Date(CleanText('//h3[contains(text(), "virement")]//following::span[@class="date"]'), dayfirst=True)(self.doc) + currency = FrenchTransaction.Currency('.')(amount_element) transfer = Transfer() transfer.currency = currency @@ -175,20 +186,53 @@ def handle_response(self, account, recipient, amount, reason): class TransferSummary(LoggedPage, CheckTransferError): def handle_response(self, transfer): - # NotAvailable in case of future exec_date not on a working day. - transfer.id = Regexp(CleanText('//div[@class="bloc Tmargin"]'), 'virement N.+ (\d+) ', default=NotAvailable)(self.doc) - if not transfer.id: + summary_filter = CleanText( + '//div[contains(@class, "bloc-recapitulatif")]//p' + ) + + # handle error + if "Votre virement n'a pas pu" in summary_filter(self.doc): + raise TransferBankError(message=summary_filter(self.doc)) + + transfer_id = Regexp(summary_filter, r'référence n° (\d+)', default=None)(self.doc) + # not always available + if transfer_id and not transfer.id: + transfer.id = transfer_id + else: # TODO handle transfer with sms code. if 'veuillez saisir votre code de validation' in CleanText('//div[@class="bloc Tmargin"]')(self.doc): raise NotImplementedError() + # WARNING: At this point, the transfer was made. + # The following code is made to retrieve the transfer execution date, + # so there is no falsy data. + # But the bp website is unstable with changing layout and messages. + # One of the goals here is for the code not to crash to avoid the user thinking + # that the transfer was not made while it was. + + old_date = transfer.exec_date + # the date was modified because on a weekend + if 'date correspondant à un week-end' in summary_filter(self.doc): + transfer.exec_date = Date(Regexp( + summary_filter, + r'jour ouvré suivant \((\d{2}/\d{2}/\d{4})\)', + default='' + ), dayfirst=True, default=NotAvailable)(self.doc) + self.logger.warning('The transfer execution date changed from %s to %s' % (old_date.strftime('%Y-%m-%d'), transfer.exec_date.strftime('%Y-%m-%d'))) + # made today + elif 'date du jour de ce virement' in summary_filter(self.doc): # there are several regexp for transfer date: # Date ([\d\/]+)|le ([\d\/]+)|suivant \(([\d\/]+)\) # be more passive to avoid impulsive reaction from user transfer.exec_date = Date(Regexp( - CleanText('//div[@class="bloc Tmargin"]'), - r' (\d{2}/\d{2}/\d{4})' - ), dayfirst=True)(self.doc) + summary_filter, + r' (\d{2}/\d{2}/\d{4})', + default='' + ), dayfirst=True, default=NotAvailable)(self.doc) + # else: using the same date because the website does not give one + + if empty(transfer.exec_date): + transfer.exec_date = old_date return transfer