From 04970b096dc5af424b66ad20f08201e710d40966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lande=20Adrien?= Date: Tue, 5 Nov 2019 18:23:27 +0100 Subject: [PATCH] [bp] repaired transfer The website layout changed. The errors are handled by the new and the old code because the layout is instable. When the execution date corresponds to the week end, the website changes itself the date. For now, it does not handle OTP or decoupled. --- modules/bp/browser.py | 13 ++++-- modules/bp/module.py | 4 ++ modules/bp/pages/transfer.py | 78 ++++++++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/modules/bp/browser.py b/modules/bp/browser.py index 445370efc2..d2970cd613 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 23657ae278..5e8a1b46c4 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 e67431b9af..d4e0b69edc 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 -- GitLab