Commit 976c68dd authored by Guillaume Risbourg's avatar Guillaume Risbourg Committed by Vincent A

[bp] Fix transfer execution with certicode

parent fdf714bd
......@@ -40,7 +40,8 @@ from weboob.tools.compat import urlsplit, urlunsplit, parse_qsl
from weboob.tools.decorators import retry
from weboob.capabilities.bank import (
Account, Recipient, AddRecipientStep, TransferStep,
TransferInvalidEmitter, RecipientInvalidOTP,
TransferInvalidEmitter, RecipientInvalidOTP, TransferBankError,
AddRecipientBankError, TransferInvalidOTP,
)
from weboob.tools.value import Value, ValueBool
......@@ -51,7 +52,8 @@ from .pages import (
ValidateCountry, ConfirmPage, RcptSummary,
SubscriptionPage, DownloadPage, ProSubscriptionPage,
RevolvingAttributesPage,
TwoFAPage, Validated2FAPage, SmsPage, DecoupledPage, HonorTransferPage, RecipientSubmitDevicePage, RcptErrorPage,
TwoFAPage, Validated2FAPage, SmsPage, DecoupledPage, HonorTransferPage,
RecipientSubmitDevicePage, OtpErrorPage,
)
from .pages.accounthistory import (
LifeInsuranceInvest, LifeInsuranceHistory, LifeInsuranceHistoryInv, RetirementHistory,
......@@ -260,6 +262,18 @@ class BPBrowser(LoginBrowser, StatesMixin):
HonorTransferPage
)
# transfer_summary needs to be before transfer_confirm because both
# have one url in common with different is_here conditions.
# We need to check the is_here of transfer_summary first to be on the correct page.
transfer_summary = URL(
r'/voscomptes/canalXHTML/virement/virementSafran_national/confirmerVirementNational-virementNational.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_pea/confirmerInformations-virementPea.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/confirmer-creerVirementSepa.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_national/confirmer-creerVirementNational.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/confirmerInformations-virementSepa.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/finalisation-creerVirementSepa.ea',
TransferSummary
)
transfer_confirm = URL(
r'/voscomptes/canalXHTML/virement/virementSafran_pea/validerVirementPea-virementPea.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/valider-creerVirementSepa.ea',
......@@ -273,14 +287,6 @@ class BPBrowser(LoginBrowser, StatesMixin):
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/confirmer-creerVirementSepa.ea',
TransferConfirm
)
transfer_summary = URL(
r'/voscomptes/canalXHTML/virement/virementSafran_national/confirmerVirementNational-virementNational.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_pea/confirmerInformations-virementPea.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/confirmer-creerVirementSepa.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_national/confirmer-creerVirementNational.ea',
r'/voscomptes/canalXHTML/virement/virementSafran_sepa/confirmerInformations-virementSepa.ea',
TransferSummary
)
create_recipient = URL(
r'/voscomptes/canalXHTML/virement/mpiGestionBeneficiairesVirementsCreationBeneficiaire/init-creationBeneficiaire.ea',
......@@ -303,9 +309,9 @@ class BPBrowser(LoginBrowser, StatesMixin):
r'/voscomptes/canalXHTML/virement/mpiGestionBeneficiairesVirementsCreationBeneficiaire/validerRecapBeneficiaire-creationBeneficiaire.ea',
ConfirmPage
)
rcpt_error = URL(
otp_benef_transfer_error = URL(
r'/voscomptes/canalXHTML/securisation/otp/validation-securisationOTP.ea',
RcptErrorPage,
OtpErrorPage,
)
rcpt_summary = URL(
r'/voscomptes/canalXHTML/virement/mpiGestionBeneficiairesVirementsCreationBeneficiaire/finalisation-creationBeneficiaire.ea',
......@@ -378,7 +384,7 @@ class BPBrowser(LoginBrowser, StatesMixin):
accounts = None
__states__ = ('need_reload_state', 'sms_form', 'recipient_form')
__states__ = ('need_reload_state', 'sms_form')
def __init__(self, config, *args, **kwargs):
self.weboob = kwargs.pop('weboob')
......@@ -398,7 +404,6 @@ class BPBrowser(LoginBrowser, StatesMixin):
proxy=self.PROXIES
)
self.recipient_form = None
self.sms_form = None
self.need_reload_state = None
......@@ -805,7 +810,6 @@ class BPBrowser(LoginBrowser, StatesMixin):
return self.page.handle_response(transfer)
@need_login
def validate_transfer_eligibility(self, transfer, **params):
# Using ValueBool to be sure to handle any kind of response.
# If it is not the accepted strings/bools, it crashes.
......@@ -821,14 +825,33 @@ class BPBrowser(LoginBrowser, StatesMixin):
return self.page.handle_response(transfer)
raise TransferInvalidEmitter("Impossible d'effectuer un virement sans attestation sur l'honneur que vous êtes bien le titulaire, représentant légal ou mandataire du compte à vue ou CCP.")
@need_login
def execute_transfer(self, transfer, code=None):
assert self.transfer_confirm.is_here(), 'Case not handled.'
self.page.confirm()
# Should only happen if double auth.
def validate_transfer_code(self, transfer, code):
if not self.post_code(code):
raise TransferBankError('La validation du code SMS a expirée.')
if self.otp_benef_transfer_error.is_here():
error = self.page.get_error()
if error:
if 'Votre code sécurité est incorrect' in error:
raise TransferInvalidOTP(message=error)
raise AssertionError('Unhandled error message : "%s"' % error)
return transfer
def execute_transfer(self, transfer):
# If we just validated a code we land on transfer_summary.
# If we just initiated the transfer we land on transfer_confirm.
if self.transfer_confirm.is_here():
self.page.choose_device()
self.page.double_auth(transfer)
# This will send a sms if a certicode validation is needed
self.page.confirm()
if self.transfer_confirm.is_here() and self.page.is_certicode_needed():
self.need_reload_state = True
self.sms_form = self.page.get_sms_form()
raise TransferStep(
transfer,
Value('code', label='Veuillez saisir le code de validation reçu par SMS'),
)
return self.page.handle_response(transfer)
def build_recipient(self, recipient):
......@@ -843,12 +866,18 @@ class BPBrowser(LoginBrowser, StatesMixin):
return r
def post_code(self, code):
data = {}
for k, v in self.recipient_form.items():
if k != 'url':
data[k] = v
data['codeOTPSaisi'] = code
self.location(self.recipient_form['url'], data=data)
url = self.sms_form.pop('url')
self.sms_form['codeOTPSaisi'] = code
self.location(url, data=self.sms_form, allow_redirects=False)
if self.response.status_code == 302:
location = self.response.headers.get('location')
if 'loginform' in location:
# The form timed out
return False
self.location(location)
return True
def end_new_recipient_with_polling(self, recipient):
polling_url = self.absurl(
......@@ -903,10 +932,11 @@ class BPBrowser(LoginBrowser, StatesMixin):
if 'code' in params:
# Case of SMS OTP
self.post_code(params['code'])
self.recipient_form = None
if not self.post_code(params['code']):
raise AddRecipientBankError('La validation du code SMS a expirée.')
self.sms_form = None
if self.rcpt_error.is_here():
if self.otp_benef_transfer_error.is_here():
error = self.page.get_error()
if error:
if 'Votre code sécurité est incorrect' in error:
......
......@@ -105,6 +105,8 @@ class BPModule(
if 'transfer_honor_savings' in params:
return self.browser.validate_transfer_eligibility(transfer, **params)
elif 'code' in params:
return self.browser.validate_transfer_code(transfer, params['code'])
self.logger.info('Going to do a new transfer')
account = strict_find_object(self.iter_accounts(), iban=transfer.account_iban)
......
......@@ -30,7 +30,7 @@ from .transfer import (
TransferSummary, CreateRecipient, ValidateRecipient,
ValidateCountry, ConfirmPage, RcptSummary,
HonorTransferPage, RecipientSubmitDevicePage,
RcptErrorPage,
OtpErrorPage,
)
from .subscription import SubscriptionPage, DownloadPage, ProSubscriptionPage
......@@ -40,5 +40,5 @@ __all__ = [
'AccountDesactivate', 'TransferChooseAccounts', 'CompleteTransfer', 'TransferConfirm', 'TransferSummary', 'UnavailablePage',
'CardsList', 'AccountRIB', 'Advisor', 'CreateRecipient', 'ValidateRecipient', 'ValidateCountry', 'ConfirmPage', 'RcptSummary',
'SubscriptionPage', 'DownloadPage', 'ProSubscriptionPage', 'RevolvingAttributesPage', 'Validated2FAPage', 'TwoFAPage',
'SmsPage', 'DecoupledPage', 'HonorTransferPage', 'RecipientSubmitDevicePage', 'RcptErrorPage',
'SmsPage', 'DecoupledPage', 'HonorTransferPage', 'RecipientSubmitDevicePage', 'OtpErrorPage',
]
......@@ -25,26 +25,25 @@ import re
from datetime import datetime
from weboob.capabilities.bank import (
TransferBankError, Transfer, TransferStep, Recipient,
AccountNotFound, AddRecipientBankError, Emitter,
TransferBankError, Transfer, Recipient, AccountNotFound,
AddRecipientBankError, Emitter,
)
from weboob.capabilities.base import find_object, empty, NotAvailable
from weboob.browser.pages import LoggedPage
from weboob.browser.pages import LoggedPage, PartialHTMLPage
from weboob.browser.filters.standard import CleanText, Env, Regexp, Date, CleanDecimal, Currency
from weboob.browser.filters.html import Attr, Link
from weboob.browser.elements import ListElement, ItemElement, method, SkipItem
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.capabilities.bank.iban import is_iban_valid
from weboob.tools.value import Value
from weboob.tools.compat import urljoin
from weboob.exceptions import BrowserUnavailable, AuthMethodNotImplemented
from weboob.exceptions import BrowserUnavailable
from .base import MyHTMLPage
class CheckTransferError(MyHTMLPage):
def on_load(self):
MyHTMLPage.on_load(self)
super(CheckTransferError, self).on_load()
error = CleanText("""
//span[@class="app_erreur"]
| //p[@class="warning"]
......@@ -219,23 +218,19 @@ class TransferConfirm(LoggedPage, CheckTransferError):
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"]') # appears when there is no need for otp/polling
or self.doc.xpath("//span[contains(text(), 'cliquant sur le bouton \"CONFIRMER\"')]") # appears on the page when there is a 'Confirmer' button or not
or CleanText('//label[contains(text(), "saisir votre code de validation reçu par SMS)]')(self.doc) # appears when there is an otp
)
def choose_device(self):
# When there is no "Confirmer" button,
# it means that the device pop up appeared (it is called by js)
if (
not self.doc.xpath('//input[@value="Confirmer"]')
or self.doc.xpath('//input[@name="codeOTPSaisi"]')
):
# transfer validation form with sms cannot be tested yet
raise AuthMethodNotImplemented()
raise AssertionError('Should not be on confirmation page after posting the form.')
def double_auth(self, transfer):
code_needed = CleanText('//label[@for="code_securite"]')(self.doc)
if code_needed:
raise TransferStep(transfer, Value('code', label=code_needed))
def is_certicode_needed(self):
return CleanText('//div[contains(text(), "veuillez saisir votre code de validation reçu par SMS")]')(self.doc)
def get_sms_form(self):
form = self.get_form(name='SaisieOTP')
# Confirmation url is relative to the current page. We need to
# build it now or the relative path will fail when reloading state
# because we do not reload the url in it.
form['url'] = self.absurl(form.url)
return form
def confirm(self):
form = self.get_form(id='formID')
......@@ -254,8 +249,8 @@ class TransferConfirm(LoggedPage, CheckTransferError):
'//form//h3[contains(text(), "créditer")]//following::span[1]', replace=[(' ', '')]
)(self.doc)
assert transfer.account_id in account_txt or ''.join(transfer.account_label.split()) == account_txt, 'Something went wrong'
assert transfer.recipient_id in recipient_txt or ''.join(transfer.recipient_label.split()) == recipient_txt, 'Something went wrong'
assert transfer.account_id in account_txt, 'Something went wrong'
assert transfer.recipient_id in recipient_txt, 'Something went wrong'
exec_date = Date(
CleanText('//h3[contains(text(), "virement")]//following::span[@class="date"]'),
......@@ -294,10 +289,6 @@ class TransferSummary(LoggedPage, CheckTransferError):
# 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,
......@@ -409,14 +400,16 @@ class ConfirmPage(CheckErrorsPage):
def set_browser_form(self):
form = self.get_form(name='SaisieOTP')
self.browser.recipient_form = dict((k, v) for k, v in form.items() if v)
self.browser.sms_form = dict((k, v) for k, v in form.items() if v)
# Confirmation url is relative to the current page. We need to
# build it now or the relative path will fail when reloading state
# because we do not reload the url in it.
self.browser.recipient_form['url'] = urljoin(self.url, form.url)
self.browser.sms_form['url'] = urljoin(self.url, form.url)
class RcptErrorPage(LoggedPage, MyHTMLPage):
class OtpErrorPage(LoggedPage, PartialHTMLPage):
# Need PartialHTMLPage because sometimes we land on this page with
# a status_code 302, so the page is empty and the build_doc crash.
def get_error(self):
return CleanText('//form//span[@class="warning"]')(self.doc)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment